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_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)
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))
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)
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_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))
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, "*****@*****.**"))
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) 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)
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
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
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)
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"))
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)
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
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:])
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 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
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))
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)