Example #1
0
    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))
Example #3
0
    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
Example #5
0
    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))
Example #6
0
    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)
Example #7
0
    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))
Example #9
0
    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")
Example #11
0
    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"
        )
Example #13
0
    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))