Beispiel #1
0
    def test_borrow_link_raises_unfulfillable_work(self):
        edition, pool = self._edition(with_license_pool=True)
        kindle_mechanism = pool.set_delivery_mechanism(
            DeliveryMechanism.KINDLE_CONTENT_TYPE,
            DeliveryMechanism.KINDLE_DRM, RightsStatus.IN_COPYRIGHT, None)
        epub_mechanism = pool.set_delivery_mechanism(
            Representation.EPUB_MEDIA_TYPE, DeliveryMechanism.ADOBE_DRM,
            RightsStatus.IN_COPYRIGHT, None)
        data_source_name = pool.data_source.name
        identifier = pool.identifier

        annotator = CirculationManagerLoanAndHoldAnnotator(
            None, None, self._default_library, test_mode=True)

        # If there's no way to fulfill the book, borrow_link raises
        # UnfulfillableWork.
        assert_raises(UnfulfillableWork, annotator.borrow_link, identifier,
                      None, [])

        assert_raises(UnfulfillableWork, annotator.borrow_link, identifier,
                      None, [kindle_mechanism])

        # If there's a fulfillable mechanism, everything's fine.
        link = annotator.borrow_link(identifier, None, [epub_mechanism])
        assert link != None

        link = annotator.borrow_link(identifier, None,
                                     [epub_mechanism, kindle_mechanism])
        assert link != None
    def test_active_loan_feed(self):
        patron = self.default_patron
        raw = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        # Nothing in the feed.
        raw = unicode(raw)
        feed = feedparser.parse(raw)
        eq_(0, len(feed['entries']))

        now = datetime.datetime.utcnow()
        tomorrow = now + datetime.timedelta(days=1)

        # A loan of an open-access book is open-ended.
        work1 = self._work(language="eng", with_open_access_download=True)
        loan1 = work1.license_pools[0].loan_to(patron, start=now)

        # A loan of some other kind of book
        work2 = self._work(language="eng", with_license_pool=True)
        loan2 = work2.license_pools[0].loan_to(patron, start=now, end=tomorrow)
        unused = self._work(language="eng", with_open_access_download=True)

        # Get the feed.
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed = feedparser.parse(raw)

        # The only entries in the feed is the work currently out on loan
        # to this patron.
        eq_(2, len(feed['entries']))
        e1, e2 = sorted(feed['entries'], key=lambda x: x['title'])
        eq_(work1.title, e1['title'])
        eq_(work2.title, e2['title'])

        # Make sure that the start and end dates from the loan are present
        # in an <opds:availability> child of the acquisition link.
        tree = etree.fromstring(raw)
        parser = OPDSXMLParser()
        acquisitions = parser._xpath(
            tree, "//atom:entry/atom:link[@rel='http://opds-spec.org/acquisition']"
        )
        eq_(2, len(acquisitions))

        now_s = _strftime(now)
        tomorrow_s = _strftime(tomorrow)
        availabilities = [
            parser._xpath1(x, "opds:availability") for x in acquisitions
        ]

        # One of these availability tags has 'since' but not 'until'.
        # The other one has both.
        [no_until] = [x for x in availabilities if 'until' not in x.attrib] 
        eq_(now_s, no_until.attrib['since'])

        [has_until] = [x for x in availabilities if 'until' in x.attrib]
        eq_(now_s, has_until.attrib['since'])
        eq_(tomorrow_s, has_until.attrib['until'])
    def test_loans_feed_includes_preload_link(self):
        patron = self._patron()
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed = feedparser.parse(raw)['feed']
        links = feed['links']

        [preload_link] = [x for x in links if x['rel'] == 'http://librarysimplified.org/terms/rel/preload']
        assert '/preload' in preload_link['href']
Beispiel #4
0
    def test_drm_device_registration_feed_tags(self):
        """Check that drm_device_registration_feed_tags returns 
        a generic drm:licensor tag, except with the drm:scheme attribute 
        set.
        """
        self.initialize_adobe(self._default_library)
        annotator = CirculationManagerLoanAndHoldAnnotator(
            None, None, self._default_library, test_mode=True)
        patron = self._patron()
        [feed_tag] = annotator.drm_device_registration_feed_tags(patron)
        [generic_tag] = annotator.adobe_id_tags(patron)

        # The feed-level tag has the drm:scheme attribute set.
        key = '{http://librarysimplified.org/terms/drm}scheme'
        eq_("http://librarysimplified.org/terms/drm/scheme/ACS",
            feed_tag.attrib[key])

        # If we remove that attribute, the feed-level tag is the same as the
        # generic tag.
        del feed_tag.attrib[key]
        eq_(etree.tostring(feed_tag), etree.tostring(generic_tag))
Beispiel #5
0
    def test_loans_feed_includes_annotations_link(self):
        patron = self._patron()
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed = feedparser.parse(raw)['feed']
        links = feed['links']

        [annotations_link] = [
            x for x in links if x['rel'].lower() ==
            "http://www.w3.org/ns/oa#annotationService".lower()
        ]
        assert '/annotations' in annotations_link['href']
