Esempio n. 1
0
    def setup(self):
        super(TestCatalogController, self).setup()
        self.controller = CatalogController(self._db)

        # The collection as it exists on the circulation manager.
        remote_collection = self._collection(username='******',
                                             external_account_id=self._url)
        # The collection as it is recorded / catalogued here.
        self.collection = self._collection(
            name=remote_collection.metadata_identifier,
            protocol=remote_collection.protocol)

        self.work1 = self._work(with_license_pool=True,
                                with_open_access_download=True)
        self.work2 = self._work(with_license_pool=True,
                                with_open_access_download=True)
Esempio n. 2
0
def register():
    return CatalogController(app._db).register()
Esempio n. 3
0
def remove(collection_metadata_identifier):
    return CatalogController(app._db).remove_items(
        collection_details=collection_metadata_identifier)
Esempio n. 4
0
def updates(collection_metadata_identifier):
    return CatalogController(app._db).updates_feed(
        collection_details=collection_metadata_identifier)
Esempio n. 5
0
def metadata_needed_for(collection_metadata_identifier):
    return CatalogController(app._db).metadata_needed_for(
        collection_details=collection_metadata_identifier)
Esempio n. 6
0
def add_with_metadata(collection_metadata_identifier):
    return CatalogController(app._db).add_with_metadata(
        collection_details=collection_metadata_identifier)
Esempio n. 7
0
def update_url():
    return CatalogController(Conf.db).update_client_url()
Esempio n. 8
0
def add(collection_metadata_identifier):
    return CatalogController(Conf.db).add_items(
        collection_details=collection_metadata_identifier
    )
