def test_fulfill_sends_analytics_event(self): self.pool.loan_to(self.patron) fulfillment = self.pool.delivery_mechanisms[0] fulfillment.content = "Fulfilled." fulfillment.content_link = None self.remote.queue_fulfill(fulfillment) config = { Configuration.POLICIES: { Configuration.ANALYTICS_POLICY: ["core.mock_analytics_provider"] } } with temp_config(config) as config: provider = MockAnalyticsProvider() analytics = Analytics.initialize(['core.mock_analytics_provider'], config) result = self.circulation.fulfill(self.patron, '1234', self.pool, self.pool.delivery_mechanisms[0]) # The fulfillment looks good. eq_(fulfillment, result) # An analytics event was created. mock = Analytics.instance().providers[0] eq_(1, mock.count) eq_(CirculationEvent.CM_FULFILL, mock.event_type)
def test_borrow_sends_analytics_event(self): now = datetime.utcnow() loaninfo = LoanInfo( self.pool.identifier.type, self.pool.identifier.identifier, now, now + timedelta(seconds=3600), ) self.remote.queue_checkout(loaninfo) now = datetime.utcnow() config = { Configuration.POLICIES: { Configuration.ANALYTICS_POLICY: ["core.mock_analytics_provider"] } } with temp_config(config) as config: provider = MockAnalyticsProvider() analytics = Analytics.initialize(['core.mock_analytics_provider'], config) loan, hold, is_new = self.borrow() # The Loan looks good. eq_(loaninfo.identifier, loan.license_pool.identifier.identifier) eq_(self.patron, loan.patron) eq_(None, hold) eq_(True, is_new) # An analytics event was created. mock = Analytics.instance().providers[0] eq_(1, mock.count) eq_(CirculationEvent.CM_CHECKOUT, mock.event_type) # Try to 'borrow' the same book again. self.remote.queue_checkout(AlreadyCheckedOut()) loan, hold, is_new = self.borrow() eq_(False, is_new) # Since the loan already existed, no new analytics event was # sent. eq_(1, mock.count) # Now try to renew the book. self.remote.queue_checkout(loaninfo) loan, hold, is_new = self.borrow() eq_(False, is_new) # Renewals are counted as loans, since from an accounting # perspective they _are_ loans. eq_(2, mock.count) # Loans of open-access books go through a different code # path, but they count as loans nonetheless. self.pool.open_access = True self.remote.queue_checkout(loaninfo) loan, hold, is_new = self.borrow() eq_(3, mock.count)
def setup(self): super(TestCirculationAPI, self).setup() self.collection = MockBibliothecaAPI.mock_collection(self._db) edition, self.pool = self._edition( data_source_name=DataSource.BIBLIOTHECA, identifier_type=Identifier.BIBLIOTHECA_ID, with_license_pool=True, collection=self.collection) self.pool.open_access = False self.identifier = self.pool.identifier [self.delivery_mechanism] = self.pool.delivery_mechanisms self.patron = self._patron() self.analytics = MockAnalyticsProvider() self.circulation = MockCirculationAPI( self._db, self._default_library, analytics=self.analytics, api_map={ExternalIntegration.BIBLIOTHECA: MockBibliothecaAPI}) self.remote = self.circulation.api_for_license_pool(self.pool)
def test_release_hold_sends_analytics_event(self): self.pool.on_hold_to(self.patron) self.remote.queue_release_hold(True) config = { Configuration.POLICIES: { Configuration.ANALYTICS_POLICY: ["core.mock_analytics_provider"] } } with temp_config(config) as config: provider = MockAnalyticsProvider() analytics = Analytics.initialize(['core.mock_analytics_provider'], config) result = self.circulation.release_hold(self.patron, '1234', self.pool) eq_(True, result) # An analytics event was created. mock = Analytics.instance().providers[0] eq_(1, mock.count) eq_(CirculationEvent.CM_HOLD_RELEASE, mock.event_type)
def test_hold_sends_analytics_event(self): self.remote.queue_checkout(NoAvailableCopies()) holdinfo = HoldInfo(self.identifier.type, self.identifier.identifier, None, None, 10) self.remote.queue_hold(holdinfo) config = { Configuration.POLICIES: { Configuration.ANALYTICS_POLICY: ["core.mock_analytics_provider"] } } with temp_config(config) as config: provider = MockAnalyticsProvider() analytics = Analytics.initialize(['core.mock_analytics_provider'], config) loan, hold, is_new = self.borrow() # The Hold looks good. eq_(holdinfo.identifier, hold.license_pool.identifier.identifier) eq_(self.patron, hold.patron) eq_(None, loan) eq_(True, is_new) # An analytics event was created. mock = Analytics.instance().providers[0] eq_(1, mock.count) eq_(CirculationEvent.CM_HOLD_PLACE, mock.event_type) # Try to 'borrow' the same book again. self.remote.queue_checkout(AlreadyOnHold()) loan, hold, is_new = self.borrow() eq_(False, is_new) # Since the hold already existed, no new analytics event was # sent. eq_(1, mock.count)
def test_handle_event(self): api = MockBibliothecaAPI(self._db, self.collection) api.queue_response( 200, content=self.sample_data("item_metadata_single.xml")) analytics = MockAnalyticsProvider() monitor = BibliothecaEventMonitor(self._db, self.collection, api_class=api, analytics=analytics) now = datetime.datetime.utcnow() monitor.handle_event("ddf4gr9", "9781250015280", None, now, None, CirculationEvent.DISTRIBUTOR_LICENSE_ADD) # The collection now has a LicensePool corresponding to the book # we just loaded. [pool] = self.collection.licensepools eq_("ddf4gr9", pool.identifier.identifier) # The book has a presentation-ready work and we know its # bibliographic metadata. eq_(True, pool.work.presentation_ready) eq_("The Incense Game", pool.work.title) # The LicensePool's circulation information has been changed # to reflect what we know about the book -- that we have one # license which (as of the instant the event happened) is # available. eq_(1, pool.licenses_owned) eq_(1, pool.licenses_available) # Three analytics events were collected: one for the license add # event itself, one for the 'checkin' that made the new # license available, and one for the first appearance of a new # LicensePool. eq_(3, analytics.count)
def test_update_circulation(self): # Here's information about a book we didn't know about before. circ_data = { "result": { "records": 1, "recentactivity": [{ "historyid": "3738", "id": "34278", "recordId": "econtentRecord34278", "time": "2018-06-26 10:08:23", "action": "Checked Out", "isbn": "9781618856050", "availability": { "accessType": "acs", "totalCopies": "1", "availableCopies": 0, "onHold": 0 } }] } } # Because the book is unknown, update_circulation will do a follow-up # call to api.get_item to get bibliographic information. bib_data = { "result": { "id": "34278", "recordId": "econtentRecord34278", "isbn": "9781618856050", "title": "A book", "availability": { "accessType": "acs", "totalCopies": "1", "availableCopies": 0, "onHold": 0 } } } api = MockEnkiAPI(self._db) api.queue_response(200, content=json.dumps(circ_data)) api.queue_response(200, content=json.dumps(bib_data)) from core.mock_analytics_provider import MockAnalyticsProvider analytics = MockAnalyticsProvider() monitor = EnkiImport(self._db, self.collection, api_class=api, analytics=analytics) since = datetime.datetime.utcnow() monitor.update_circulation(since) # Two requests were made -- one to getRecentActivity # and one to getItem. [method, url, headers, data, params, kwargs] = api.requests.pop(0) eq_('get', method) eq_('https://enkilibrary.org/API/ItemAPI', url) eq_('getRecentActivity', params['method']) eq_(0, params['minutes']) [method, url, headers, data, params, kwargs] = api.requests.pop(0) eq_('get', method) eq_('https://enkilibrary.org/API/ItemAPI', url) eq_('getItem', params['method']) eq_('34278', params['recordid']) # We ended up with one Work, one LicensePool, and one Edition. work = self._db.query(Work).one() licensepool = self._db.query(LicensePool).one() edition = self._db.query(Edition).one() eq_([licensepool], work.license_pools) eq_(edition, licensepool.presentation_edition) identifier = licensepool.identifier eq_(Identifier.ENKI_ID, identifier.type) eq_(u"34278", identifier.identifier) # The LicensePool and Edition take their data from the mock API # requests. eq_("A book", work.title) eq_(1, licensepool.licenses_owned) eq_(0, licensepool.licenses_available) # An analytics event was sent out for the newly discovered book. eq_(1, analytics.count) # Now let's see what update_circulation does when the work # already exists. circ_data['result']['recentactivity'][0]['availability'][ 'totalCopies'] = 10 api.queue_response(200, content=json.dumps(circ_data)) # We're not queuing up more bib data, but that's no problem -- # EnkiImport won't ask for it. # Pump the monitor again. monitor.update_circulation(since) # We made a single request, to getRecentActivity. [method, url, headers, data, params, kwargs] = api.requests.pop(0) eq_('getRecentActivity', params['method']) # The LicensePool was updated, but no new objects were created. eq_(10, licensepool.licenses_owned) for c in (LicensePool, Edition, Work): eq_(1, self._db.query(c).count())
def test__update_circulation(self): # Here's information about a book we didn't know about before. circ_data = { "result": { "records": 1, "recentactivity": [{ "historyid": "3738", "id": "34278", "recordId": "econtentRecord34278", "time": "2018-06-26 10:08:23", "action": "Checked Out", "isbn": "9781618856050", "availability": { "accessType": "acs", "totalCopies": "1", "availableCopies": 0, "onHold": 0, }, }], } } # Because the book is unknown, update_circulation will do a follow-up # call to api.get_item to get bibliographic information. bib_data = { "result": { "id": "34278", "recordId": "econtentRecord34278", "isbn": "9781618856050", "title": "A book", "availability": { "accessType": "acs", "totalCopies": "1", "availableCopies": 0, "onHold": 0, }, } } api = MockEnkiAPI(self._db) api.queue_response(200, content=json.dumps(circ_data)) api.queue_response(200, content=json.dumps(bib_data)) from core.mock_analytics_provider import MockAnalyticsProvider analytics = MockAnalyticsProvider() monitor = EnkiImport(self._db, self.collection, api_class=api, analytics=analytics) end = utc_now() # Ask for circulation events from one hour in 1970. start = datetime_utc(1970, 1, 1, 0, 0, 0) end = datetime_utc(1970, 1, 1, 1, 0, 0) monitor._update_circulation(start, end) # Two requests were made -- one to getRecentActivityTime # and one to getItem. [method, url, headers, data, params, kwargs] = api.requests.pop(0) assert "get" == method assert "https://enkilibrary.org/API/ItemAPI" == url assert "getRecentActivityTime" == params["method"] # The parameters passed to getRecentActivityTime show the # start and end points of the request as seconds since the # epoch. assert "0" == params["stime"] assert "3600" == params["etime"] [method, url, headers, data, params, kwargs] = api.requests.pop(0) assert "get" == method assert "https://enkilibrary.org/API/ItemAPI" == url assert "getItem" == params["method"] assert "34278" == params["recordid"] # We ended up with one Work, one LicensePool, and one Edition. work = self._db.query(Work).one() licensepool = self._db.query(LicensePool).one() edition = self._db.query(Edition).one() assert [licensepool] == work.license_pools assert edition == licensepool.presentation_edition identifier = licensepool.identifier assert Identifier.ENKI_ID == identifier.type assert "34278" == identifier.identifier # The LicensePool and Edition take their data from the mock API # requests. assert "A book" == work.title assert 1 == licensepool.licenses_owned assert 0 == licensepool.licenses_available # An analytics event was sent out for the newly discovered book. assert 1 == analytics.count # Now let's see what update_circulation does when the work # already exists. circ_data["result"]["recentactivity"][0]["availability"][ "totalCopies"] = 10 api.queue_response(200, content=json.dumps(circ_data)) # We're not queuing up more bib data, but that's no problem -- # EnkiImport won't ask for it. # Pump the monitor again. monitor._update_circulation(start, end) # We made a single request, to getRecentActivityTime. [method, url, headers, data, params, kwargs] = api.requests.pop(0) assert "getRecentActivityTime" == params["method"] # The LicensePool was updated, but no new objects were created. assert 10 == licensepool.licenses_owned for c in (LicensePool, Edition, Work): assert 1 == self._db.query(c).count()