def test_metadata_service_delete(self):
        l1, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        novelist_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.NOVELIST,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        novelist_service.username = "******"
        novelist_service.password = "******"
        novelist_service.libraries = [l1]

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            assert_raises(AdminNotAuthorized,
                          self.manager.admin_metadata_services_controller.process_delete,
                          novelist_service.id)

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = self.manager.admin_metadata_services_controller.process_delete(novelist_service.id)
            eq_(response.status_code, 200)

        service = get_one(self._db, ExternalIntegration, id=novelist_service.id)
        eq_(None, service)
예제 #2
0
    def test_uniqueness_constraints_with_library(self):
        # If library is provided, then license_pool + library + type +
        # start must be unique.
        pool = self._licensepool(edition=None)
        now = utc_now()
        kwargs = dict(
            license_pool=pool,
            library=self._default_library,
            type=CirculationEvent.DISTRIBUTOR_TITLE_ADD,
        )
        event = create(self._db, CirculationEvent, start=now, **kwargs)

        # Different timestamp -- no problem.
        now2 = utc_now()
        event2 = create(self._db, CirculationEvent, start=now2, **kwargs)
        assert event != event2

        # Reuse the timestamp and you get an IntegrityError which ruins the
        # entire transaction.
        pytest.raises(IntegrityError,
                      create,
                      self._db,
                      CirculationEvent,
                      start=now,
                      **kwargs)
        self._db.rollback()
    def test_check_name_unique(self):
       kwargs = dict(protocol=ExternalIntegration.OPDS_REGISTRATION,
                     goal=ExternalIntegration.DISCOVERY_GOAL,)

       existing_service, ignore = create(self._db, ExternalIntegration, name="existing service", **kwargs)
       new_service, ignore = create(self._db, ExternalIntegration, name="new service", **kwargs)

       m = self.manager.admin_discovery_services_controller.check_name_unique

       # Try to change new service so that it has the same name as existing service
       # -- this is not allowed.
       result = m(new_service, existing_service.name)
       eq_(result, INTEGRATION_NAME_ALREADY_IN_USE)

       # Try to edit existing service without changing its name -- this is fine.
       eq_(
           None,
           m(existing_service, existing_service.name)
       )

       # Changing the existing service's name is also fine.
       eq_(
            None,
            m(existing_service, "new name")
       )
    def test_analytics_services_post_edit(self):
        l1, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        l2, ignore = create(
            self._db, Library, name="Library 2", short_name="L2",
        )

        ga_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=GoogleAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )
        ga_service.url = "oldurl"
        ga_service.libraries = [l1]

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("id", ga_service.id),
                ("name", "some other analytics name"),
                ("protocol", GoogleAnalyticsProvider.__module__),
                (ExternalIntegration.URL, "http://test"),
                ("libraries", json.dumps([{"short_name": "L2", "tracking_id": "l2id"}])),
            ])
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            eq_(response.status_code, 200)

        eq_(ga_service.id, int(response.response[0]))
        eq_(GoogleAnalyticsProvider.__module__, ga_service.protocol)
        eq_("http://test", ga_service.url)
        eq_([l2], ga_service.libraries)
        eq_("l2id", ConfigurationSetting.for_library_and_externalintegration(
                self._db, GoogleAnalyticsProvider.TRACKING_ID, l2, ga_service).value)
예제 #5
0
    def test_patron_annotations_are_descending(self):
        pool1 = self._licensepool(None)
        pool2 = self._licensepool(None)
        patron = self._patron()
        annotation1, ignore = create(
            self._db,
            Annotation,
            patron=patron,
            identifier=pool2.identifier,
            motivation=Annotation.IDLING,
            content="The content",
            active=True,
        )
        annotation2, ignore = create(
            self._db,
            Annotation,
            patron=patron,
            identifier=pool2.identifier,
            motivation=Annotation.IDLING,
            content="The content",
            active=True,
        )

        yesterday = utc_now() - datetime.timedelta(days=1)
        today = utc_now()
        annotation1.timestamp = yesterday
        annotation2.timestamp = today

        assert 2 == len(patron.annotations)
        assert annotation2 == patron.annotations[0]
        assert annotation1 == patron.annotations[1]
    def test_metadata_services_post_edit(self):
        l1, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        l2, ignore = create(
            self._db, Library, name="Library 2", short_name="L2",
        )

        novelist_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.NOVELIST,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        novelist_service.username = "******"
        novelist_service.password = "******"
        novelist_service.libraries = [l1]

        controller = self.manager.admin_metadata_services_controller
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "Name"),
                ("id", novelist_service.id),
                ("protocol", ExternalIntegration.NOVELIST),
                (ExternalIntegration.USERNAME, "user"),
                (ExternalIntegration.PASSWORD, "pass"),
                ("libraries", json.dumps([{"short_name": "L2"}])),
            ])
            response = controller.process_post()
            eq_(response.status_code, 200)
    def test_metadata_service_delete(self):
        l1, ignore = create(
            self._db,
            Library,
            name="Library 1",
            short_name="L1",
        )
        novelist_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.NOVELIST,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        novelist_service.username = "******"
        novelist_service.password = "******"
        novelist_service.libraries = [l1]

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            assert_raises(
                AdminNotAuthorized,
                self.manager.admin_metadata_services_controller.process_delete,
                novelist_service.id)

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = self.manager.admin_metadata_services_controller.process_delete(
                novelist_service.id)
            eq_(response.status_code, 200)

        service = get_one(self._db,
                          ExternalIntegration,
                          id=novelist_service.id)
        eq_(None, service)
    def test_check_name_unique(self):
        kwargs = dict(protocol=ExternalIntegration.CDN,
                      goal=ExternalIntegration.CDN_GOAL)

        existing_service, ignore = create(self._db,
                                          ExternalIntegration,
                                          name="existing service",
                                          **kwargs)
        new_service, ignore = create(self._db,
                                     ExternalIntegration,
                                     name="new service",
                                     **kwargs)

        m = self.manager.admin_cdn_services_controller.check_name_unique

        # Try to change new service so that it has the same name as existing service
        # -- this is not allowed.
        result = m(new_service, existing_service.name)
        assert result == INTEGRATION_NAME_ALREADY_IN_USE

        # Try to edit existing service without changing its name -- this is fine.
        assert None == m(existing_service, existing_service.name)

        # Changing the existing service's name is also fine.
        assert None == m(existing_service, "new name")
    def test_analytics_service_delete(self):
        l1, ignore = create(
            self._db,
            Library,
            name="Library 1",
            short_name="L1",
        )
        ga_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=GoogleAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )
        ga_service.url = "oldurl"
        ga_service.libraries = [l1]

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            pytest.raises(
                AdminNotAuthorized,
                self.manager.admin_analytics_services_controller.
                process_delete,
                ga_service.id,
            )

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = self.manager.admin_analytics_services_controller.process_delete(
                ga_service.id)
            assert response.status_code == 200

        service = get_one(self._db, ExternalIntegration, id=ga_service.id)
        assert None == service
    def test_annotations_for(self):
        patron = self._patron()

        # The patron doesn't have any annotations yet.
        eq_([], AnnotationWriter.annotations_for(patron))

        identifier = self._identifier()
        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
        )

        # The patron has one annotation.
        eq_([annotation], AnnotationWriter.annotations_for(patron))
        eq_([annotation], AnnotationWriter.annotations_for(patron, identifier))

        identifier2 = self._identifier()
        annotation2, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier2,
            motivation=Annotation.IDLING,
        )

        # The patron has two annotations for different identifiers.
        eq_(set([annotation, annotation2]), set(AnnotationWriter.annotations_for(patron)))
        eq_([annotation], AnnotationWriter.annotations_for(patron, identifier))
        eq_([annotation2], AnnotationWriter.annotations_for(patron, identifier2))
