def test_process_batch_with_identifier_mapping(self):
        """Test that internal identifiers are mapped to and from the form used
        by the external service.
        """

        # Unlike other tests in this class, we are using a real
        # implementation of OPDSImportCoverageProvider.process_batch.
        class TestProvider(OPDSImportCoverageProvider):
            SERVICE_NAME = "Test provider"
            DATA_SOURCE_NAME = DataSource.OA_CONTENT_SERVER

            # Mock the identifier mapping
            def create_identifier_mapping(self, batch):
                return self.mapping

        # This means we need to mock the lookup client instead.
        lookup = MockSimplifiedOPDSLookup(self._url)

        # And create an ExternalIntegration for the metadata_client object.
        self._external_integration(
            ExternalIntegration.METADATA_WRANGLER,
            goal=ExternalIntegration.METADATA_GOAL,
            url=self._url,
        )

        self._default_collection.external_integration.set_setting(
            Collection.DATA_SOURCE_NAME_SETTING, DataSource.OA_CONTENT_SERVER
        )
        provider = TestProvider(self._default_collection, lookup)

        # Create a hard-coded mapping. We use id1 internally, but the
        # foreign data source knows the book as id2.
        id1 = self._identifier()
        id2 = self._identifier()
        provider.mapping = {id2: id1}

        feed = (
            "<feed><entry><id>%s</id><title>Here's your title!</title></entry></feed>"
            % id2.urn
        )
        headers = {"content-type": OPDSFeed.ACQUISITION_FEED_TYPE}
        lookup.queue_response(200, headers=headers, content=feed)
        [identifier] = provider.process_batch([id1])

        # We wanted to process id1. We sent id2 to the server, the
        # server responded with an <entry> for id2, and it was used to
        # modify the Edition associated with id1.
        assert id1 == identifier

        [edition] = id1.primarily_identifies
        assert "Here's your title!" == edition.title
Exemple #2
0
    def test_import_feed_response(self):
        """Verify that import_feed_response instantiates the
        OPDS_IMPORTER_CLASS subclass and calls import_from_feed
        on it.
        """
        class MockOPDSImporter(OPDSImporter):
            def import_from_feed(self, text):
                """Return information that's useful for verifying
                that the OPDSImporter was instantiated with the
                right values.
                """
                return (text, self.collection, self.identifier_mapping,
                        self.data_source_name)

        class MockProvider(MockOPDSImportCoverageProvider):
            OPDS_IMPORTER_CLASS = MockOPDSImporter

        provider = MockProvider(self._default_collection)
        provider.lookup_client = MockSimplifiedOPDSLookup(self._url)

        response = MockRequestsResponse(
            200, {'content-type': OPDSFeed.ACQUISITION_FEED_TYPE}, "some data")
        id_mapping = object()
        (text, collection, mapping,
         data_source_name) = provider.import_feed_response(
             response, id_mapping)
        eq_("some data", text)
        eq_(provider.collection, collection)
        eq_(id_mapping, mapping)
        eq_(provider.data_source.name, data_source_name)
    def test_process_batch_with_identifier_mapping(self):
        """Test that internal identifiers are mapped to and from the form used
        by the external service.
        """

        # Unlike other tests in this class, we are using a real
        # implementation of OPDSImportCoverageProvider.process_batch.
        class TestProvider(OPDSImportCoverageProvider):
            SERVICE_NAME = "Test provider"
            DATA_SOURCE_NAME = DataSource.OA_CONTENT_SERVER

            # Mock the identifier mapping
            def create_identifier_mapping(self, batch):
                return self.mapping

        # This means we need to mock the lookup client instead.
        lookup = MockSimplifiedOPDSLookup(self._url)

        # And create an ExternalIntegration for the metadata_client object.
        self._external_integration(
            ExternalIntegration.METADATA_WRANGLER,
            goal=ExternalIntegration.METADATA_GOAL, url=self._url
        )

        self._default_collection.external_integration.set_setting(
            Collection.DATA_SOURCE_NAME_SETTING, DataSource.OA_CONTENT_SERVER
        )
        provider = TestProvider(self._default_collection, lookup)

        # Create a hard-coded mapping. We use id1 internally, but the
        # foreign data source knows the book as id2.
        id1 = self._identifier()
        id2 = self._identifier()
        provider.mapping = { id2 : id1 }

        feed = "<feed><entry><id>%s</id><title>Here's your title!</title></entry></feed>" % id2.urn
        headers = {"content-type" : OPDSFeed.ACQUISITION_FEED_TYPE}
        lookup.queue_response(200, headers=headers, content=feed)
        [identifier] = provider.process_batch([id1])

        # We wanted to process id1. We sent id2 to the server, the
        # server responded with an <entry> for id2, and it was used to
        # modify the Edition associated with id1.
        eq_(id1, identifier)

        [edition] = id1.primarily_identifies
        eq_("Here's your title!", edition.title)