Beispiel #6
0
    def test_loans_feed_includes_preload_link(self):
        patron = self._patron()
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed = feedparser.parse(raw)['feed']
        links = feed['links']

        [preload_link] = [
            x for x in links
            if x['rel'] == 'http://librarysimplified.org/terms/rel/preload'
        ]
        assert '/preload' in preload_link['href']
    def test_loan_feed_includes_patron(self):
        patron = self._patron()
        patron.username = u'bellhooks'
        patron.authorization_identifier = u'987654321'

        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed_details = feedparser.parse(raw)['feed']

        assert "simplified:authorizationIdentifier" in raw
        assert "simplified:username" in raw
        eq_(patron.username, feed_details['simplified_patron']['simplified:username'])
        eq_(u'987654321', feed_details['simplified_patron']['simplified:authorizationidentifier'])
Beispiel #8
0
    def test_loan_feed_includes_patron(self):
        patron = self._patron()
        patron.username = u'bellhooks'
        patron.authorization_identifier = u'987654321'
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed_details = feedparser.parse(raw)['feed']

        assert "simplified:authorizationIdentifier" in raw
        assert "simplified:username" in raw
        eq_(patron.username,
            feed_details['simplified_patron']['simplified:username'])
        eq_(
            u'987654321', feed_details['simplified_patron']
            ['simplified:authorizationidentifier'])
Beispiel #9
0
    def test_active_loan_feed_ignores_inconsistent_local_data(self):
        patron = self._patron()

        work1 = self._work(language="eng", with_license_pool=True)
        loan, ignore = work1.license_pools[0].loan_to(patron)
        work2 = self._work(language="eng", with_license_pool=True)
        hold, ignore = work2.license_pools[0].on_hold_to(patron)

        # Uh-oh, our local loan data is bad.
        loan.license_pool.identifier = None

        # Our local hold data is also bad.
        hold.license_pool = None

        # We can still get a feed...
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)

        # ...but it's empty.
        assert '<entry>' not in unicode(feed_obj)
Beispiel #10
0
    def test_fulfill_feed(self):
        patron = self._patron()

        work = self._work(with_license_pool=True,
                          with_open_access_download=False)
        pool = work.license_pools[0]
        pool.open_access = False
        streaming_mech = pool.set_delivery_mechanism(
            DeliveryMechanism.STREAMING_TEXT_CONTENT_TYPE,
            DeliveryMechanism.OVERDRIVE_DRM, RightsStatus.IN_COPYRIGHT, None)

        now = datetime.datetime.utcnow()
        loan, ignore = pool.loan_to(patron, start=now)
        fulfillment = FulfillmentInfo(
            pool.collection, pool.data_source.name, pool.identifier.type,
            pool.identifier.identifier, "http://streaming_link",
            Representation.TEXT_HTML_MEDIA_TYPE +
            DeliveryMechanism.STREAMING_PROFILE, None, None)

        feed_obj = CirculationManagerLoanAndHoldAnnotator.single_fulfillment_feed(
            None, loan, fulfillment, test_mode=True)
        raw = etree.tostring(feed_obj)

        entries = feedparser.parse(raw)['entries']
        eq_(1, len(entries))

        links = entries[0]['links']

        # The feed for a single fulfillment only includes one fulfill link.
        fulfill_links = [
            link for link in links
            if link['rel'] == "http://opds-spec.org/acquisition"
        ]
        eq_(1, len(fulfill_links))

        eq_(
            Representation.TEXT_HTML_MEDIA_TYPE +
            DeliveryMechanism.STREAMING_PROFILE, fulfill_links[0]['type'])
        eq_("http://streaming_link", fulfill_links[0]['href'])
Beispiel #11
0
    def test_loans_feed_includes_fulfill_links_for_streaming(self):
        patron = self._patron()

        work = self._work(with_license_pool=True,
                          with_open_access_download=False)
        pool = work.license_pools[0]
        pool.open_access = False
        mech1 = pool.delivery_mechanisms[0]
        mech2 = pool.set_delivery_mechanism(Representation.PDF_MEDIA_TYPE,
                                            DeliveryMechanism.ADOBE_DRM,
                                            RightsStatus.IN_COPYRIGHT, None)
        streaming_mech = pool.set_delivery_mechanism(
            DeliveryMechanism.STREAMING_TEXT_CONTENT_TYPE,
            DeliveryMechanism.OVERDRIVE_DRM, RightsStatus.IN_COPYRIGHT, None)

        now = datetime.datetime.utcnow()
        loan, ignore = pool.loan_to(patron, start=now)

        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)

        entries = feedparser.parse(raw)['entries']
        eq_(1, len(entries))

        links = entries[0]['links']

        # Before we fulfill the loan, there are fulfill links for all three mechanisms.
        fulfill_links = [
            link for link in links
            if link['rel'] == "http://opds-spec.org/acquisition"
        ]
        eq_(3, len(fulfill_links))

        eq_(
            set([
                mech1.delivery_mechanism.drm_scheme_media_type,
                mech2.delivery_mechanism.drm_scheme_media_type,
                OPDSFeed.ENTRY_TYPE
            ]), set([link['type'] for link in fulfill_links]))

        # When the loan is fulfilled, there are only fulfill links for that mechanism
        # and the streaming mechanism.
        loan.fulfillment = mech1

        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)

        entries = feedparser.parse(raw)['entries']
        eq_(1, len(entries))

        links = entries[0]['links']

        fulfill_links = [
            link for link in links
            if link['rel'] == "http://opds-spec.org/acquisition"
        ]
        eq_(2, len(fulfill_links))

        eq_(
            set([
                mech1.delivery_mechanism.drm_scheme_media_type,
                OPDSFeed.ENTRY_TYPE
            ]), set([link['type'] for link in fulfill_links]))
