Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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())
Esempio n. 8
0
    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()