예제 #11
0
    def test_configuration_relevant_collection_change_updates_configuration(
            self):
        """When you add a relevant item to a SQLAlchemy collection, such as
        adding a Collection to library.collections,
        site_configuration_has_changed is called.
        """

        # Creating a collection calls the method via an 'after_insert'
        # event on Collection.
        library = self._default_library
        collection = self._collection()
        self._db.commit()
        self.mock.assert_was_called()

        # Adding the collection to the library calls the method via
        # an 'append' event on Collection.libraries.
        library.collections.append(collection)
        self._db.commit()
        self.mock.assert_was_called()

        # Associating a CachedFeed with the library does _not_ call
        # the method, because nothing changed on the Library object and
        # we don't listen for 'append' events on Library.cachedfeeds.
        create(self._db,
               CachedFeed,
               type="page",
               pagination="",
               facets="",
               library=library)
        self._db.commit()
        self.mock.assert_was_not_called()
    def test_parse_treats_duplicates_as_interchangeable(self):
        self.pool.loan_to(self.patron)

        # Due to an earlier race condition, two duplicate annotations
        # were put in the database.
        a1, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        a2, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        assert a1 != a2

        # Parsing the annotation again retrieves one or the other
        # of the annotations rather than crashing or creating a third
        # annotation.
        data = self._sample_jsonld()
        data = json.dumps(data)
        annotation = AnnotationParser.parse(self._db, data, self.patron)
        assert annotation in (a1, a2)
    def test_patron_auth_service_delete(self):
        l1, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=SimpleAuthenticationProvider.__module__,
            goal=ExternalIntegration.PATRON_AUTH_GOAL
        )
        auth_service.setting(BasicAuthenticationProvider.TEST_IDENTIFIER).value = "old_user"
        auth_service.setting(BasicAuthenticationProvider.TEST_PASSWORD).value = "old_password"
        auth_service.libraries = [l1]

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            assert_raises(AdminNotAuthorized,
                          self.manager.admin_patron_auth_services_controller.process_delete,
                          auth_service.id)

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = self.manager.admin_patron_auth_services_controller.process_delete(auth_service.id)
            eq_(response.status_code, 200)

        service = get_one(self._db, ExternalIntegration, id=auth_service.id)
        eq_(None, service)
예제 #14
0
    def test_check_name_unique(self):
       kwargs = dict(protocol=ExternalIntegration.OPDS_REGISTRATION,
                     goal=ExternalIntegration.DISCOVERY_GOAL,)

       existing_service, ignore = create(self._db, ExternalIntegration, name="existing service", **kwargs)
       new_service, ignore = create(self._db, ExternalIntegration, name="new service", **kwargs)

       m = self.manager.admin_discovery_services_controller.check_name_unique

       # Try to change new service so that it has the same name as existing service
       # -- this is not allowed.
       result = m(new_service, existing_service.name)
       eq_(result, INTEGRATION_NAME_ALREADY_IN_USE)

       # Try to edit existing service without changing its name -- this is fine.
       eq_(
           None,
           m(existing_service, existing_service.name)
       )

       # Changing the existing service's name is also fine.
       eq_(
            None,
            m(existing_service, "new name")
       )
예제 #15
0
    def test_annotations_for(self):
        patron = self._patron()

        # The patron doesn't have any annotations yet.
        eq_([], AnnotationWriter.annotations_for(patron))

        identifier = self._identifier()
        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
        )

        # The patron has one annotation.
        eq_([annotation], AnnotationWriter.annotations_for(patron))
        eq_([annotation], AnnotationWriter.annotations_for(patron, identifier))

        identifier2 = self._identifier()
        annotation2, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier2,
            motivation=Annotation.IDLING,
        )

        # The patron has two annotations for different identifiers.
        eq_(set([annotation, annotation2]), set(AnnotationWriter.annotations_for(patron)))
        eq_([annotation], AnnotationWriter.annotations_for(patron, identifier))
        eq_([annotation2], AnnotationWriter.annotations_for(patron, identifier2))
예제 #16
0
    def test_metadata_services_post_edit(self):
        l1, ignore = create(
            self._db,
            Library,
            name="Library 1",
            short_name="L1",
        )
        l2, ignore = create(
            self._db,
            Library,
            name="Library 2",
            short_name="L2",
        )
        novelist_service = self.create_service("NOVELIST")
        novelist_service.username = "******"
        novelist_service.password = "******"
        novelist_service.libraries = [l1]

        controller = self.manager.admin_metadata_services_controller
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "Name"),
                ("id", novelist_service.id),
                ("protocol", ExternalIntegration.NOVELIST),
                (ExternalIntegration.USERNAME, "user"),
                (ExternalIntegration.PASSWORD, "pass"),
                ("libraries", json.dumps([{
                    "short_name": "L2"
                }])),
            ])
            response = controller.process_post()
            eq_(response.status_code, 200)
    def test_staff_email(self):
        super(TestGoogleOAuthAdminAuthenticationProvider, self).setup()
        auth_integration, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.GOOGLE_OAUTH,
            goal=ExternalIntegration.ADMIN_AUTH_GOAL
        )

        nypl_admin = create(self._db, Admin, email="*****@*****.**")
        bpl_admin = create(self._db, Admin, email="*****@*****.**")

        # If no domains are set, the admin must already exist in the db
        # to be considered library staff.
        google = GoogleOAuthAdminAuthenticationProvider(auth_integration, "", test_mode=True)

        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(False, google.staff_email(self._db, "*****@*****.**"))

        # If domains are set, the admin's domain can match one of the domains
        # if the admin doesn't exist yet.
        auth_integration.libraries += [self._default_library]
        setting = ConfigurationSetting.for_library_and_externalintegration(
            self._db, "domains", self._default_library, auth_integration)
        setting.value = json.dumps(["nypl.org"])
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(False, google.staff_email(self._db, "*****@*****.**"))

        setting.value = json.dumps(["nypl.org", "bklynlibrary.org"])
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
        eq_(True, google.staff_email(self._db, "*****@*****.**"))
예제 #18
0
    def test_parse_treats_duplicates_as_interchangeable(self):
        self.pool.loan_to(self.patron)

        # Due to an earlier race condition, two duplicate annotations
        # were put in the database.
        a1, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        a2, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        assert a1 != a2

        # Parsing the annotation again retrieves one or the other
        # of the annotations rather than crashing or creating a third
        # annotation.
        data = self._sample_jsonld()
        data = json.dumps(data)
        annotation = AnnotationParser.parse(self._db, data, self.patron)
        assert annotation in (a1, a2)
예제 #19
0
    def test_patron_auth_service_delete(self):
        l1, ignore = create(
            self._db,
            Library,
            name="Library 1",
            short_name="L1",
        )
        auth_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=SimpleAuthenticationProvider.__module__,
            goal=ExternalIntegration.PATRON_AUTH_GOAL,
        )
        auth_service.setting(
            BasicAuthenticationProvider.TEST_IDENTIFIER).value = "old_user"
        auth_service.setting(
            BasicAuthenticationProvider.TEST_PASSWORD).value = "old_password"
        auth_service.libraries = [l1]

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            pytest.raises(
                AdminNotAuthorized,
                self.manager.admin_patron_auth_services_controller.
                process_delete,
                auth_service.id,
            )

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = (self.manager.admin_patron_auth_services_controller.
                        process_delete(auth_service.id))
            assert response.status_code == 200

        service = get_one(self._db, ExternalIntegration, id=auth_service.id)
        assert None == service
    def test_analytics_services_post_edit(self):
        l1, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        l2, ignore = create(
            self._db, Library, name="Library 2", short_name="L2",
        )

        ga_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=GoogleAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )
        ga_service.url = "oldurl"
        ga_service.libraries = [l1]

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("id", ga_service.id),
                ("name", "some other analytics name"),
                ("protocol", GoogleAnalyticsProvider.__module__),
                (ExternalIntegration.URL, "http://test"),
                ("libraries", json.dumps([{"short_name": "L2", "tracking_id": "l2id"}])),
            ])
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            eq_(response.status_code, 200)

        eq_(ga_service.id, int(response.response[0]))
        eq_(GoogleAnalyticsProvider.__module__, ga_service.protocol)
        eq_("http://test", ga_service.url)
        eq_([l2], ga_service.libraries)
        eq_("l2id", ConfigurationSetting.for_library_and_externalintegration(
                self._db, GoogleAnalyticsProvider.TRACKING_ID, l2, ga_service).value)