Esempio n. 9
0
class TestCatalogController(ControllerTest):

    XML_PARSE = OPDSXMLParser()._xpath

    def setup(self):
        super(TestCatalogController, self).setup()
        self.controller = CatalogController(self._db)

        # The collection as it exists on the circulation manager.
        remote_collection = self._collection(username='******',
                                             external_account_id=self._url)
        # The collection as it is recorded / catalogued here.
        self.collection = self._collection(
            name=remote_collection.metadata_identifier,
            protocol=remote_collection.protocol)

        self.work1 = self._work(with_license_pool=True,
                                with_open_access_download=True)
        self.work2 = self._work(with_license_pool=True,
                                with_open_access_download=True)

    def xml_value(self, message, tag):
        return self.XML_PARSE(message, tag)[0].text

    def test_updates_feed(self):
        identifier = self.work1.license_pools[0].identifier
        self.collection.catalog_identifier(self._db, identifier)

        with self.app.test_request_context('/', headers=self.valid_auth):
            response = self.controller.updates_feed(self.collection.name)
            # The catalog's updates feed is returned.
            eq_(HTTP_OK, response.status_code)
            feed = feedparser.parse(response.get_data())
            eq_(
                feed.feed.title, u"%s Collection Updates for %s" %
                (self.collection.protocol, self.client.url))

            # The feed has the catalog's catalog.
            eq_(1, len(feed['entries']))
            [entry] = feed['entries']
            eq_(self.work1.title, entry['title'])
            eq_(identifier.urn, entry['id'])

        # A time can be passed.
        time = datetime.utcnow()
        timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ")
        for record in self.work1.coverage_records:
            # Set back the clock on all of work1's time records
            record.timestamp = time - timedelta(days=1)
        with self.app.test_request_context('/?last_update_time=%s' % timestamp,
                                           headers=self.valid_auth):
            response = self.controller.updates_feed(self.collection.name)
            eq_(HTTP_OK, response.status_code)
            feed = feedparser.parse(response.get_data())
            eq_(
                feed.feed.title, u"%s Collection Updates for %s" %
                (self.collection.protocol, self.client.url))

            # The timestamp is included in the url.
            linkified_timestamp = time.strftime("%Y-%m-%d+%H:%M:%S").replace(
                ":", "%3A")
            assert feed['feed']['id'].endswith(linkified_timestamp)
            # And only works updated since the timestamp are returned.
            eq_(0, len(feed['entries']))

        # Works updated since the timestamp are returned
        self.work1.coverage_records[0].timestamp = datetime.utcnow()
        with self.app.test_request_context('/?last_update_time=%s' % timestamp,
                                           headers=self.valid_auth):
            response = self.controller.updates_feed(self.collection.name)
            feed = feedparser.parse(response.get_data())
            eq_(1, len(feed['entries']))
            [entry] = feed['entries']
            eq_(self.work1.title, entry['title'])
            eq_(identifier.urn, entry['id'])

    def test_updates_feed_is_paginated(self):
        for work in [self.work1, self.work2]:
            self.collection.catalog_identifier(
                self._db, work.license_pools[0].identifier)
        with self.app.test_request_context('/?size=1',
                                           headers=self.valid_auth):
            response = self.controller.updates_feed(self.collection.name)
            links = feedparser.parse(response.get_data())['feed']['links']
            assert any([link['rel'] == 'next' for link in links])
            assert not any([link['rel'] == 'previous' for link in links])
            assert not any([link['rel'] == 'first' for l in links])

        with self.app.test_request_context('/?size=1&after=1',
                                           headers=self.valid_auth):
            response = self.controller.updates_feed(self.collection.name)
            links = feedparser.parse(response.get_data())['feed']['links']
            assert any([link['rel'] == 'previous' for link in links])
            assert any([link['rel'] == 'first' for link in links])
            assert not any([link['rel'] == 'next' for link in links])

    def test_add_items(self):
        invalid_urn = "FAKE AS I WANNA BE"
        catalogued_id = self._identifier()
        uncatalogued_id = self._identifier()
        self.collection.catalog_identifier(self._db, catalogued_id)

        parser = OPDSXMLParser()
        message_path = '/atom:feed/simplified:message'

        with self.app.test_request_context(
                '/?urn=%s&urn=%s&urn=%s' %
            (catalogued_id.urn, uncatalogued_id.urn, invalid_urn),
                headers=self.valid_auth):

            response = self.controller.add_items(self.collection.name)

        # None of the identifiers raise or return an error.
        eq_(HTTP_OK, response.status_code)

        # It sends three messages.
        root = etree.parse(StringIO(response.data))
        catalogued, uncatalogued, invalid = self.XML_PARSE(root, message_path)

        # The uncatalogued identifier is now in the catalog.
        assert uncatalogued_id in self.collection.catalog
        # It has an accurate response message.
        eq_(uncatalogued_id.urn, self.xml_value(uncatalogued, 'atom:id'))
        eq_('201', self.xml_value(uncatalogued, 'simplified:status_code'))
        eq_('Successfully added',
            self.xml_value(uncatalogued, 'schema:description'))

        # The catalogued identifier is still in the catalog.
        assert catalogued_id in self.collection.catalog
        # And even though it responds 'OK', the message tells you it
        # was already there.
        eq_(catalogued_id.urn, self.xml_value(catalogued, 'atom:id'))
        eq_('200', self.xml_value(catalogued, 'simplified:status_code'))
        eq_('Already in catalog',
            self.xml_value(catalogued, 'schema:description'))

        # Invalid identifier return 400 errors.
        eq_(invalid_urn, self.xml_value(invalid, 'atom:id'))
        eq_('400', self.xml_value(invalid, 'simplified:status_code'))
        eq_('Could not parse identifier.',
            self.xml_value(invalid, 'schema:description'))

    def test_remove_items(self):
        invalid_urn = "FAKE AS I WANNA BE"
        catalogued_id = self._identifier()
        uncatalogued_id = self._identifier()
        self.collection.catalog_identifier(self._db, catalogued_id)

        message_path = '/atom:feed/simplified:message'
        with self.app.test_request_context(
                '/?urn=%s&urn=%s' % (catalogued_id.urn, uncatalogued_id.urn),
                headers=self.valid_auth):

            # The uncatalogued identifier doesn't raise or return an error.
            response = self.controller.remove_items(self.collection.name)
            eq_(HTTP_OK, response.status_code)

            # It sends two <simplified:message> tags.
            root = etree.parse(StringIO(response.data))
            catalogued, uncatalogued = self.XML_PARSE(root, message_path)

            eq_(catalogued_id.urn, self.xml_value(catalogued, 'atom:id'))
            eq_(str(HTTP_OK),
                self.xml_value(catalogued, 'simplified:status_code'))
            eq_("Successfully removed",
                self.xml_value(catalogued, 'schema:description'))

            eq_(uncatalogued_id.urn, self.xml_value(uncatalogued, 'atom:id'))
            eq_(str(HTTP_NOT_FOUND),
                self.xml_value(uncatalogued, 'simplified:status_code'))
            eq_("Not in catalog",
                self.xml_value(uncatalogued, 'schema:description'))

            # It sends no <entry> tags.
            eq_([], self.XML_PARSE(root, "//atom:entry"))

            # The catalogued identifier isn't in the catalog.
            assert catalogued_id not in self.collection.catalog
            # But it's still in the database.
            eq_(
                catalogued_id,
                self._db.query(Identifier).filter_by(
                    id=catalogued_id.id).one())

        # Try again, this time including an invalid URN.
        self.collection.catalog_identifier(self._db, catalogued_id)
        with self.app.test_request_context('/?urn=%s&urn=%s' %
                                           (invalid_urn, catalogued_id.urn),
                                           headers=self.valid_auth):
            response = self.controller.remove_items(self.collection.name)
            eq_(HTTP_OK, int(response.status_code))

            # Once again we get two <simplified:message> tags.
            root = etree.parse(StringIO(response.data))
            invalid, catalogued = self.XML_PARSE(root, message_path)

            eq_(invalid_urn, self.xml_value(invalid, 'atom:id'))
            eq_("400", self.xml_value(invalid, 'simplified:status_code'))
            eq_("Could not parse identifier.",
                self.xml_value(invalid, 'schema:description'))

            eq_(catalogued_id.urn, self.xml_value(catalogued, 'atom:id'))
            eq_("200", self.xml_value(catalogued, 'simplified:status_code'))
            eq_("Successfully removed",
                self.xml_value(catalogued, 'schema:description'))

            # We have no <entry> tags.
            eq_([], self.XML_PARSE(root, "//atom:entry"))

            # The catalogued identifier is still removed.
            assert catalogued_id not in self.collection.catalog

    def test_update_client_url(self):
        url = urllib.quote('https://try-me.fake.us/')
        with self.app.test_request_context('/'):
            # Without authentication a ProblemDetail is returned.
            response = self.controller.update_client_url()
            eq_(True, isinstance(response, ProblemDetail))
            eq_(INVALID_CREDENTIALS, response)

        with self.app.test_request_context('/', headers=self.valid_auth):
            # When a URL isn't provided, a ProblemDetail is returned.
            response = self.controller.update_client_url()
            eq_(True, isinstance(response, ProblemDetail))
            eq_(400, response.status_code)
            eq_(INVALID_INPUT.uri, response.uri)
            assert 'client_url' in response.detail

        with self.app.test_request_context('/?client_url=%s' % url,
                                           headers=self.valid_auth):
            response = self.controller.update_client_url()
            # The request was successful.
            eq_(HTTP_OK, response.status_code)
            # The IntegrationClient's URL has been changed.
            self.client.url = 'try-me.fake.us'