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']
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']
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'])
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'])
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)
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]))
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'])