예제 #21
0
def create_lane_for_small_collection(_db, library, parent, languages, priority=0):
    """Create a lane (with sublanes) for a small collection based on language.

    :param parent: The parent of the new lane.
    """
    if isinstance(languages, basestring):
        languages = [languages]

    ADULT = Classifier.AUDIENCES_ADULT
    YA_CHILDREN = [Classifier.AUDIENCE_YOUNG_ADULT, Classifier.AUDIENCE_CHILDREN]

    common_args = dict(
        languages=languages,
        media=[Edition.BOOK_MEDIUM],
        genres=[],
    )
    language_identifier = LanguageCodes.name_for_languageset(languages)
    sublane_priority = 0

    adult_fiction, ignore = create(
        _db, Lane, library=library,
        display_name="Fiction",
        fiction=True,
        audiences=ADULT,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    adult_nonfiction, ignore = create(
        _db, Lane, library=library,
        display_name="Nonfiction",
        fiction=False,
        audiences=ADULT,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    ya_children, ignore = create(
        _db, Lane, library=library,
        display_name="Children & Young Adult",
        fiction=None,
        audiences=YA_CHILDREN,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    lane, ignore = create(
        _db, Lane, library=library,
        display_name=language_identifier,
        parent=parent,
        sublanes=[adult_fiction, adult_nonfiction, ya_children],
        priority=priority,
        **common_args
    )
    priority += 1
    return priority
예제 #22
0
def create_lane_for_small_collection(_db, library, parent, languages, priority=0):
    """Create a lane (with sublanes) for a small collection based on language.

    :param parent: The parent of the new lane.
    """
    if isinstance(languages, basestring):
        languages = [languages]

    ADULT = Classifier.AUDIENCES_ADULT
    YA_CHILDREN = [Classifier.AUDIENCE_YOUNG_ADULT, Classifier.AUDIENCE_CHILDREN]

    common_args = dict(
        languages=languages,
        media=[Edition.BOOK_MEDIUM],
        genres=[],
    )
    language_identifier = LanguageCodes.name_for_languageset(languages)
    sublane_priority = 0

    adult_fiction, ignore = create(
        _db, Lane, library=library,
        display_name="Fiction",
        fiction=True,
        audiences=ADULT,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    adult_nonfiction, ignore = create(
        _db, Lane, library=library,
        display_name="Nonfiction",
        fiction=False,
        audiences=ADULT,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    ya_children, ignore = create(
        _db, Lane, library=library,
        display_name="Children & Young Adult",
        fiction=None,
        audiences=YA_CHILDREN,
        priority=sublane_priority,
        **common_args
    )
    sublane_priority += 1

    lane, ignore = create(
        _db, Lane, library=library,
        display_name=language_identifier,
        parent=parent,
        sublanes=[adult_fiction, adult_nonfiction, ya_children],
        priority=priority,
        **common_args
    )
    priority += 1
    return priority
예제 #23
0
    def test_sign_in(self):
        password_auth = PasswordAdminAuthenticationProvider(None)

        # There are two admins with passwords.
        admin1, ignore = create(self._db, Admin, email="*****@*****.**")
        admin1.password = "******"
        admin2, ignore = create(self._db, Admin, email="*****@*****.**")
        admin2.password = "******"

        # This admin doesn't have a password.
        admin3, ignore = create(self._db, Admin, email="*****@*****.**")

        # Both admins with passwords can sign in.
        admin_details, redirect = password_auth.sign_in(
            self._db,
            dict(email="*****@*****.**", password="******", redirect="foo"))
        assert "*****@*****.**" == admin_details.get("email")
        assert PasswordAdminAuthenticationProvider.NAME == admin_details.get(
            "type")
        assert "foo" == redirect

        admin_details, redirect = password_auth.sign_in(
            self._db,
            dict(email="*****@*****.**", password="******", redirect="foo"))
        assert "*****@*****.**" == admin_details.get("email")
        assert PasswordAdminAuthenticationProvider.NAME == admin_details.get(
            "type")
        assert "foo" == redirect

        # An admin can't sign in with an incorrect password..
        admin_details, redirect = password_auth.sign_in(
            self._db,
            dict(email="*****@*****.**",
                 password="******",
                 redirect="foo"),
        )
        assert INVALID_ADMIN_CREDENTIALS == admin_details
        assert None == redirect

        # An admin can't sign in with a different admin's password.
        admin_details, redirect = password_auth.sign_in(
            self._db,
            dict(email="*****@*****.**", password="******", redirect="foo"))
        assert INVALID_ADMIN_CREDENTIALS == admin_details
        assert None == redirect

        # The admin with no password can't sign in.
        admin_details, redirect = password_auth.sign_in(
            self._db, dict(email="*****@*****.**", redirect="foo"))
        assert INVALID_ADMIN_CREDENTIALS == admin_details
        assert None == redirect

        # An admin email that's not in the db at all can't sign in.
        admin_details, redirect = password_auth.sign_in(
            self._db,
            dict(email="*****@*****.**", password="******", redirect="foo"))
        assert INVALID_ADMIN_CREDENTIALS == admin_details
        assert None == redirect
    def test_catalog_services_post_edit(self):
        ME = MARCExporter

        s3, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.S3,
            goal=ExternalIntegration.STORAGE_GOAL,
        )
        s3.setting(
            S3UploaderConfiguration.MARC_BUCKET_KEY).value = "marc-files"

        service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ME.NAME,
            goal=ExternalIntegration.CATALOG_GOAL,
            name="name",
        )

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "exporter name"),
                ("id", service.id),
                ("protocol", ME.NAME),
                ("mirror_integration_id", s3.id),
                (
                    "libraries",
                    json.dumps([{
                        "short_name": self._default_library.short_name,
                        ME.INCLUDE_SUMMARY: "false",
                        ME.INCLUDE_SIMPLIFIED_GENRES: "true",
                    }]),
                ),
            ])
            response = (self.manager.admin_catalog_services_controller.
                        process_catalog_services())
            assert response.status_code == 200

        integration_link = get_one(
            self._db,
            ExternalIntegrationLink,
            external_integration_id=service.id,
            purpose=ExternalIntegrationLink.MARC,
        )
        assert service.id == int(response.get_data())
        assert ME.NAME == service.protocol
        assert "exporter name" == service.name
        assert s3.id == integration_link.other_integration_id
        assert [self._default_library] == service.libraries
        assert ("false" ==
                ConfigurationSetting.for_library_and_externalintegration(
                    self._db, ME.INCLUDE_SUMMARY, self._default_library,
                    service).value)
        assert (
            "true" == ConfigurationSetting.for_library_and_externalintegration(
                self._db, ME.INCLUDE_SIMPLIFIED_GENRES, self._default_library,
                service).value)