Exemple #4
0
 def setup(self):
     super(TestContentServerCoverageProvider, self).setup()
     self.lookup = MockSimplifiedOPDSLookup("http://url/")
     self.provider = ContentServerCoverageProvider(
         self._db, content_server=self.lookup
     )
     base_path = os.path.split(__file__)[0]
     self.resource_path = os.path.join(base_path, "files", "opds")
 def create_provider(self, **kwargs):
     with temp_config() as config:
         config[Configuration.INTEGRATIONS][
             Configuration.METADATA_WRANGLER_INTEGRATION] = {
                 Configuration.URL: "http://url.gov"
             }
         lookup = MockSimplifiedOPDSLookup.from_config()
         return MetadataWranglerCoverageProvider(self._db,
                                                 lookup=lookup,
                                                 **kwargs)
Exemple #6
0
    def test_badresponseexception_on_non_opds_feed(self):
        """If the lookup protocol sends something that's not an OPDS
        feed, refuse to go any further.
        """
        provider = self._provider()
        provider.lookup_client = MockSimplifiedOPDSLookup(self._url)

        response = MockRequestsResponse(200, {"content-type": "text/plain"},
                                        "Some data")
        provider.lookup_client.queue_response(response)
        assert_raises_regexp(BadResponseException,
                             "Wrong media type: text/plain",
                             provider.import_feed_response, response, None)
    def test_process_batch_with_identifier_mapping(self):
        """Test that internal identifiers are mapped to and from the form used
        by the external service.
        """

        # Unlike other tests in this class, we are using a real
        # implementation of OPDSImportCoverageProvider.process_batch.
        class TestProvider(OPDSImportCoverageProvider):

            # Mock the identifier mapping
            def create_identifier_mapping(self, batch):
                return self.mapping

        # This means we need to mock the lookup client instead.
        lookup = MockSimplifiedOPDSLookup(self._url)

        source = DataSource.lookup(self._db, DataSource.OA_CONTENT_SERVER)
        provider = TestProvider("test provider", [], source, lookup=lookup)

        # Create a hard-coded mapping. We use id1 internally, but the
        # foreign data source knows the book as id2.
        id1 = self._identifier()
        id2 = self._identifier()
        provider.mapping = {id2: id1}

        feed = "<feed><entry><id>%s</id><title>Here's your title!</title></entry></feed>" % id2.urn
        headers = {"content-type": OPDSFeed.ACQUISITION_FEED_TYPE}
        lookup.queue_response(200, headers=headers, content=feed)
        [identifier] = provider.process_batch([id1])

        # We wanted to process id1. We sent id2 to the server, the
        # server responded with an <entry> for id2, and it was used to
        # modify the Edition associated with id1.
        eq_(id1, identifier)

        [edition] = id1.primarily_identifies
        eq_("Here's your title!", edition.title)
        eq_(id1, edition.primary_identifier)
Exemple #8
0
    def test_only_open_access_books_considered(self):

        lookup = MockSimplifiedOPDSLookup(self._url)
        provider = ContentServerBibliographicCoverageProvider(
            self._default_collection, lookup)

        # Here's an open-access work.
        w1 = self._work(with_license_pool=True, with_open_access_download=True)

        # Here's a work that's not open-access.
        w2 = self._work(with_license_pool=True,
                        with_open_access_download=False)
        w2.license_pools[0].open_access = False

        # Only the open-access work needs coverage.
        eq_([w1.license_pools[0].identifier],
            provider.items_that_need_coverage().all())
