def test_initialization(self): """Asserts that a RelatedBooksLane won't be initialized for a work without related books """ # A book without a series or a contributor on a circ manager without # NoveList recommendations raises an error. self._db.delete(self.edition.contributions[0]) self._db.commit() assert_raises( ValueError, RelatedBooksLane, self._default_library, self.work, "" ) # A book with a contributor initializes a RelatedBooksLane. luthor, i = self._contributor('Luthor, Lex') self.edition.add_contributor(luthor, [Contributor.EDITOR_ROLE]) result = RelatedBooksLane(self._default_library, self.work, '') eq_(self.work, result.work) [sublane] = result.children eq_(True, isinstance(sublane, ContributorLane)) eq_(sublane.contributors, [luthor]) # As does a book in a series. self.edition.series = "All By Myself" result = RelatedBooksLane(self._default_library, self.work, "") eq_(2, len(result.children)) [contributor, series] = result.children eq_(True, isinstance(series, SeriesLane)) # When NoveList is configured and recommendations are available, # a RecommendationLane will be included. self._external_integration( ExternalIntegration.NOVELIST, goal=ExternalIntegration.METADATA_GOAL, username=u'library', password=u'sure', libraries=[self._default_library] ) mock_api = MockNoveListAPI(self._db) response = Metadata( self.edition.data_source, recommendations=[self._identifier()] ) mock_api.setup(response) result = RelatedBooksLane(self._default_library, self.work, "", novelist_api=mock_api) eq_(3, len(result.children)) # The book's language and audience list is passed down to all sublanes. eq_(['eng'], result.languages) for sublane in result.children: eq_(result.languages, sublane.languages) if isinstance(sublane, SeriesLane): eq_([result.source_audience], sublane.audiences) else: eq_(sorted(list(result.audiences)), sorted(list(sublane.audiences))) contributor, recommendations, series = result.children eq_(True, isinstance(recommendations, RecommendationLane)) eq_(True, isinstance(series, SeriesLane)) eq_(True, isinstance(contributor, ContributorLane))
def test_initialization(self): """Asserts that a RelatedBooksLane won't be initialized for a work without related books """ # A book without a series or a contributor on a circ manager without # NoveList recommendations raises an error. self._db.delete(self.edition.contributions[0]) self._db.commit() assert_raises( ValueError, RelatedBooksLane, self._default_library, self.work, "" ) # A book with a contributor initializes a RelatedBooksLane. luthor, i = self._contributor('Luthor, Lex') self.edition.add_contributor(luthor, [Contributor.EDITOR_ROLE]) result = RelatedBooksLane(self._default_library, self.work, '') eq_(self.work, result.work) [sublane] = result.children eq_(True, isinstance(sublane, ContributorLane)) eq_(sublane.contributors, [luthor]) # As does a book in a series. self.edition.series = "All By Myself" result = RelatedBooksLane(self._default_library, self.work, "") eq_(2, len(result.children)) [contributor, series] = result.children eq_(True, isinstance(series, SeriesLane)) # When NoveList is configured and recommendations are available, # a RecommendationLane will be included. self._external_integration( ExternalIntegration.NOVELIST, goal=ExternalIntegration.METADATA_GOAL, username=u'library', password=u'sure', libraries=[self._default_library] ) mock_api = MockNoveListAPI(self._db) response = Metadata( self.edition.data_source, recommendations=[self._identifier()] ) mock_api.setup(response) result = RelatedBooksLane(self._default_library, self.work, "", novelist_api=mock_api) eq_(3, len(result.children)) # The book's language and audience list is passed down to all sublanes. eq_(['eng'], result.languages) for sublane in result.children: eq_(result.languages, sublane.languages) if isinstance(sublane, SeriesLane): eq_([result.source_audience], sublane.audiences) else: eq_(sorted(list(result.audiences)), sorted(list(sublane.audiences))) contributor, recommendations, series = result.children eq_(True, isinstance(recommendations, RecommendationLane)) eq_(True, isinstance(series, SeriesLane)) eq_(True, isinstance(contributor, ContributorLane))
def generate_mock_api(self): """Prep an empty NoveList result.""" source = DataSource.lookup(self._db, DataSource.OVERDRIVE) metadata = Metadata(source) mock_api = MockNoveListAPI(self._db) mock_api.setup_method(metadata) return mock_api
def generate_mock_api(self): """Prep an empty NoveList result.""" source = DataSource.lookup(self._db, DataSource.OVERDRIVE) metadata = Metadata(source) mock_api = MockNoveListAPI(self._db) mock_api.setup(metadata) return mock_api
def test_related_books(self): # A book with no related books returns a ProblemDetail. with temp_config() as config: config['integrations'][Configuration.NOVELIST_INTEGRATION] = {} with self.app.test_request_context('/'): response = self.manager.work_controller.related( self.datasource, self.identifier.type, self.identifier.identifier ) eq_(404, response.status_code) eq_("http://librarysimplified.org/terms/problem/unknown-lane", response.uri) # Prep book with a book in its series and a recommendation. self.lp.presentation_edition.series = "Around the World" self.french_1.presentation_edition.series = "Around the World" SessionManager.refresh_materialized_views(self._db) source = DataSource.lookup(self._db, self.datasource) metadata = Metadata(source) mock_api = MockNoveListAPI() metadata.recommendations = [self.english_2.license_pools[0].identifier] mock_api.setup(metadata) # A grouped feed is returned with both of these related books with self.app.test_request_context('/'): response = self.manager.work_controller.related( self.datasource, self.identifier.type, self.identifier.identifier, novelist_api=mock_api ) eq_(200, response.status_code) feed = feedparser.parse(response.data) eq_(3, len(feed['entries'])) # One book is in the recommendations feed. [e1] = [e for e in feed['entries'] if e['title'] == self.english_2.title] [collection_link] = [link for link in e1['links'] if link['rel']=='collection'] eq_("Recommended Books", collection_link['title']) work_url = "/works/%s/%s/%s/" % (self.datasource, self.identifier.type, self.identifier.identifier) expected = urllib.quote(work_url + 'recommendations') eq_(True, collection_link['href'].endswith(expected)) # Two books are in the series feed. The original work and its companion [e2] = [e for e in feed['entries'] if e['title'] == self.french_1.title] [collection_link] = [link for link in e2['links'] if link['rel']=='collection'] eq_("Around the World", collection_link['title']) expected = urllib.quote(work_url + 'series') eq_(True, collection_link['href'].endswith(expected)) [e3] = [e for e in feed['entries'] if e['title'] == self.english_1.title] [collection_link] = [link for link in e3['links'] if link['rel']=='collection'] eq_("Around the World", collection_link['title']) expected = urllib.quote(work_url + 'series') eq_(True, collection_link['href'].endswith(expected))
def test_recommendations(self): # Prep an empty recommendation. source = DataSource.lookup(self._db, self.datasource) metadata = Metadata(source) mock_api = MockNoveListAPI() mock_api.setup(metadata) SessionManager.refresh_materialized_views(self._db) with self.app.test_request_context('/'): response = self.manager.work_controller.recommendations( self.datasource, self.identifier.type, self.identifier.identifier, novelist_api=mock_api ) eq_(200, response.status_code) feed = feedparser.parse(response.data) eq_('Recommended Books', feed['feed']['title']) eq_(0, len(feed['entries'])) # Delete the cache and prep a recommendation result. [cached_empty_feed] = self._db.query(CachedFeed).all() self._db.delete(cached_empty_feed) metadata.recommendations = [self.english_2.license_pools[0].identifier] mock_api.setup(metadata) SessionManager.refresh_materialized_views(self._db) with self.app.test_request_context('/'): response = self.manager.work_controller.recommendations( self.datasource, self.identifier.type, self.identifier.identifier, novelist_api=mock_api ) # A feed is returned with the proper recommendation. eq_(200, response.status_code) feed = feedparser.parse(response.data) eq_('Recommended Books', feed['feed']['title']) eq_(1, len(feed['entries'])) [entry] = feed['entries'] eq_(self.english_2.title, entry['title']) eq_(self.english_2.author, entry['author']) with temp_config() as config: with self.app.test_request_context('/'): config['integrations'][Configuration.NOVELIST_INTEGRATION] = {} response = self.manager.work_controller.recommendations( self.datasource, self.identifier.type, self.identifier.identifier ) eq_(404, response.status_code) eq_("http://librarysimplified.org/terms/problem/unknown-lane", response.uri)
def test_lookup_equivalent_isbns(self): identifier = self._identifier(identifier_type=Identifier.OVERDRIVE_ID) api = MockNoveListAPI.from_config(self._default_library) # If there are no ISBN equivalents, it returns None. eq_(None, api.lookup_equivalent_isbns(identifier)) source = DataSource.lookup(self._db, DataSource.OVERDRIVE) identifier.equivalent_to(source, self._identifier(), strength=1) self._db.commit() eq_(None, api.lookup_equivalent_isbns(identifier)) # If there's an ISBN equivalent, but it doesn't result in metadata, # it returns none. isbn = self._identifier(identifier_type=Identifier.ISBN) identifier.equivalent_to(source, isbn, strength=1) self._db.commit() api.responses.append(None) eq_(None, api.lookup_equivalent_isbns(identifier)) # Create an API class that can mockout NoveListAPI.choose_best_metadata class MockBestMetadataAPI(MockNoveListAPI): choose_best_metadata_return = None def choose_best_metadata(self, *args, **kwargs): return self.choose_best_metadata_return api = MockBestMetadataAPI.from_config(self._default_library) # Give the identifier another ISBN equivalent. isbn2 = self._identifier(identifier_type=Identifier.ISBN) identifier.equivalent_to(source, isbn2, strength=1) self._db.commit() # Queue metadata responses for each ISBN lookup. metadatas = [object(), object()] api.responses.extend(metadatas) # If choose_best_metadata returns None, the lookup returns None. api.choose_best_metadata_return = (None, None) eq_(None, api.lookup_equivalent_isbns(identifier)) # Lookup was performed for both ISBNs. eq_([], api.responses) # If choose_best_metadata returns a low confidence metadata, the # lookup returns None. api.responses.extend(metadatas) api.choose_best_metadata_return = (metadatas[0], 0.33) eq_(None, api.lookup_equivalent_isbns(identifier)) # If choose_best_metadata returns a high confidence metadata, the # lookup returns the metadata. api.responses.extend(metadatas) api.choose_best_metadata_return = (metadatas[1], 0.67) eq_(metadatas[1], api.lookup_equivalent_isbns(identifier))
def test_lookup_equivalent_isbns(self): identifier = self._identifier(identifier_type=Identifier.OVERDRIVE_ID) api = MockNoveListAPI.from_config(self._default_library) # If there are no ISBN equivalents, it returns None. eq_(None, api.lookup_equivalent_isbns(identifier)) source = DataSource.lookup(self._db, DataSource.OVERDRIVE) identifier.equivalent_to(source, self._identifier(), strength=1) self._db.commit() eq_(None, api.lookup_equivalent_isbns(identifier)) # If there's an ISBN equivalent, but it doesn't result in metadata, # it returns none. isbn = self._identifier(identifier_type=Identifier.ISBN) identifier.equivalent_to(source, isbn, strength=1) self._db.commit() api.responses.append(None) eq_(None, api.lookup_equivalent_isbns(identifier)) # Create an API class that can mockout NoveListAPI.choose_best_metadata class MockBestMetadataAPI(MockNoveListAPI): choose_best_metadata_return = None def choose_best_metadata(self, *args, **kwargs): return self.choose_best_metadata_return api = MockBestMetadataAPI.from_config(self._default_library) # Give the identifier another ISBN equivalent. isbn2 = self._identifier(identifier_type=Identifier.ISBN) identifier.equivalent_to(source, isbn2, strength=1) self._db.commit() # Queue metadata responses for each ISBN lookup. metadatas = [object(), object()] api.responses.extend(metadatas) # If choose_best_metadata returns None, the lookup returns None. api.choose_best_metadata_return = (None, None) eq_(None, api.lookup_equivalent_isbns(identifier)) # Lookup was performed for both ISBNs. eq_([], api.responses) # If choose_best_metadata returns a low confidence metadata, the # lookup returns None. api.responses.extend(metadatas) api.choose_best_metadata_return = (metadatas[0], 0.33) eq_(None, api.lookup_equivalent_isbns(identifier)) # If choose_best_metadata returns a high confidence metadata, the # lookup returns the metadata. api.responses.extend(metadatas) api.choose_best_metadata_return = (metadatas[1], 0.67) eq_(metadatas[1], api.lookup_equivalent_isbns(identifier))
def test_initialization(self): """Asserts that a RelatedBooksLane won't be initialized for a work without related books """ work = self._work(with_license_pool=True) [lp] = work.license_pools with temp_config() as config: # A book without a series on a circ manager without # NoveList recommendations raises an error. config['integrations'][Configuration.NOVELIST_INTEGRATION] = {} assert_raises( ValueError, RelatedBooksLane, self._db, lp, "" ) # But a book from a series initializes a RelatedBooksLane just fine. lp.presentation_edition.series = "All By Myself" result = RelatedBooksLane(self._db, lp, "") eq_(lp, result.license_pool) [sublane] = result.sublanes eq_(True, isinstance(sublane, SeriesLane)) with temp_config() as config: config['integrations'][Configuration.NOVELIST_INTEGRATION] = { Configuration.NOVELIST_PROFILE : 'library', Configuration.NOVELIST_PASSWORD : '******' } # When NoveList is configured and recommendations are available, # a RecommendationLane will be included. mock_api = MockNoveListAPI() response = Metadata( lp.data_source, recommendations=[self._identifier()] ) mock_api.setup(response) result = RelatedBooksLane(self._db, lp, "", novelist_api=mock_api) eq_(2, len(result.sublanes)) recommendations, series = result.sublanes eq_(True, isinstance(recommendations, RecommendationLane)) eq_(True, isinstance(series, SeriesLane))
def setup(self): super(TestNoveListCoverageProvider, self).setup() with temp_config() as config: config['integrations'][Configuration.NOVELIST_INTEGRATION] = { Configuration.NOVELIST_PROFILE: "library", Configuration.NOVELIST_PASSWORD: "******" } self.novelist = NoveListCoverageProvider(self._db) self.novelist.api = MockNoveListAPI() self.metadata = Metadata(data_source=self.novelist.source, primary_identifier=self._identifier( identifier_type=Identifier.NOVELIST_ID), title=u"The Great American Novel")
def setup(self): super(TestNoveListCoverageProvider, self).setup() self.integration = self._external_integration( ExternalIntegration.NOVELIST, ExternalIntegration.METADATA_GOAL, username=u'library', password=u'yep', libraries=[self._default_library]) self.novelist = NoveListCoverageProvider(self._db) self.novelist.api = MockNoveListAPI.from_config(self._default_library) self.metadata = Metadata(data_source=self.novelist.data_source, primary_identifier=self._identifier( identifier_type=Identifier.NOVELIST_ID), title=u"The Great American Novel")
def setup(self): super(TestNoveListCoverageProvider, self).setup() self.integration = self._external_integration( ExternalIntegration.NOVELIST, ExternalIntegration.METADATA_GOAL, username=u'library', password=u'yep', libraries=[self._default_library] ) self.novelist = NoveListCoverageProvider(self._db) self.novelist.api = MockNoveListAPI.from_config(self._default_library) self.metadata = Metadata( data_source = self.novelist.data_source, primary_identifier=self._identifier( identifier_type=Identifier.NOVELIST_ID ), title=u"The Great American Novel" )
def test_initialization(self): # Asserts that a RelatedBooksLane won't be initialized for a work # without related books # A book without a series or a contributor on a circ manager without # NoveList recommendations raises an error. self._db.delete(self.edition.contributions[0]) self._db.commit() pytest.raises( ValueError, RelatedBooksLane, self._default_library, self.work, "" ) # A book with a contributor initializes a RelatedBooksLane. luthor, i = self._contributor("Luthor, Lex") self.edition.add_contributor(luthor, [Contributor.EDITOR_ROLE]) result = RelatedBooksLane(self._default_library, self.work, "") assert self.work == result.work [sublane] = result.children assert True == isinstance(sublane, ContributorLane) assert sublane.contributor == luthor # As does a book in a series. self.edition.series = "All By Myself" result = RelatedBooksLane(self._default_library, self.work, "") assert 2 == len(result.children) [contributor, series] = result.children assert True == isinstance(series, SeriesLane) # When NoveList is configured and recommendations are available, # a RecommendationLane will be included. self._external_integration( ExternalIntegration.NOVELIST, goal=ExternalIntegration.METADATA_GOAL, username="******", password="******", libraries=[self._default_library], ) mock_api = MockNoveListAPI(self._db) response = Metadata( self.edition.data_source, recommendations=[self._identifier()] ) mock_api.setup_method(response) result = RelatedBooksLane( self._default_library, self.work, "", novelist_api=mock_api ) assert 3 == len(result.children) [novelist_recommendations] = [ x for x in result.children if isinstance(x, RecommendationLane) ] assert ( "Similar titles recommended by NoveList" == novelist_recommendations.display_name ) # The book's language and audience list is passed down to all sublanes. assert ["eng"] == result.languages for sublane in result.children: assert result.languages == sublane.languages if isinstance(sublane, SeriesLane): assert [result.source_audience] == sublane.audiences else: assert sorted(list(result.audiences)) == sorted(list(sublane.audiences)) contributor, recommendations, series = result.children assert True == isinstance(recommendations, RecommendationLane) assert True == isinstance(series, SeriesLane) assert True == isinstance(contributor, ContributorLane)
def test_initialization(self): """Asserts that a RelatedBooksLane won't be initialized for a work without related books """ with temp_config() as config: # A book without a series or a contributor on a circ manager without # NoveList recommendations raises an error. config['integrations'][Configuration.NOVELIST_INTEGRATION] = {} self._db.delete(self.edition.contributions[0]) self._db.commit() assert_raises(ValueError, RelatedBooksLane, self._db, self.lp, "") # A book with a contributor initializes a RelatedBooksLane. luthor, i = self._contributor('Luthor, Lex') self.edition.add_contributor(luthor, [Contributor.EDITOR_ROLE]) result = RelatedBooksLane(self._db, self.lp, '') eq_(self.lp, result.license_pool) [sublane] = result.sublanes eq_(True, isinstance(sublane, ContributorLane)) eq_(sublane.contributor, luthor) # As does a book in a series. self.edition.series = "All By Myself" result = RelatedBooksLane(self._db, self.lp, "") eq_(2, len(result.sublanes)) [contributor, series] = result.sublanes eq_(True, isinstance(series, SeriesLane)) with temp_config() as config: config['integrations'][Configuration.NOVELIST_INTEGRATION] = { Configuration.NOVELIST_PROFILE: 'library', Configuration.NOVELIST_PASSWORD: '******' } # When NoveList is configured and recommendations are available, # a RecommendationLane will be included. mock_api = MockNoveListAPI() response = Metadata(self.lp.data_source, recommendations=[self._identifier()]) mock_api.setup(response) result = RelatedBooksLane(self._db, self.lp, "", novelist_api=mock_api) eq_(3, len(result.sublanes)) # The book's language and audience list is passed down to all sublanes. eq_(['eng'], result.languages) for sublane in result.sublanes: eq_(result.languages, sublane.languages) if isinstance(sublane, SeriesLane): eq_(set([result.source_audience]), sublane.audiences) else: eq_(sorted(list(result.audiences)), sorted(list(sublane.audiences))) contributor, recommendations, series = result.sublanes eq_(True, isinstance(recommendations, RecommendationLane)) eq_(True, isinstance(series, SeriesLane)) eq_(True, isinstance(contributor, ContributorLane))