예제 #25
0
    def test_collection_library_registrations_get(self):
        collection = self._default_collection
        succeeded, ignore = create(
            self._db,
            Library,
            name="Library 1",
            short_name="L1",
        )
        ConfigurationSetting.for_library_and_externalintegration(
            self._db,
            "library-registration-status",
            succeeded,
            collection.external_integration,
        ).value = "success"
        failed, ignore = create(
            self._db,
            Library,
            name="Library 2",
            short_name="L2",
        )
        ConfigurationSetting.for_library_and_externalintegration(
            self._db,
            "library-registration-status",
            failed,
            collection.external_integration,
        ).value = "failure"
        unregistered, ignore = create(
            self._db,
            Library,
            name="Library 3",
            short_name="L3",
        )
        collection.libraries = [succeeded, failed, unregistered]

        with self.request_context_with_admin("/", method="GET"):
            response = (
                self.manager.admin_collection_library_registrations_controller.
                process_collection_library_registrations())

            serviceInfo = response.get("library_registrations")
            assert 1 == len(serviceInfo)
            assert collection.id == serviceInfo[0].get("id")

            libraryInfo = serviceInfo[0].get("libraries")
            expected = [
                dict(short_name=succeeded.short_name, status="success"),
                dict(short_name=failed.short_name, status="failure"),
            ]
            assert expected == libraryInfo

            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            self._db.flush()
            pytest.raises(
                AdminNotAuthorized,
                self.manager.admin_collection_library_registrations_controller.
                process_collection_library_registrations,
            )
    def test_analytics_services_get_with_one_service(self):
        # Delete the local analytics service that gets created by default.
        local_analytics_default = get_one(
            self._db, ExternalIntegration,
            protocol=LocalAnalyticsProvider.__module__
        )

        self._db.delete(local_analytics_default)

        ga_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=GoogleAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )
        ga_service.url = self._str

        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [service] = response.get("analytics_services")

            eq_(ga_service.id, service.get("id"))
            eq_(ga_service.protocol, service.get("protocol"))
            eq_(ga_service.url, service.get("settings").get(ExternalIntegration.URL))

        ga_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, GoogleAnalyticsProvider.TRACKING_ID, self._default_library, ga_service
        ).value = "trackingid"
        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [service] = response.get("analytics_services")

            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
            eq_("trackingid", library.get(GoogleAnalyticsProvider.TRACKING_ID))

        self._db.delete(ga_service)

        local_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=LocalAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )

        local_service.libraries += [self._default_library]
        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [local_analytics] = response.get("analytics_services")

            eq_(local_service.id, local_analytics.get("id"))
            eq_(local_service.protocol, local_analytics.get("protocol"))
            eq_(local_analytics.get("protocol"), LocalAnalyticsProvider.__module__)
            [library] = local_analytics.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
예제 #27
0
    def test_collect_event(self):
        # This will be a site-wide integration because it will have no
        # associated libraries when the Analytics singleton is instantiated.
        # the first time.
        sitewide_integration, ignore = create(
            self._db,
            ExternalIntegration,
            goal=ExternalIntegration.ANALYTICS_GOAL,
            protocol=MOCK_PROTOCOL,
        )

        # This will be a per-library integration because it will have at least
        # one associated library when the Analytics singleton is instantiated.
        library_integration, ignore = create(
            self._db,
            ExternalIntegration,
            goal=ExternalIntegration.ANALYTICS_GOAL,
            protocol=MOCK_PROTOCOL,
        )
        library, ignore = create(self._db, Library, short_name="library")
        library_integration.libraries += [library]

        work = self._work(title="title", with_license_pool=True)
        [lp] = work.license_pools
        analytics = Analytics(self._db)
        sitewide_provider = analytics.sitewide_providers[0]
        library_provider = analytics.library_providers[library.id][0]

        analytics.collect_event(self._default_library, lp,
                                CirculationEvent.DISTRIBUTOR_CHECKIN, None)

        # The sitewide provider was called.
        assert 1 == sitewide_provider.count
        assert CirculationEvent.DISTRIBUTOR_CHECKIN == sitewide_provider.event_type

        # The library provider wasn't called, since the event was for a different library.
        assert 0 == library_provider.count

        analytics.collect_event(library, lp,
                                CirculationEvent.DISTRIBUTOR_CHECKIN, None)

        # Now both providers were called, since the event was for the library provider's library.
        assert 2 == sitewide_provider.count
        assert 1 == library_provider.count
        assert CirculationEvent.DISTRIBUTOR_CHECKIN == library_provider.event_type

        # Here's an event that we couldn't associate with any
        # particular library.
        analytics.collect_event(None, lp,
                                CirculationEvent.DISTRIBUTOR_CHECKOUT, None)

        # It's counted as a sitewide event, but not as a library event.
        assert 3 == sitewide_provider.count
        assert 1 == library_provider.count
    def test_catalog_services_post_edit(self):
        ME = MARCExporter

        s3, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.S3,
            goal=ExternalIntegration.STORAGE_GOAL,
        )
        s3.setting(S3Uploader.MARC_BUCKET_KEY).value = "marc-files"

        service, ignore = create(self._db,
                                 ExternalIntegration,
                                 protocol=ME.NAME,
                                 goal=ExternalIntegration.CATALOG_GOAL,
                                 name="name")

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "exporter name"),
                ("id", service.id),
                ("protocol", ME.NAME),
                (ME.STORAGE_PROTOCOL, ExternalIntegration.S3),
                ("libraries",
                 json.dumps([{
                     "short_name": self._default_library.short_name,
                     ME.INCLUDE_SUMMARY: "false",
                     ME.INCLUDE_SIMPLIFIED_GENRES: "true",
                 }])),
            ])
            response = self.manager.admin_catalog_services_controller.process_catalog_services(
            )
            eq_(response.status_code, 200)

        eq_(service.id, int(response.response[0]))
        eq_(ME.NAME, service.protocol)
        eq_("exporter name", service.name)
        eq_(ExternalIntegration.S3, service.setting(ME.STORAGE_PROTOCOL).value)
        eq_([self._default_library], service.libraries)
        eq_(
            "false",
            ConfigurationSetting.for_library_and_externalintegration(
                self._db, ME.INCLUDE_SUMMARY, self._default_library,
                service).value)
        eq_(
            "true",
            ConfigurationSetting.for_library_and_externalintegration(
                self._db, ME.INCLUDE_SIMPLIFIED_GENRES, self._default_library,
                service).value)