Beispiel #12
0
    def test_active_loan_feed(self):
        self.initialize_adobe(self._default_library)
        patron = self._patron()
        cls = CirculationManagerLoanAndHoldAnnotator
        raw = cls.active_loans_for(None, patron, test_mode=True)
        # No entries in the feed...
        raw = unicode(raw)
        feed = feedparser.parse(raw)
        eq_(0, len(feed['entries']))

        # ... but we have a link to the User Profile Management
        # Protocol endpoint...
        links = feed['feed']['links']
        [upmp_link] = [
            x for x in links if x['rel'] ==
            'http://librarysimplified.org/terms/rel/user-profile'
        ]
        annotator = cls(None, None, patron, test_mode=True)
        expect_url = annotator.url_for(
            'patron_profile',
            library_short_name=patron.library.short_name,
            _external=True)
        eq_(expect_url, upmp_link['href'])

        # ... and we have DRM licensing information.
        tree = etree.fromstring(raw)
        parser = OPDSXMLParser()
        licensor = parser._xpath1(tree, "//atom:feed/drm:licensor")

        adobe_patron_identifier = cls._adobe_patron_identifier(patron)

        # The DRM licensing information includes the Adobe vendor ID
        # and the patron's patron identifier for Adobe purposes.
        eq_(self.adobe_vendor_id.username,
            licensor.attrib['{http://librarysimplified.org/terms/drm}vendor'])
        [client_token, device_management_link] = licensor.getchildren()
        expected = ConfigurationSetting.for_library_and_externalintegration(
            self._db, ExternalIntegration.USERNAME, self._default_library,
            self.registry).value.upper()
        assert client_token.text.startswith(expected)
        assert adobe_patron_identifier in client_token.text
        eq_("{http://www.w3.org/2005/Atom}link", device_management_link.tag)
        eq_("http://librarysimplified.org/terms/drm/rel/devices",
            device_management_link.attrib['rel'])

        # Unlike other places this tag shows up, we use the
        # 'scheme' attribute to explicitly state that this
        # <drm:licensor> tag is talking about an ACS licensing
        # scheme. Since we're in a <feed> and not a <link> to a
        # specific book, that context would otherwise be lost.
        eq_('http://librarysimplified.org/terms/drm/scheme/ACS',
            licensor.attrib['{http://librarysimplified.org/terms/drm}scheme'])

        now = datetime.datetime.utcnow()
        tomorrow = now + datetime.timedelta(days=1)

        # A loan of an open-access book is open-ended.
        work1 = self._work(language="eng", with_open_access_download=True)
        loan1 = work1.license_pools[0].loan_to(patron, start=now)

        # A loan of some other kind of book
        work2 = self._work(language="eng", with_license_pool=True)
        loan2 = work2.license_pools[0].loan_to(patron, start=now, end=tomorrow)
        unused = self._work(language="eng", with_open_access_download=True)

        # Get the feed.
        feed_obj = CirculationManagerLoanAndHoldAnnotator.active_loans_for(
            None, patron, test_mode=True)
        raw = unicode(feed_obj)
        feed = feedparser.parse(raw)

        # The only entries in the feed is the work currently out on loan
        # to this patron.
        eq_(2, len(feed['entries']))
        e1, e2 = sorted(feed['entries'], key=lambda x: x['title'])
        eq_(work1.title, e1['title'])
        eq_(work2.title, e2['title'])

        # Make sure that the start and end dates from the loan are present
        # in an <opds:availability> child of the acquisition link.
        tree = etree.fromstring(raw)
        parser = OPDSXMLParser()
        acquisitions = parser._xpath(
            tree,
            "//atom:entry/atom:link[@rel='http://opds-spec.org/acquisition']")
        eq_(2, len(acquisitions))

        now_s = _strftime(now)
        tomorrow_s = _strftime(tomorrow)
        availabilities = [
            parser._xpath1(x, "opds:availability") for x in acquisitions
        ]

        # One of these availability tags has 'since' but not 'until'.
        # The other one has both.
        [no_until] = [x for x in availabilities if 'until' not in x.attrib]
        eq_(now_s, no_until.attrib['since'])

        [has_until] = [x for x in availabilities if 'until' in x.attrib]
        eq_(now_s, has_until.attrib['since'])
        eq_(tomorrow_s, has_until.attrib['until'])