Exemple #9
0
    def test_finalize_license_pool(self):

        identifier = self._identifier()
        license_source = DataSource.lookup(self._db, DataSource.GUTENBERG)
        data_source = DataSource.lookup(self._db, DataSource.OA_CONTENT_SERVER)

        # Here's a LicensePool with no presentation edition.
        pool, is_new = LicensePool.for_foreign_id(
            self._db,
            license_source,
            identifier.type,
            identifier.identifier,
            collection=self._default_collection)
        eq_(None, pool.presentation_edition)

        # Here's an Edition for the same book as the LicensePool but
        # from a different data source.
        edition, is_new = Edition.for_foreign_id(self._db, data_source,
                                                 identifier.type,
                                                 identifier.identifier)
        edition.title = self._str

        # Although Edition and LicensePool share an identifier, they
        # are not otherwise related.
        eq_(None, pool.presentation_edition)

        # finalize_license_pool() will create a Work and update the
        # LicensePool's presentation edition, based on the brand-new
        # Edition.
        lookup = MockSimplifiedOPDSLookup(self._url)
        provider = ContentServerBibliographicCoverageProvider(
            self._default_collection, lookup)
        provider.finalize_license_pool(pool)
        work = pool.work
        eq_(edition.title, pool.presentation_edition.title)
        eq_(True, work.presentation_ready)
 def _lookup_client(self, url):
     return MockSimplifiedOPDSLookup(url)
Exemple #11
0
class TestContentServerCoverageProvider(DatabaseTest):

    def setup(self):
        super(TestContentServerCoverageProvider, self).setup()
        self.lookup = MockSimplifiedOPDSLookup("http://url/")
        self.provider = ContentServerCoverageProvider(
            self._db, content_server=self.lookup
        )
        base_path = os.path.split(__file__)[0]
        self.resource_path = os.path.join(base_path, "files", "opds")

    def sample_data(self, filename):
        path = os.path.join(self.resource_path, filename)
        return open(path).read()

    def test_success(self):
        data = self.sample_data("content_server_lookup.opds")
        self.lookup.queue_response(
            200, {"content-type": "application/atom+xml"},
            content=data
        )
        identifier = self._identifier(identifier_type=Identifier.GUTENBERG_ID)

        # Make the Identifier match the book the queued-up response is
        # talking about
        identifier.identifier = "20201"
        success = self.provider.process_item(identifier)
        eq_(success, identifier)

        # The book was imported and turned into a Work.
        work = identifier.licensed_through.work
        eq_("Mary Gray", work.title)

        # It's not presentation-ready yet, because we are the metadata
        # wrangler and our work is not yet done.
        eq_(False, work.presentation_ready)

    def test_no_such_work(self):
        data = self.sample_data("no_such_work.opds")
        self.lookup.queue_response(
            200, {"content-type": "application/atom+xml"},
            content=data
        )
        identifier = self._identifier(identifier_type=Identifier.GUTENBERG_ID)

        # Make the Identifier match the book the queued-up response is
        # talking about
        identifier.identifier = "2020110"
        failure = self.provider.process_item(identifier)
        eq_(identifier, failure.obj)
        eq_("404: I've never heard of this work.", failure.exception)
        eq_(DataSource.OA_CONTENT_SERVER, failure.data_source.name)

        # Most of the time this is a persistent error but it's
        # possible that we know about a book the content server
        # doesn't know about yet.
        eq_(True, failure.transient)

    def test_wrong_work_in_response(self):
        data = self.sample_data("content_server_lookup.opds")
        self.lookup.queue_response(
            200, {"content-type": "application/atom+xml"},
            content=data
        )
        identifier = self._identifier(identifier_type=Identifier.GUTENBERG_ID)

        # The content server told us about a different book than the
        # one we asked about.
        identifier.identifier = "999"
        failure = self.provider.process_item(identifier)
        eq_(identifier, failure.obj)
        eq_('Identifier was not mentioned in lookup response', failure.exception)
        eq_(DataSource.OA_CONTENT_SERVER, failure.data_source.name)
        eq_(True, failure.transient)

    def test_content_server_http_failure(self):
        """Test that HTTP-level failures of the content server
        become transient CoverageFailures.
        """
        identifier = self._identifier(identifier_type=Identifier.GUTENBERG_ID)

        self.lookup.queue_response(
            500, content="help me!"
        )
        failure = self.provider.process_item(identifier)
        eq_(identifier, failure.obj)
        eq_("Got status code 500 from external server, cannot continue.",
            failure.exception)
        eq_(True, failure.transient)

        self.lookup.queue_response(
            200, {"content-type": "text/plain"}, content="help me!"
        )
        failure = self.provider.process_item(identifier)
        eq_(identifier, failure.obj)
        eq_("Content Server served unhandleable media type: text/plain",
            failure.exception)
        eq_(True, failure.transient)
        eq_(DataSource.OA_CONTENT_SERVER, failure.data_source.name)