예제 #29
0
    def test_with_password(self):
        self._db.delete(self.admin)
        assert [] == Admin.with_password(self._db).all()

        admin, ignore = create(self._db, Admin, email="*****@*****.**")
        assert [] == Admin.with_password(self._db).all()

        admin.password = "******"
        assert [admin] == Admin.with_password(self._db).all()

        admin2, ignore = create(self._db, Admin, email="*****@*****.**")
        assert [admin] == Admin.with_password(self._db).all()

        admin2.password = "******"
        assert set([admin, admin2]) == set(Admin.with_password(self._db).all())
    def test_discovery_service_library_registrations_get(self):
        discovery_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.OPDS_REGISTRATION,
            goal=ExternalIntegration.DISCOVERY_GOAL,
        )
        succeeded, ignore = create(
            self._db, Library, name="Library 1", short_name="L1",
        )
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "library-registration-status", succeeded, discovery_service,
            ).value = "success"
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "library-registration-stage", succeeded, discovery_service,
            ).value = "production"
        failed, ignore = create(
            self._db, Library, name="Library 2", short_name="L2",
        )
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "library-registration-status", failed, discovery_service,
            ).value = "failure"
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "library-registration-stage", failed, discovery_service,
            ).value = "testing"
        unregistered, ignore = create(
            self._db, Library, name="Library 3", short_name="L3",
        )
        discovery_service.libraries = [succeeded, failed]

        controller = self.manager.admin_discovery_service_library_registrations_controller
        with self.request_context_with_admin("/", method="GET"):
            response = controller.process_discovery_service_library_registrations()

            serviceInfo = response.get("library_registrations")
            eq_(1, len(serviceInfo))
            eq_(discovery_service.id, serviceInfo[0].get("id"))

            libraryInfo = serviceInfo[0].get("libraries")
            expected = [
                dict(short_name=succeeded.short_name, status="success", stage="production"),
                dict(short_name=failed.short_name, status="failure", stage="testing"),
            ]
            eq_(expected, libraryInfo)

            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            self._db.flush()
            assert_raises(AdminNotAuthorized,
                         controller.process_discovery_service_library_registrations)
    def test_domains(self):
        super(TestGoogleOAuthAdminAuthenticationProvider, self).setup()
        auth_integration, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.GOOGLE_OAUTH,
            goal=ExternalIntegration.ADMIN_AUTH_GOAL
        )
        auth_integration.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "domains", self._default_library, auth_integration
        ).value = json.dumps(["nypl.org"])

        google = GoogleOAuthAdminAuthenticationProvider(auth_integration, "", test_mode=True)

        eq_(["nypl.org"], google.domains.keys())
        eq_([self._default_library], google.domains["nypl.org"])

        l2 = self._library()
        auth_integration.libraries += [l2]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "domains", l2, auth_integration
        ).value = json.dumps(["nypl.org", "l2.org"])

        eq_(set([self._default_library, l2]), set(google.domains["nypl.org"]))
        eq_([l2], google.domains["l2.org"])
    def test_annotation_page_for(self):
        patron = self._patron()

        with self.app.test_request_context("/"):
            page = AnnotationWriter.annotation_page_for(patron)

            # The patron doesn't have any annotations, so the page is empty.
            eq_(AnnotationWriter.JSONLD_CONTEXT, page['@context'])
            assert 'annotations' in page['id']
            eq_('AnnotationPage', page['type'])
            eq_(0, len(page['items']))

            # If we add an annotation, the page will have an item.
            identifier = self._identifier()
            annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=identifier,
                motivation=Annotation.IDLING,
            )

            page = AnnotationWriter.annotation_page_for(patron)

            eq_(1, len(page['items']))

            # But if the annotation is deleted, the page will be empty again.
            annotation.active = False

            page = AnnotationWriter.annotation_page_for(patron)

            eq_(0, len(page['items']))
    def test_metadata_services_post_create(self):
        controller = self.manager.admin_metadata_services_controller
        library, ignore = create(
            self._db, Library, name="Library", short_name="L",
        )
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "Name"),
                ("protocol", ExternalIntegration.NOVELIST),
                (ExternalIntegration.USERNAME, "user"),
                (ExternalIntegration.PASSWORD, "pass"),
                ("libraries", json.dumps([{"short_name": "L"}])),
            ])
            response = controller.process_post()
            eq_(response.status_code, 201)

        # A new ExternalIntegration has been created based on the submitted
        # information.
        service = get_one(
            self._db, ExternalIntegration,
            goal=ExternalIntegration.METADATA_GOAL
        )
        eq_(service.id, int(response.response[0]))
        eq_(ExternalIntegration.NOVELIST, service.protocol)
        eq_("user", service.username)
        eq_("pass", service.password)
        eq_([library], service.libraries)
    def test_catalog_services_post_create(self):
        ME = MARCExporter

        s3, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.S3,
            goal=ExternalIntegration.STORAGE_GOAL,
        )
        s3.setting(S3Uploader.MARC_BUCKET_KEY).value = "marc-files"

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "exporter name"),
                ("protocol", ME.NAME),
                (ME.STORAGE_PROTOCOL, ExternalIntegration.S3),
                ("libraries", json.dumps([{
                    "short_name": self._default_library.short_name,
                    ME.INCLUDE_SUMMARY: "false",
                    ME.INCLUDE_SIMPLIFIED_GENRES: "true",
                }])),
            ])
            response = self.manager.admin_catalog_services_controller.process_catalog_services()
            eq_(response.status_code, 201)

        service = get_one(self._db, ExternalIntegration, goal=ExternalIntegration.CATALOG_GOAL)
        eq_(service.id, int(response.response[0]))
        eq_(ME.NAME, service.protocol)
        eq_("exporter name", service.name)
        eq_(ExternalIntegration.S3, service.setting(ME.STORAGE_PROTOCOL).value)
        eq_([self._default_library], service.libraries)
        eq_("false", ConfigurationSetting.for_library_and_externalintegration(
                self._db, ME.INCLUDE_SUMMARY, self._default_library, service).value)
        eq_("true", ConfigurationSetting.for_library_and_externalintegration(
                self._db, ME.INCLUDE_SIMPLIFIED_GENRES, self._default_library, service).value)
    def test_patron_auth_services_get_with_firstbook_auth_service(self):
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=FirstBookAuthenticationAPI.__module__,
            goal=ExternalIntegration.PATRON_AUTH_GOAL
        )
        auth_service.url = "url"
        auth_service.password = "******"
        auth_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION,
            self._default_library, auth_service,
        ).value = "^(u)"

        with self.request_context_with_admin("/"):
            response = self.manager.admin_patron_auth_services_controller.process_patron_auth_services()
            [service] = response.get("patron_auth_services")

            eq_(auth_service.id, service.get("id"))
            eq_(FirstBookAuthenticationAPI.__module__, service.get("protocol"))
            eq_("url", service.get("settings").get(ExternalIntegration.URL))
            eq_("pass", service.get("settings").get(ExternalIntegration.PASSWORD))
            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
            eq_("^(u)", library.get(AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION))
    def test_patron_auth_services_get_with_sip2_auth_service(self):
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=SIP2AuthenticationProvider.__module__,
            goal=ExternalIntegration.PATRON_AUTH_GOAL
        )
        auth_service.url = "url"
        auth_service.setting(SIP2AuthenticationProvider.PORT).value = "1234"
        auth_service.username = "******"
        auth_service.password = "******"
        auth_service.setting(SIP2AuthenticationProvider.LOCATION_CODE).value = "5"
        auth_service.setting(SIP2AuthenticationProvider.FIELD_SEPARATOR).value = ","

        auth_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION,
            self._default_library, auth_service,
        ).value = "^(u)"

        with self.request_context_with_admin("/"):
            response = self.manager.admin_patron_auth_services_controller.process_patron_auth_services()
            [service] = response.get("patron_auth_services")

            eq_(auth_service.id, service.get("id"))
            eq_(SIP2AuthenticationProvider.__module__, service.get("protocol"))
            eq_("url", service.get("settings").get(ExternalIntegration.URL))
            eq_("1234", service.get("settings").get(SIP2AuthenticationProvider.PORT))
            eq_("user", service.get("settings").get(ExternalIntegration.USERNAME))
            eq_("pass", service.get("settings").get(ExternalIntegration.PASSWORD))
            eq_("5", service.get("settings").get(SIP2AuthenticationProvider.LOCATION_CODE))
            eq_(",", service.get("settings").get(SIP2AuthenticationProvider.FIELD_SEPARATOR))
            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
            eq_("^(u)", library.get(AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION))
    def test_admin_auth_services_post_google_oauth_edit(self):
        # The auth service exists.
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.GOOGLE_OAUTH,
            goal=ExternalIntegration.ADMIN_AUTH_GOAL
        )
        auth_service.url = "url"
        auth_service.username = "******"
        auth_service.password = "******"
        auth_service.libraries += [self._default_library]
        setting = ConfigurationSetting.for_library_and_externalintegration(
            self._db, "domains", self._default_library, auth_service)
        setting.value = json.dumps(["library1.org"])

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "oauth"),
                ("protocol", "Google OAuth"),
                ("url", "http://url2"),
                ("username", "user2"),
                ("password", "pass2"),
                ("libraries", json.dumps([{ "short_name": self._default_library.short_name,
                                            "domains": ["library2.org"] }])),
            ])
            response = self.manager.admin_auth_services_controller.process_admin_auth_services()
            eq_(response.status_code, 200)

        eq_(auth_service.protocol, response.response[0])
        eq_("oauth", auth_service.name)
        eq_("http://url2", auth_service.url)
        eq_("user2", auth_service.username)
        eq_("domains", setting.key)
        eq_(["library2.org"], json.loads(setting.value))
    def test_admin_auth_services_get_with_google_oauth_service(self):
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.GOOGLE_OAUTH,
            goal=ExternalIntegration.ADMIN_AUTH_GOAL
        )
        auth_service.url = "http://oauth.test"
        auth_service.username = "******"
        auth_service.password = "******"
        auth_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, "domains", self._default_library, auth_service
        ).value = json.dumps(["nypl.org"])

        with self.request_context_with_admin("/"):
            response = self.manager.admin_auth_services_controller.process_admin_auth_services()
            [service] = response.get("admin_auth_services")

            eq_(auth_service.id, service.get("id"))
            eq_(auth_service.name, service.get("name"))
            eq_(auth_service.protocol, service.get("protocol"))
            eq_(auth_service.url, service.get("settings").get("url"))
            eq_(auth_service.username, service.get("settings").get("username"))
            eq_(auth_service.password, service.get("settings").get("password"))
            [library_info] = service.get("libraries")
            eq_(self._default_library.short_name, library_info.get("short_name"))
            eq_(["nypl.org"], library_info.get("domains"))
    def test_detail_body(self):
        patron = self._patron()
        identifier = self._identifier()
        body = {
            "@type": "http://www.w3.org/ns/oa#TextualBody",
            "http://www.w3.org/ns/oa#bodyValue": "A good description of the topic that bears further investigation",
            "http://www.w3.org/ns/oa#hasPurpose": {
                "@id": "http://www.w3.org/ns/oa#describing"
            }
        }

        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
            content=json.dumps(body),
        )

        with self.app.test_request_context("/"):
            detail = AnnotationWriter.detail(annotation)

            assert "annotations/%i" % annotation.id in detail["id"]
            eq_("Annotation", detail['type'])
            eq_(Annotation.IDLING, detail['motivation'])
            compacted_body = {
                "type": "TextualBody",
                "bodyValue": "A good description of the topic that bears further investigation",
                "purpose": "describing"
            }
            eq_(compacted_body, detail["body"])
    def test_analytics_services_post_create(self):
        library, ignore = create(
            self._db, Library, name="Library", short_name="L",
        )
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "Google analytics name"),
                ("protocol", GoogleAnalyticsProvider.__module__),
                (ExternalIntegration.URL, "http://test"),
                ("libraries", json.dumps([{"short_name": "L", "tracking_id": "trackingid"}])),
            ])
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            eq_(response.status_code, 201)

        service = get_one(self._db, ExternalIntegration, goal=ExternalIntegration.ANALYTICS_GOAL)
        eq_(service.id, int(response.response[0]))
        eq_(GoogleAnalyticsProvider.__module__, service.protocol)
        eq_("http://test", service.url)
        eq_([library], service.libraries)
        eq_("trackingid", ConfigurationSetting.for_library_and_externalintegration(
                self._db, GoogleAnalyticsProvider.TRACKING_ID, library, service).value)

        # Creating a local analytics service doesn't require a URL.
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "local analytics name"),
                ("protocol", LocalAnalyticsProvider.__module__),
                ("libraries", json.dumps([{"short_name": "L", "tracking_id": "trackingid"}])),
            ])
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            eq_(response.status_code, 201)
    def test_detail_target(self):
        patron = self._patron()
        identifier = self._identifier()
        target = {
            "http://www.w3.org/ns/oa#hasSource": {
                "@id": identifier.urn
            },
            "http://www.w3.org/ns/oa#hasSelector": {
                "@type": "http://www.w3.org/ns/oa#FragmentSelector",
                "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
            }
        }

        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
            target=json.dumps(target),
        )

        with self.app.test_request_context("/"):
            detail = AnnotationWriter.detail(annotation)

            assert "annotations/%i" % annotation.id in detail["id"]
            eq_("Annotation", detail['type'])
            eq_(Annotation.IDLING, detail['motivation'])
            compacted_target = {
                "source": identifier.urn,
                "selector": {
                    "type": "FragmentSelector",
                    "value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
                }
            }
            eq_(compacted_target, detail["target"])
        def test_catalog_services_get_with_marc_exporter(self):
            integration, ignore = create(
                self._db, ExternalIntegration,
                protocol=ExternalIntegration.MARC_EXPORT,
                goal=ExternalIntegration.CATALOG_GOAL,
                name="name",
            )
            integration.setting(MARCExporter.STORAGE_PROTOCOL).value = ExternalIntegration.S3
            integration.libraries += [self._default_library]
            ConfigurationSetting.for_library_and_externalintegration(
                self._db, MARCExporter.MARC_ORGANIZATION_CODE,
                self._default_library, integration).value = "US-MaBoDPL"
            ConfigurationSetting.for_library_and_externalintegration(
                self._db, MARCExporter.INCLUDE_SUMMARY,
                self._default_library, integration).value = "false"
            ConfigurationSetting.for_library_and_externalintegration(
                self._db, MARCExporter.INCLUDE_SIMPLIFIED_GENRES,
                self._default_library, integration).value = "true"

            with self.request_context_with_admin("/"):
                response = self.manager.admin_catalog_services_controller.process_catalog_services()
                [service] = response.get("catalog_services")
                eq_(integration.id, service.get("id"))
                eq_(integration.name, service.get("name"))
                eq_(integration.protocol, service.get("protocol"))
                eq_(ExternalIntegration.S3, service.get("settings").get(MARCExporter.STORAGE_PROTOCOL))
                [library] = service.get("libraries")
                eq_(self._default_library.short_name, library.get("short_name"))
                eq_("US-MaBoDPL", library.get(MARCExporter.MARC_ORGANIZATION_CODE))
                eq_("false", library.get(MARCExporter.INCLUDE_SUMMARY))
                eq_("true", library.get(MARCExporter.INCLUDE_SIMPLIFIED_GENRES))
    def test_patron_auth_services_get_with_millenium_auth_service(self):
        auth_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=MilleniumPatronAPI.__module__,
            goal=ExternalIntegration.PATRON_AUTH_GOAL
        )
        auth_service.setting(BasicAuthenticationProvider.TEST_IDENTIFIER).value = "user"
        auth_service.setting(BasicAuthenticationProvider.TEST_PASSWORD).value = "pass"
        auth_service.setting(BasicAuthenticationProvider.IDENTIFIER_REGULAR_EXPRESSION).value = "u*"
        auth_service.setting(BasicAuthenticationProvider.PASSWORD_REGULAR_EXPRESSION).value = "p*"
        auth_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION,
            self._default_library, auth_service,
        ).value = "^(u)"

        with self.request_context_with_admin("/"):
            response = self.manager.admin_patron_auth_services_controller.process_patron_auth_services()
            [service] = response.get("patron_auth_services")

            eq_(auth_service.id, service.get("id"))
            eq_(MilleniumPatronAPI.__module__, service.get("protocol"))
            eq_("user", service.get("settings").get(BasicAuthenticationProvider.TEST_IDENTIFIER))
            eq_("pass", service.get("settings").get(BasicAuthenticationProvider.TEST_PASSWORD))
            eq_("u*", service.get("settings").get(BasicAuthenticationProvider.IDENTIFIER_REGULAR_EXPRESSION))
            eq_("p*", service.get("settings").get(BasicAuthenticationProvider.PASSWORD_REGULAR_EXPRESSION))
            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
            eq_("^(u)", library.get(AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION))
    def test_process_get_with_one_service(self):
        novelist_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.NOVELIST,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        novelist_service.username = "******"
        novelist_service.password = "******"

        controller = self.manager.admin_metadata_services_controller

        with self.request_context_with_admin("/"):
            response = controller.process_get()
            [service] = response.get("metadata_services")

            eq_(novelist_service.id, service.get("id"))
            eq_(ExternalIntegration.NOVELIST, service.get("protocol"))
            eq_("user", service.get("settings").get(ExternalIntegration.USERNAME))
            eq_("pass", service.get("settings").get(ExternalIntegration.PASSWORD))

        novelist_service.libraries += [self._default_library]
        with self.request_context_with_admin("/"):
            response = controller.process_get()
            [service] = response.get("metadata_services")

            eq_("user", service.get("settings").get(ExternalIntegration.USERNAME))
            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
    def test_individual_admins_post_edit(self):
        # An admin exists.
        admin, ignore = create(
            self._db, Admin, email="*****@*****.**",
        )
        admin.password = "******"
        admin.add_role(AdminRole.SYSTEM_ADMIN)

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("email", "*****@*****.**"),
                ("password", "new password"),
                ("roles", json.dumps([{"role": AdminRole.SITEWIDE_LIBRARIAN},
                                      {"role": AdminRole.LIBRARY_MANAGER, "library": self._default_library.short_name}])),
            ])
            response = self.manager.admin_individual_admin_settings_controller.process_post()
            eq_(response.status_code, 200)

        eq_(admin.email, response.response[0])

        # The password was changed.
        old_password_match = Admin.authenticate(self._db, "*****@*****.**", "password")
        eq_(None, old_password_match)

        new_password_match = Admin.authenticate(self._db, "*****@*****.**", "new password")
        eq_(admin, new_password_match)

        # The roles were changed.
        eq_(False, admin.is_system_admin())
        [librarian_all, manager] = sorted(admin.roles, key=lambda x: x.role)
        eq_(AdminRole.SITEWIDE_LIBRARIAN, librarian_all.role)
        eq_(None, librarian_all.library)
        eq_(AdminRole.LIBRARY_MANAGER, manager.role)
        eq_(self._default_library, manager.library)
    def test_search_services_post_edit(self):
        search_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=ExternalIntegration.ELASTICSEARCH,
            goal=ExternalIntegration.SEARCH_GOAL,
        )
        search_service.url = "search url"
        search_service.setting(ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY).value = "works-index-prefix"
        search_service.setting(ExternalSearchIndex.TEST_SEARCH_TERM_KEY).value = "sample-search-term"

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("name", "Name"),
                ("id", search_service.id),
                ("protocol", ExternalIntegration.ELASTICSEARCH),
                (ExternalIntegration.URL, "http://new_search_url"),
                (ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY, "new-works-index-prefix"),
                (ExternalSearchIndex.TEST_SEARCH_TERM_KEY, "new-sample-search-term")
            ])
            response = self.manager.admin_search_services_controller.process_services()
            eq_(response.status_code, 200)

        eq_(search_service.id, int(response.response[0]))
        eq_(ExternalIntegration.ELASTICSEARCH, search_service.protocol)
        eq_("http://new_search_url", search_service.url)
        eq_("new-works-index-prefix", search_service.setting(ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY).value)
        eq_("new-sample-search-term", search_service.setting(ExternalSearchIndex.TEST_SEARCH_TERM_KEY).value)
    def test_metadata_service_self_tests_post(self):
        old_run_self_tests = HasSelfTests.run_self_tests
        HasSelfTests.run_self_tests = self.mock_run_self_tests

        metadata_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.NYT,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        m = (self.manager.admin_metadata_service_self_tests_controller.
             self_tests_process_post)
        with self.request_context_with_admin("/", method="POST"):
            response = m(metadata_service.id)
            assert response._status == "200 OK"
            assert "Successfully ran new self tests" == response.get_data(
                as_text=True)

        positional, keyword = self.run_self_tests_called_with
        # run_self_tests was called with positional arguments:
        # * The database connection
        # * The method to call to instantiate a HasSelfTests implementation
        #   (NYTBestSellerAPI.from_config)
        # * The database connection again (to be passed into
        #   NYTBestSellerAPI.from_config).
        assert (self._db, NYTBestSellerAPI.from_config, self._db) == positional

        # run_self_tests was not called with any keyword arguments.
        assert {} == keyword

        # Undo the mock.
        HasSelfTests.run_self_tests = old_run_self_tests
    def test_fulfill(self):
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")
        loan, ignore = create(self._db, Loan, integration_client=other_client, license_pool=self.pool)
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan, self.delivery_mechanism)

        loan.integration_client = self.client

        # If the API does not return content or a content link, the loan can't be fulfilled.
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan, self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills)

        self.api.fulfillment = FulfillmentInfo(
            self.collection,
            self.pool.data_source.name,
            self.pool.identifier.type,
            self.pool.identifier.identifier,
            "http://content",
            "text/html",
            None,
            None,
        )
        fulfillment = self.shared_collection.fulfill(self.collection, self.client, loan, self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills[1:])
        eq_(self.delivery_mechanism, loan.fulfillment)
    def test_init(self):
        integration, ignore = create(
            self._db, ExternalIntegration,
            goal=ExternalIntegration.ANALYTICS_GOAL,
            protocol="api.google_analytics_provider",
        )

        assert_raises_regexp(
            CannotLoadConfiguration,
            "Google Analytics can't be configured without a library.",
            GoogleAnalyticsProvider, integration
        )

        assert_raises_regexp(
            CannotLoadConfiguration,
            "Missing tracking id for library %s" % self._default_library.short_name,
            GoogleAnalyticsProvider, integration, self._default_library
        )

        ConfigurationSetting.for_library_and_externalintegration(
            self._db, GoogleAnalyticsProvider.TRACKING_ID, self._default_library, integration
        ).value = "faketrackingid"
        ga = GoogleAnalyticsProvider(integration, self._default_library)
        eq_(GoogleAnalyticsProvider.DEFAULT_URL, ga.url)
        eq_("faketrackingid", ga.tracking_id)

        integration.url = self._str
        ga = GoogleAnalyticsProvider(integration, self._default_library)
        eq_(integration.url, ga.url)
        eq_("faketrackingid", ga.tracking_id)
    def test_metadata_service_self_tests_test_get(self):
        old_prior_test_results = HasSelfTests.prior_test_results
        HasSelfTests.prior_test_results = self.mock_prior_test_results
        metadata_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.NYT,
            goal=ExternalIntegration.METADATA_GOAL,
        )
        # Make sure that HasSelfTest.prior_test_results() was called and that
        # it is in the response's self tests object.
        with self.request_context_with_admin("/"):
            response = self.manager.admin_metadata_service_self_tests_controller.process_metadata_service_self_tests(
                metadata_service.id)
            response_metadata_service = response.get("self_test_results")

            assert response_metadata_service.get("id") == metadata_service.id
            assert response_metadata_service.get(
                "name") == metadata_service.name
            assert (response_metadata_service.get("protocol").get("label") ==
                    NYTBestSellerAPI.NAME)
            assert response_metadata_service.get(
                "goal") == metadata_service.goal
            assert (response_metadata_service.get("self_test_results") ==
                    HasSelfTests.prior_test_results())
        HasSelfTests.prior_test_results = old_prior_test_results
