def test_sync_bookshelf_creates_local_loans(self): loans_data, json_loans = self.sample_json( "shelf_with_some_checked_out_books.json") holds_data, json_holds = self.sample_json("no_holds.json") overdrive = MockOverdriveAPI(self._db) overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) patron = self._patron() circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") # All four loans in the sample data were created. eq_(4, len(loans)) eq_(loans, patron.loans) eq_([], holds) # Running the sync again leaves all four loans in place. overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(loans)) eq_(loans, patron.loans)
def test_sync_bookshelf(self): patron = self._patron() circulation = CirculationAPI( self._db, self._default_library, api_map={self.collection.protocol: MockBibliothecaAPI}) api = circulation.api_for_collection[self.collection.id] api.queue_response(200, content=self.sample_data("checkouts.xml")) circulation.sync_bookshelf(patron, "dummy pin") # The patron should have two loans and two holds. l1, l2 = patron.loans h1, h2 = patron.holds eq_(datetime.datetime(2015, 3, 20, 18, 50, 22), l1.start) eq_(datetime.datetime(2015, 4, 10, 18, 50, 22), l1.end) eq_(datetime.datetime(2015, 3, 13, 13, 38, 19), l2.start) eq_(datetime.datetime(2015, 4, 3, 13, 38, 19), l2.end) # This hold has no end date because there's no decision to be # made. The patron is fourth in line. eq_(datetime.datetime(2015, 3, 24, 15, 6, 56), h1.start) eq_(None, h1.end) eq_(4, h1.position) # The hold has an end date. It's time for the patron to decide # whether or not to check out this book. eq_(datetime.datetime(2015, 5, 25, 17, 5, 34), h2.start) eq_(datetime.datetime(2015, 5, 27, 17, 5, 34), h2.end) eq_(0, h2.position)
def basic_test(self): patron = self._patron() api = DummyThreeMAPI(self._db) api.queue_response(content=self.sample_data("checkouts.xml")) circulation = CirculationAPI(self._db, threem=api) circulation.sync_bookshelf(patron, "dummy pin") # The patron should have two loans and two holds. l1, l2 = patron.loans h1, h2 = patron.holds eq_(datetime.datetime(2015, 3, 20, 18, 50, 22), l1.start) eq_(datetime.datetime(2015, 4, 10, 18, 50, 22), l1.end) eq_(datetime.datetime(2015, 3, 13, 13, 38, 19), l2.start) eq_(datetime.datetime(2015, 4, 3, 13, 38, 19), l2.end) # This hold has no end date because there's no decision to be # made. The patron is fourth in line. eq_(datetime.datetime(2015, 3, 24, 15, 6, 56), h1.start) eq_(None, h1.end) eq_(4, h1.position) # The hold has an end date. It's time for the patron to decide # whether or not to check out this book. eq_(datetime.datetime(2015, 5, 25, 17, 5, 34), h2.start) eq_(datetime.datetime(2015, 5, 27, 17, 5, 34), h2.end) eq_(0, h2.position)
def test_sync_bookshelf_removes_loans_not_present_on_remote(self): loans_data, json_loans = self.sample_json( "shelf_with_some_checked_out_books.json") holds_data, json_holds = self.sample_json("no_holds.json") overdrive = MockOverdriveAPI(self._db) overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) # Create a loan not present in the sample data. patron = self._patron() overdrive_edition, new = self._edition( data_source_name=DataSource.OVERDRIVE, with_license_pool=True) overdrive_loan, new = overdrive_edition.license_pool.loan_to(patron) yesterday = datetime.utcnow() - timedelta(days=1) overdrive_loan.start = yesterday # Sync with Overdrive, and the loan not present in the sample # data is removed. circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(loans)) eq_(loans, patron.loans) assert overdrive_loan not in patron.loans
def test_do_run(self): library1 = self._default_library library2 = self._library(name="library2") out = StringIO() class MockParsed(object): pass class MockScript(RunSelfTestsScript): tested = [] def parse_command_line(self, *args, **kwargs): parsed = MockParsed() parsed.libraries = [library1, library2] return parsed def test_collection(self, collection, api_map): self.tested.append((collection, api_map)) script = MockScript(self._db, out) script.do_run() # Both libraries were tested. assert out.getvalue() == "Testing %s\nTesting %s\n" % ( library1.name, library2.name, ) # The default library is the only one with a collection; # test_collection() was called on that collection. [(collection, api_map)] = script.tested assert [collection] == library1.collections # The API lookup map passed into test_collection() is based on # CirculationAPI's default API map. default_api_map = CirculationAPI(self._db, self._default_library).default_api_map for k, v in list(default_api_map.items()): assert api_map[k] == v # But a couple things were added to the map that are not in # CirculationAPI. assert api_map[ExternalIntegration.OPDS_IMPORT] == OPDSImportMonitor assert api_map[ExternalIntegration.FEEDBOOKS] == FeedbooksImportMonitor # If test_collection raises an exception, the exception is recorded, # and we move on. class MockScript2(MockScript): def test_collection(self, collection, api_map): raise Exception("blah") out = StringIO() script = MockScript2(self._db, out) script.do_run() assert ( out.getvalue() == "Testing %s\n Exception while running self-test: 'blah'\nTesting %s\n" % (library1.name, library2.name))
def test_do_run(self): library1 = self._default_library library2 = self._library(name="library2") out = StringIO() class MockParsed(object): pass class MockScript(RunSelfTestsScript): tested = [] def parse_command_line(self, *args, **kwargs): parsed = MockParsed() parsed.libraries = [library1, library2] return parsed def test_collection(self, collection, api_map): self.tested.append((collection, api_map)) script = MockScript(self._db, out) script.do_run() # Both libraries were tested. eq_(out.getvalue(), "Testing %s\nTesting %s\n" % (library1.name, library2.name)) # The default library is the only one with a collection; # test_collection() was called on that collection. [(collection, api_map)] = script.tested eq_([collection], library1.collections) # The API lookup map passed into test_collection() is based on # CirculationAPI's default API map. default_api_map = CirculationAPI( self._db, self._default_library ).default_api_map for k, v in default_api_map.items(): eq_(api_map[k], v) # But a couple things were added to the map that are not in # CirculationAPI. eq_(api_map[ExternalIntegration.OPDS_IMPORT], OPDSImportMonitor) eq_(api_map[ExternalIntegration.FEEDBOOKS], FeedbooksImportMonitor) # If test_collection raises an exception, the exception is recorded, # and we move on. class MockScript2(MockScript): def test_collection(self, collection, api_map): raise Exception("blah") out = StringIO() script = MockScript2(self._db, out) script.do_run() eq_(out.getvalue(), "Testing %s\n Exception while running self-test: Exception('blah',)\nTesting %s\n" % (library1.name, library2.name) )
def setup(self): super(OverdriveAPITest, self).setup() library = self._default_library self.collection = MockOverdriveAPI.mock_collection(self._db) self.circulation = CirculationAPI( self._db, library, api_map={ExternalIntegration.OVERDRIVE:MockOverdriveAPI} ) self.api = self.circulation.api_for_collection[self.collection.id]
def test_can_fulfill_without_loan(self): """Can a title can be fulfilled without an active loan? It depends on the BaseCirculationAPI implementation for that title's colelction. """ class Mock(BaseCirculationAPI): def can_fulfill_without_loan(self, patron, pool, lpdm): return "yep" pool = self._licensepool(None) circulation = CirculationAPI(self._db, self._default_library) circulation.api_for_collection[pool.collection.id] = Mock() eq_( "yep", circulation.can_fulfill_without_loan(None, pool, object()) ) # If format data is missing or the BaseCirculationAPI cannot # be found, we assume the title cannot be fulfilled. eq_(False, circulation.can_fulfill_without_loan(None, pool, None)) eq_(False, circulation.can_fulfill_without_loan(None, None, object())) circulation.api_for_collection = {} eq_(False, circulation.can_fulfill_without_loan(None, pool, None)) # An open access pool can be fulfilled even without the BaseCirculationAPI. pool.open_access = True eq_(True, circulation.can_fulfill_without_loan(None, pool, object()))
def test_sync_bookshelf_ignores_holds_from_other_sources(self): loans_data, json_loans = self.sample_json("no_loans.json") holds_data, json_holds = self.sample_json("holds.json") patron = self._patron() threem, new = self._edition(data_source_name=DataSource.THREEM, with_license_pool=True) threem_hold, new = threem.license_pool.on_hold_to(patron) overdrive = MockOverdriveAPI(self._db) overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) # Overdrive doesn't know about the 3M hold, but it was # not destroyed, because it came from another source. circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(5, len(patron.holds)) assert threem_hold in patron.holds
def test_sync_bookshelf_ignores_holds_from_other_sources(self): loans_data, json_loans = self.sample_json("no_loans.json") holds_data, json_holds = self.sample_json("holds.json") patron = self.default_patron threem, new = self._edition(data_source_name=DataSource.THREEM, with_license_pool=True) threem_hold, new = threem.license_pool.on_hold_to(patron) overdrive = DummyOverdriveAPI(self._db) overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) # Overdrive doesn't know about the 3M hold, but it was # not destroyed, because it came from another source. circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(5, len(patron.holds)) assert threem_hold in patron.holds
def test_sync_bookshelf_removes_holds_not_present_on_remote(self): loans_data, json_loans = self.sample_json("no_loans.json") holds_data, json_holds = self.sample_json("holds.json") patron = self._patron() overdrive_edition, new = self._edition( data_source_name=DataSource.OVERDRIVE, with_license_pool=True) overdrive_hold, new = overdrive_edition.license_pool.on_hold_to(patron) overdrive = MockOverdriveAPI(self._db) overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) # The hold not present in the sample data has been removed circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(holds)) eq_(holds, patron.holds) assert overdrive_hold not in patron.loans
def test_sync_bookshelf_ignores_loans_from_other_sources(self): patron = self.default_patron gutenberg, new = self._edition(data_source_name=DataSource.GUTENBERG, with_license_pool=True) gutenberg_loan, new = gutenberg.license_pool.loan_to(patron) loans_data, json_loans = self.sample_json("shelf_with_some_checked_out_books.json") holds_data, json_holds = self.sample_json("no_holds.json") # Overdrive doesn't know about the Gutenberg loan, but it was # not destroyed, because it came from another source. overdrive = DummyOverdriveAPI(self._db) overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) circulation = CirculationAPI(self._db, overdrive=overdrive) patron = self.default_patron loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(5, len(patron.loans)) assert gutenberg_loan in patron.loans
def test_sync_bookshelf_ignores_loans_from_other_sources(self): patron = self._patron() gutenberg, new = self._edition(data_source_name=DataSource.GUTENBERG, with_license_pool=True) gutenberg_loan, new = gutenberg.license_pool.loan_to(patron) loans_data, json_loans = self.sample_json( "shelf_with_some_checked_out_books.json") holds_data, json_holds = self.sample_json("no_holds.json") # Overdrive doesn't know about the Gutenberg loan, but it was # not destroyed, because it came from another source. overdrive = MockOverdriveAPI(self._db) overdrive.queue_response(200, content=loans_data) overdrive.queue_response(200, content=holds_data) circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(5, len(patron.loans)) assert gutenberg_loan in patron.loans
def test_test_patron(self): """Verify that test_patron() returns credentials determined by the basic auth provider. """ auth = self.mock_auth provider = auth.basic_auth_provider api = CirculationAPI(self._db, self._default_library) status = ServiceStatus(api, auth=auth) patron, password = status.test_patron eq_(provider.test_username, patron.authorization_identifier) eq_(provider.test_password, password)
def test_sync_bookshelf_removes_holds_not_present_on_remote(self): loans_data, json_loans = self.sample_json("no_loans.json") holds_data, json_holds = self.sample_json("holds.json") patron = self.default_patron overdrive_edition, new = self._edition(data_source_name=DataSource.OVERDRIVE, with_license_pool=True) overdrive_hold, new = overdrive_edition.license_pool.on_hold_to(patron) overdrive = DummyOverdriveAPI(self._db) overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) # The hold not present in the sample data has been removed circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(holds)) eq_(holds, patron.holds) assert overdrive_hold not in patron.loans
def test_patron_activity(self): # Get a CirculationAPI that doesn't mock out its API's patron activity. circulation = CirculationAPI( self._db, self._default_library, api_map={ ExternalIntegration.BIBLIOTHECA : MockBibliothecaAPI }) mock_bibliotheca = circulation.api_for_collection[self.collection.id] data = sample_data("checkouts.xml", "bibliotheca") mock_bibliotheca.queue_response(200, content=data) loans, holds, complete = circulation.patron_activity(self.patron, "1234") eq_(2, len(loans)) eq_(2, len(holds)) eq_(True, complete) mock_bibliotheca.queue_response(500, content="Error") loans, holds, complete = circulation.patron_activity(self.patron, "1234") eq_(0, len(loans)) eq_(0, len(holds)) eq_(False, complete)
def test_patron_activity(self): threem = MockThreeMAPI(self._db) circulation = CirculationAPI(self._db, threem=threem) data = sample_data("checkouts.xml", "threem") threem.queue_response(200, content=data) loans, holds, complete = circulation.patron_activity( self.patron, "1234") eq_(2, len(loans)) eq_(2, len(holds)) eq_(True, complete) threem.queue_response(500, content="Error") loans, holds, complete = circulation.patron_activity( self.patron, "1234") eq_(0, len(loans)) eq_(0, len(holds)) eq_(False, complete)
def test_sync_bookshelf_creates_local_holds(self): loans_data, json_loans = self.sample_json("no_loans.json") holds_data, json_holds = self.sample_json("holds.json") overdrive = DummyOverdriveAPI(self._db) overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) circulation = CirculationAPI(self._db, overdrive=overdrive) patron = self.default_patron loans, holds = circulation.sync_bookshelf(patron, "dummy pin") # All four loans in the sample data were created. eq_(4, len(holds)) eq_(holds, patron.holds) # Running the sync again leaves all four holds in place. overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(holds)) eq_(holds, patron.holds)
def test_init(self): # Test that ServiceStatus can create an Authenticator. integration = self._external_integration( "api.simple_authentication", goal=ExternalIntegration.PATRON_AUTH_GOAL) provider = SimpleAuthenticationProvider integration.setting(provider.TEST_IDENTIFIER).value = "validpatron" integration.setting(provider.TEST_PASSWORD).value = "password" self._default_library.integrations.append(integration) api = CirculationAPI(self._db, self._default_library) service_status = ServiceStatus(api) assert service_status.auth != None assert isinstance(service_status.auth.basic_auth_provider, provider) eq_(self._default_library, service_status.auth.library)
def test_patron_activity(self): # Get a CirculationAPI that doesn't mock out its API's patron activity. circulation = CirculationAPI( self._db, self._default_library, api_map={ExternalIntegration.BIBLIOTHECA: MockBibliothecaAPI}) mock_bibliotheca = circulation.api_for_collection[self.collection.id] data = sample_data("checkouts.xml", "bibliotheca") mock_bibliotheca.queue_response(200, content=data) loans, holds, complete = circulation.patron_activity( self.patron, "1234") eq_(2, len(loans)) eq_(2, len(holds)) eq_(True, complete) mock_bibliotheca.queue_response(500, content="Error") loans, holds, complete = circulation.patron_activity( self.patron, "1234") eq_(0, len(loans)) eq_(0, len(holds)) eq_(False, complete)
def test_sync_bookshelf_removes_loans_not_present_on_remote(self): loans_data, json_loans = self.sample_json("shelf_with_some_checked_out_books.json") holds_data, json_holds = self.sample_json("no_holds.json") overdrive = DummyOverdriveAPI(self._db) overdrive.queue_response(content=holds_data) overdrive.queue_response(content=loans_data) # Create a loan not present in the sample data. patron = self.default_patron overdrive_edition, new = self._edition( data_source_name=DataSource.OVERDRIVE, with_license_pool=True ) overdrive_loan, new = overdrive_edition.license_pool.loan_to(patron) # Sync with Overdrive, and the loan not present in the sample # data is removed. circulation = CirculationAPI(self._db, overdrive=overdrive) loans, holds = circulation.sync_bookshelf(patron, "dummy pin") eq_(4, len(loans)) eq_(loans, patron.loans) assert overdrive_loan not in patron.loans
def setup(self): super(OdiloAPITest, self).setup() library = self._default_library self.patron = self._patron() self.patron.authorization_identifier='0001000265' self.collection = MockOdiloAPI.mock_collection(self._db) self.circulation = CirculationAPI( self._db, library, api_map={ExternalIntegration.ODILO: MockOdiloAPI} ) self.api = self.circulation.api_for_collection[self.collection.id] self.edition, self.licensepool = self._edition( data_source_name=DataSource.ODILO, identifier_type=Identifier.ODILO_ID, collection=self.collection, identifier_id=self.RECORD_ID, with_license_pool=True )
def test_configuration_exception_is_stored(self): """If the initialization of an API object raises CannotLoadConfiguration, the exception is stored with the CirculationAPI rather than being propagated. """ api_map = {self._default_collection.protocol: self.MisconfiguredAPI} circulation = CirculationAPI(self._db, self._default_library, api_map=api_map) # Although the CirculationAPI was created, it has no functioning # APIs. eq_({}, circulation.api_for_collection) # Instead, the CannotLoadConfiguration exception raised by the # constructor has been stored in initialization_exceptions. e = circulation.initialization_exceptions[self._default_collection.id] assert isinstance(e, CannotLoadConfiguration) eq_("doomed!", e.message)
def test_can_fulfill_without_loan(self): """Can a title can be fulfilled without an active loan? It depends on the BaseCirculationAPI implementation for that title's colelction. """ class Mock(BaseCirculationAPI): def can_fulfill_without_loan(self, patron, pool, lpdm): return "yep" pool = self._licensepool(None) circulation = CirculationAPI(self._db, self._default_library) circulation.api_for_collection[pool.collection.id] = Mock() eq_("yep", circulation.can_fulfill_without_loan(None, pool, object())) # If format data is missing or the BaseCirculationAPI cannot # be found, we assume the title cannot be fulfilled. eq_(False, circulation.can_fulfill_without_loan(None, pool, None)) eq_(False, circulation.can_fulfill_without_loan(None, None, object())) circulation.api_for_collection = {} eq_(False, circulation.can_fulfill_without_loan(None, pool, None)) # An open access pool can be fulfilled even without the BaseCirculationAPI. pool.open_access = True eq_(True, circulation.can_fulfill_without_loan(None, pool, object()))
def test_checkout_status(self): # Create a Collection to test. overdrive_collection = self._collection( protocol=ExternalIntegration.OVERDRIVE) edition, lp = self._edition(with_license_pool=True, collection=overdrive_collection) library = self._default_library library.collections.append(overdrive_collection) # Test a scenario where we get information for every # relevant collection in the library. class CheckoutSuccess(object): def __init__(self, *args, **kwargs): self.borrowed = False self.fulfilled = False self.revoked = False def borrow(self, patron, password, license_pool, *args, **kwargs): "Simulate a successful borrow." self.borrowed = True return object(), None, True def fulfill(self, *args, **kwargs): "Simulate a successful fulfillment." self.fulfilled = True def revoke_loan(self, *args, **kwargs): "Simulate a successful loan revocation." self.revoked = True everything_succeeds = {ExternalIntegration.OVERDRIVE: CheckoutSuccess} auth = self.mock_auth api = CirculationAPI(self._db, library, api_map=everything_succeeds) status = ServiceStatus(api, auth=auth) ConfigurationSetting.for_library( Configuration.DEFAULT_NOTIFICATION_EMAIL_ADDRESS, library).value = "a@b" response = status.checkout_status(lp.identifier) # The ServiceStatus object was able to run its test. for value in response.values(): assert value.startswith('SUCCESS') # The mock Overdrive API had all its methods called. api = status.circulation.api_for_collection[overdrive_collection.id] eq_(True, api.borrowed) eq_(True, api.fulfilled) eq_(True, api.revoked) # Now try some failure conditions. # First: the 'borrow' operation succeeds on an API level but # it doesn't create a loan. class NoLoanCreated(CheckoutSuccess): def borrow(self, patron, password, license_pool, *args, **kwargs): "Oops! We put the book on hold instead of borrowing it." return None, object(), True no_loan_created = {ExternalIntegration.OVERDRIVE: NoLoanCreated} api = CirculationAPI(self._db, library, api_map=no_loan_created) status = ServiceStatus(api, auth=auth) response = status.checkout_status(lp.identifier) assert 'FAILURE: No loan created during checkout' in response.values() # Next: The 'revoke' operation fails on an API level. class RevokeFail(CheckoutSuccess): def revoke_loan(self, *args, **kwargs): "Simulate an error during loan revocation." raise Exception("Doomed to fail!") revoke_fail = {ExternalIntegration.OVERDRIVE: RevokeFail} api = CirculationAPI(self._db, library, api_map=revoke_fail) status = ServiceStatus(api, auth=auth) response = status.checkout_status(lp.identifier) assert 'FAILURE: Doomed to fail!' in response.values() # But at least we got through the borrow and fulfill steps. api = status.circulation.api_for_collection[overdrive_collection.id] eq_(True, api.borrowed) eq_(True, api.fulfilled) eq_(False, api.revoked)
def test_loans_status(self): auth = self.mock_auth class MockPatronActivitySuccess(object): def __init__(self, *args, **kwargs): pass def patron_activity(self, patron, pin): "Simulate a patron with nothing going on." return class MockPatronActivityFailure(object): def __init__(self, *args, **kwargs): pass def patron_activity(self, patron, pin): "Simulate an integration failure." raise ValueError("Doomed to fail!") # Create a variety of Collections for this library. overdrive_collection = self._collection( protocol=ExternalIntegration.OVERDRIVE) axis_collection = self._collection( protocol=ExternalIntegration.AXIS_360) self._default_library.collections.append(overdrive_collection) self._default_library.collections.append(axis_collection) # Test a scenario where we get information for every # relevant collection in the library. everything_succeeds = { ExternalIntegration.OVERDRIVE: MockPatronActivitySuccess, ExternalIntegration.AXIS_360: MockPatronActivitySuccess } api = CirculationAPI(self._db, self._default_library, api_map=everything_succeeds) status = ServiceStatus(api, auth=auth) response = status.loans_status(response=True) for value in response.values(): assert value.startswith('SUCCESS') # Simulate a failure in one of the providers. overdrive_fails = { ExternalIntegration.OVERDRIVE: MockPatronActivityFailure, ExternalIntegration.AXIS_360: MockPatronActivitySuccess } api = CirculationAPI(self._db, self._default_library, api_map=overdrive_fails) status = ServiceStatus(api, auth=auth) response = status.loans_status(response=True) key = '%s patron account (Overdrive)' % overdrive_collection.name eq_("FAILURE: Doomed to fail!", response[key]) # Simulate failures on the ILS level. def test_with_broken_basic_auth_provider(value): class BrokenBasicAuthProvider(object): def testing_patron(self, _db): return value auth.basic_auth_provider = BrokenBasicAuthProvider() response = status.loans_status(response=True) eq_( { 'Patron authentication': 'Could not create patron with configured credentials.' }, response) # Test patron can't authenticate test_with_broken_basic_auth_provider( (None, "password that didn't work")) # Auth provider is just totally broken. test_with_broken_basic_auth_provider(None) # If the auth process returns a problem detail, the problem # detail is used as the basis for the error message. class ExpiredPatronProvider(object): def testing_patron(self, _db): return EXPIRED_CREDENTIALS, None auth.basic_auth_provider = ExpiredPatronProvider() response = status.loans_status(response=True) eq_({'Patron authentication': EXPIRED_CREDENTIALS.response[0]}, response)