예제 #51
0
    def test_borrow(self):
        # This client is registered, but isn't one of the allowed URLs for the collection
        # (maybe it was registered for a different shared collection).
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")

        # Trying to borrow raises an exception.
        assert_raises(AuthorizationFailedException,
                      self.shared_collection.borrow, self.collection,
                      other_client, self.pool)

        # A client that's registered with the collection can borrow.
        self.shared_collection.borrow(self.collection, self.client, self.pool)
        eq_([(self.client, self.pool)], self.api.checkouts)

        # If the client's checking out an existing hold, the hold must be for that client.
        hold, ignore = create(self._db,
                              Hold,
                              integration_client=other_client,
                              license_pool=self.pool)
        assert_raises(CannotLoan,
                      self.shared_collection.borrow,
                      self.collection,
                      self.client,
                      self.pool,
                      hold=hold)

        hold.integration_client = self.client
        self.shared_collection.borrow(self.collection,
                                      self.client,
                                      self.pool,
                                      hold=hold)
        eq_([(self.client, self.pool)], self.api.checkouts[1:])
예제 #52
0
    def test_fulfill(self):
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")
        loan, ignore = create(self._db,
                              Loan,
                              integration_client=other_client,
                              license_pool=self.pool)
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan,
                      self.delivery_mechanism)

        loan.integration_client = self.client

        # If the API does not return content or a content link, the loan can't be fulfilled.
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan,
                      self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills)

        self.api.fulfillment = FulfillmentInfo(
            self.collection,
            self.pool.data_source.name,
            self.pool.identifier.type,
            self.pool.identifier.identifier,
            "http://content",
            "text/html",
            None,
            None,
        )
        fulfillment = self.shared_collection.fulfill(self.collection,
                                                     self.client, loan,
                                                     self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)],
            self.api.fulfills[1:])
        eq_(self.delivery_mechanism, loan.fulfillment)
예제 #53
0
파일: lanes.py 프로젝트: lhuabu/circulation
def create_lane_for_tiny_collection(_db, library, parent, languages, priority=0):
    """Create a single lane for a tiny collection based on language,
    if the language exists in the lookup table.

    :param parent: The parent of the new lane.
    """
    if not languages:
        return None

    if isinstance(languages, basestring):
        languages = [languages]
    
    try:
        name = LanguageCodes.name_for_languageset(languages)
    except ValueError as e:
        logging.getLogger().warn(
            "Could not create a lane for tiny collection with languages %s", languages
        )
        return 0

    language_lane, ignore = create(
        _db, Lane, library=library,
        display_name=name,
        parent=parent,
        genres=[],
        media=[Edition.BOOK_MEDIUM],
        fiction=None,
        priority=priority,
        languages=languages,
    )
    return priority + 1
예제 #54
0
    def test_search_services_get_with_one_service(self):
        search_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.ELASTICSEARCH,
            goal=ExternalIntegration.SEARCH_GOAL,
        )
        search_service.url = "search url"
        search_service.setting(ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY
                               ).value = "works-index-prefix"
        search_service.setting(ExternalSearchIndex.TEST_SEARCH_TERM_KEY
                               ).value = "search-term-for-self-tests"

        with self.request_context_with_admin("/"):
            response = self.manager.admin_search_services_controller.process_services(
            )
            [service] = response.get("search_services")

            eq_(search_service.id, service.get("id"))
            eq_(search_service.protocol, service.get("protocol"))
            eq_("search url",
                service.get("settings").get(ExternalIntegration.URL))
            eq_(
                "works-index-prefix",
                service.get("settings").get(
                    ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY))
            eq_(
                "search-term-for-self-tests",
                service.get("settings").get(
                    ExternalSearchIndex.TEST_SEARCH_TERM_KEY))
예제 #55
0
    def test_search_service_delete(self):
        search_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.ELASTICSEARCH,
            goal=ExternalIntegration.SEARCH_GOAL,
        )
        search_service.url = "search url"
        search_service.setting(ExternalSearchIndex.WORKS_INDEX_PREFIX_KEY
                               ).value = "works-index-prefix"

        with self.request_context_with_admin("/", method="DELETE"):
            self.admin.remove_role(AdminRole.SYSTEM_ADMIN)
            assert_raises(
                AdminNotAuthorized,
                self.manager.admin_search_services_controller.process_delete,
                search_service.id)

            self.admin.add_role(AdminRole.SYSTEM_ADMIN)
            response = self.manager.admin_search_services_controller.process_delete(
                search_service.id)
            eq_(response.status_code, 200)

        service = get_one(self._db, ExternalIntegration, id=search_service.id)
        eq_(None, service)
    def test_search_service_self_tests_post(self):
        old_run_self_tests = HasSelfTests.run_self_tests
        HasSelfTests.run_self_tests = self.mock_run_self_tests

        search_service, ignore = create(
            self._db,
            ExternalIntegration,
            protocol=ExternalIntegration.ELASTICSEARCH,
            goal=ExternalIntegration.SEARCH_GOAL)
        m = self.manager.admin_search_service_self_tests_controller.self_tests_process_post
        with self.request_context_with_admin("/", method="POST"):
            response = m(search_service.id)
            eq_(response._status, "200 OK")
            eq_("Successfully ran new self tests", response.data)

        positional, keyword = self.run_self_tests_called_with
        # run_self_tests was called with positional arguments:
        # * The database connection
        # * The method to call to instantiate a HasSelfTests implementation
        #   (None -- this means to use the default ExternalSearchIndex
        #   constructor.)
        # * The database connection again (to be passed into
        #   the ExternalSearchIndex constructor).
        eq_((self._db, None, self._db), positional)

        # run_self_tests was not called with any keyword arguments.
        eq_({}, keyword)

        # Undo the mock.
        HasSelfTests.run_self_tests = old_run_self_tests
    def test_patron_auth_services_post_create(self):
        mock_controller = self._get_mock()

        library, ignore = create(
            self._db, Library, name="Library", short_name="L",
        )

        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("protocol", SimpleAuthenticationProvider.__module__),
                ("libraries", json.dumps([{
                    "short_name": library.short_name,
                    AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION: "^(.)",
                    AuthenticationProvider.LIBRARY_IDENTIFIER_RESTRICTION_TYPE: AuthenticationProvider.LIBRARY_IDENTIFIER_RESTRICTION_TYPE_REGEX,
                    AuthenticationProvider.LIBRARY_IDENTIFIER_FIELD: AuthenticationProvider.LIBRARY_IDENTIFIER_RESTRICTION_BARCODE,
                    AuthenticationProvider.LIBRARY_IDENTIFIER_RESTRICTION: "^1234",
                }])),
            ] + self._common_basic_auth_arguments())

            response = mock_controller.process_patron_auth_services()
            eq_(response.status_code, 201)
            eq_(mock_controller.validate_formats_call_count, 1)

        auth_service = get_one(self._db, ExternalIntegration, goal=ExternalIntegration.PATRON_AUTH_GOAL)
        eq_(auth_service.id, int(response.response[0]))
        eq_(SimpleAuthenticationProvider.__module__, auth_service.protocol)
        eq_("user", auth_service.setting(BasicAuthenticationProvider.TEST_IDENTIFIER).value)
        eq_("pass", auth_service.setting(BasicAuthenticationProvider.TEST_PASSWORD).value)
        eq_([library], auth_service.libraries)
        eq_("^(.)", ConfigurationSetting.for_library_and_externalintegration(
                self._db, AuthenticationProvider.EXTERNAL_TYPE_REGULAR_EXPRESSION,
                library, auth_service).value)
        common_args = self._common_basic_auth_arguments()
        with self.request_context_with_admin("/", method="POST"):
            flask.request.form = MultiDict([
                ("protocol", MilleniumPatronAPI.__module__),
                (ExternalIntegration.URL, "url"),
                (MilleniumPatronAPI.VERIFY_CERTIFICATE, "true"),
                (MilleniumPatronAPI.AUTHENTICATION_MODE, MilleniumPatronAPI.PIN_AUTHENTICATION_MODE),
            ] + common_args)
            response = mock_controller.process_patron_auth_services()
            eq_(response.status_code, 201)
            eq_(mock_controller.validate_formats_call_count, 2)

        auth_service2 = get_one(self._db, ExternalIntegration,
                               goal=ExternalIntegration.PATRON_AUTH_GOAL,
                               protocol=MilleniumPatronAPI.__module__)
        assert auth_service2 != auth_service
        eq_(auth_service2.id, int(response.response[0]))
        eq_("url", auth_service2.url)
        eq_("user", auth_service2.setting(BasicAuthenticationProvider.TEST_IDENTIFIER).value)
        eq_("pass", auth_service2.setting(BasicAuthenticationProvider.TEST_PASSWORD).value)
        eq_("true",
            auth_service2.setting(MilleniumPatronAPI.VERIFY_CERTIFICATE).value)
        eq_(MilleniumPatronAPI.PIN_AUTHENTICATION_MODE,
            auth_service2.setting(MilleniumPatronAPI.AUTHENTICATION_MODE).value)
        eq_(None, auth_service2.setting(MilleniumPatronAPI.BLOCK_TYPES).value)
        eq_([], auth_service2.libraries)
    def test_analytics_services_get_with_one_service(self):
        ga_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=GoogleAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )
        ga_service.url = self._str

        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [service] = response.get("analytics_services")

            eq_(ga_service.id, service.get("id"))
            eq_(ga_service.protocol, service.get("protocol"))
            eq_(ga_service.url, service.get("settings").get(ExternalIntegration.URL))

        ga_service.libraries += [self._default_library]
        ConfigurationSetting.for_library_and_externalintegration(
            self._db, GoogleAnalyticsProvider.TRACKING_ID, self._default_library, ga_service
        ).value = "trackingid"
        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [service] = response.get("analytics_services")

            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
            eq_("trackingid", library.get(GoogleAnalyticsProvider.TRACKING_ID))

        self._db.delete(ga_service)

        local_service, ignore = create(
            self._db, ExternalIntegration,
            protocol=LocalAnalyticsProvider.__module__,
            goal=ExternalIntegration.ANALYTICS_GOAL,
        )

        local_service.libraries += [self._default_library]
        with self.request_context_with_admin("/"):
            response = self.manager.admin_analytics_services_controller.process_analytics_services()
            [service] = response.get("analytics_services")

            eq_(local_service.id, service.get("id"))
            eq_(local_service.protocol, service.get("protocol"))
            [library] = service.get("libraries")
            eq_(self._default_library.short_name, library.get("short_name"))
    def test_revoke_loan(self):
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")
        loan, ignore = create(self._db, Loan, integration_client=other_client, license_pool=self.pool)
        assert_raises(NotCheckedOut, self.shared_collection.revoke_loan,
                      self.collection, self.client, loan)

        loan.integration_client = self.client
        self.shared_collection.revoke_loan(self.collection, self.client, loan)
        eq_([(self.client, loan)], self.api.returns)