示例#1
0
 def test_api_for_licensepool(self):
     collection = self._collection(
         protocol=ODLWithConsolidatedCopiesAPI.NAME)
     edition, pool = self._edition(with_license_pool=True,
                                   collection=collection)
     shared_collection = SharedCollectionAPI(self._db)
     assert isinstance(shared_collection.api_for_licensepool(pool),
                       ODLWithConsolidatedCopiesAPI)
    def test_api_for_collection(self):
        collection = self._collection()
        shared_collection = SharedCollectionAPI(self._db)
        # The collection isn't a shared collection, so looking up its API
        # raises an exception.
        assert_raises(CirculationException, shared_collection.api, collection)

        collection.protocol = ODLAPI.NAME
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api(collection), ODLAPI)
    def test_api_for_collection(self):
        collection = self._collection()
        shared_collection = SharedCollectionAPI(self._db)
        # The collection isn't a shared collection, so looking up its API
        # raises an exception.
        assert_raises(CirculationException, shared_collection.api, collection)

        collection.protocol = ODLAPI.NAME
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api(collection), ODLAPI)
示例#4
0
 def setup(self):
     super(TestSharedCollectionAPI, self).setup()
     self.collection = self._collection(protocol="Mock")
     self.shared_collection = SharedCollectionAPI(self._db,
                                                  api_map={"Mock": MockAPI})
     self.api = self.shared_collection.api(self.collection)
     ConfigurationSetting.for_externalintegration(
         BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS,
         self.collection.external_integration).value = json.dumps(
             ["http://library.org"])
     self.client, ignore = IntegrationClient.register(
         self._db, "http://library.org")
     edition, self.pool = self._edition(with_license_pool=True,
                                        collection=self.collection)
     [self.delivery_mechanism] = self.pool.delivery_mechanisms
示例#5
0
    def test_initialization_exception(self):
        class MisconfiguredAPI(object):
            def __init__(self, _db, collection):
                raise CannotLoadConfiguration("doomed!")

        api_map = {self._default_collection.protocol: MisconfiguredAPI}
        shared_collection = SharedCollectionAPI(self._db, api_map=api_map)
        # Although the SharedCollectionAPI was created, it has no functioning
        # APIs.
        eq_({}, shared_collection.api_for_collection)

        # Instead, the CannotLoadConfiguration exception raised by the
        # constructor has been stored in initialization_exceptions.
        e = shared_collection.initialization_exceptions[
            self._default_collection.id]
        assert isinstance(e, CannotLoadConfiguration)
        eq_("doomed!", e.message)
 def setup(self):
     super(TestSharedCollectionAPI, self).setup()
     self.collection = self._collection(protocol="Mock")
     self.shared_collection = SharedCollectionAPI(
         self._db, api_map = {
             "Mock" : MockAPI
         }
     )
     self.api = self.shared_collection.api(self.collection)
     ConfigurationSetting.for_externalintegration(
         BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration
     ).value = json.dumps(["http://library.org"])
     self.client, ignore = IntegrationClient.register(self._db, "http://library.org")
     edition, self.pool = self._edition(
         with_license_pool=True, collection=self.collection
     )
     [self.delivery_mechanism] = self.pool.delivery_mechanisms
示例#7
0
class TestSharedCollectionAPI(DatabaseTest):
    def setup(self):
        super(TestSharedCollectionAPI, self).setup()
        self.collection = self._collection(protocol="Mock")
        self.shared_collection = SharedCollectionAPI(self._db,
                                                     api_map={"Mock": MockAPI})
        self.api = self.shared_collection.api(self.collection)
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS,
            self.collection.external_integration).value = json.dumps(
                ["http://library.org"])
        self.client, ignore = IntegrationClient.register(
            self._db, "http://library.org")
        edition, self.pool = self._edition(with_license_pool=True,
                                           collection=self.collection)
        [self.delivery_mechanism] = self.pool.delivery_mechanisms

    def test_initialization_exception(self):
        class MisconfiguredAPI(object):
            def __init__(self, _db, collection):
                raise CannotLoadConfiguration("doomed!")

        api_map = {self._default_collection.protocol: MisconfiguredAPI}
        shared_collection = SharedCollectionAPI(self._db, api_map=api_map)
        # Although the SharedCollectionAPI was created, it has no functioning
        # APIs.
        eq_({}, shared_collection.api_for_collection)

        # Instead, the CannotLoadConfiguration exception raised by the
        # constructor has been stored in initialization_exceptions.
        e = shared_collection.initialization_exceptions[
            self._default_collection.id]
        assert isinstance(e, CannotLoadConfiguration)
        eq_("doomed!", e.message)

    def test_api_for_licensepool(self):
        collection = self._collection(
            protocol=ODLWithConsolidatedCopiesAPI.NAME)
        edition, pool = self._edition(with_license_pool=True,
                                      collection=collection)
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api_for_licensepool(pool),
                          ODLWithConsolidatedCopiesAPI)

    def test_api_for_collection(self):
        collection = self._collection()
        shared_collection = SharedCollectionAPI(self._db)
        # The collection isn't a shared collection, so looking up its API
        # raises an exception.
        assert_raises(CirculationException, shared_collection.api, collection)

        collection.protocol = ODLWithConsolidatedCopiesAPI.NAME
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api(collection),
                          ODLWithConsolidatedCopiesAPI)

    def test_register(self):
        # An auth document URL is required to register.
        assert_raises(InvalidInputException, self.shared_collection.register,
                      self.collection, None)

        # If the url doesn't return a valid auth document, there's an exception.
        auth_response = "not json"

        def do_get(*args, **kwargs):
            return MockRequestsResponse(200, content=auth_response)

        assert_raises(RemoteInitiatedServerError,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        # The auth document also must have a link to the library's catalog.
        auth_response = json.dumps({"links": []})
        assert_raises(RemoteInitiatedServerError,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        # If no external library URLs are configured, no one can register.
        auth_response = json.dumps(
            {"links": [{
                "href": "http://library.org",
                "rel": "start"
            }]})
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS,
            self.collection.external_integration).value = None
        assert_raises(AuthorizationFailedException,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        # If the library's URL isn't in the configuration, it can't register.
        auth_response = json.dumps({
            "links": [{
                "href": "http://differentlibrary.org",
                "rel": "start"
            }]
        })
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS,
            self.collection.external_integration).value = json.dumps(
                ["http://library.org"])
        assert_raises(AuthorizationFailedException,
                      self.shared_collection.register,
                      self.collection,
                      "http://differentlibrary.org/auth",
                      do_get=do_get)

        # Or if the public key is missing from the auth document.
        auth_response = json.dumps(
            {"links": [{
                "href": "http://library.org",
                "rel": "start"
            }]})
        assert_raises(RemoteInitiatedServerError,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        auth_response = json.dumps({
            "public_key": {
                "type": "not RSA",
                "value": "123"
            },
            "links": [{
                "href": "http://library.org",
                "rel": "start"
            }]
        })
        assert_raises(RemoteInitiatedServerError,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        auth_response = json.dumps({
            "public_key": {
                "type": "RSA"
            },
            "links": [{
                "href": "http://library.org",
                "rel": "start"
            }]
        })
        assert_raises(RemoteInitiatedServerError,
                      self.shared_collection.register,
                      self.collection,
                      "http://library.org/auth",
                      do_get=do_get)

        # Here's an auth document with a valid key.
        key = RSA.generate(2048)
        public_key = key.publickey().exportKey()
        encryptor = PKCS1_OAEP.new(key)
        auth_response = json.dumps({
            "public_key": {
                "type": "RSA",
                "value": public_key
            },
            "links": [{
                "href": "http://library.org",
                "rel": "start"
            }]
        })
        response = self.shared_collection.register(self.collection,
                                                   "http://library.org/auth",
                                                   do_get=do_get)

        # An IntegrationClient has been created.
        client = get_one(
            self._db,
            IntegrationClient,
            url=IntegrationClient.normalize_url("http://library.org/"))
        decrypted_secret = encryptor.decrypt(
            base64.b64decode(
                response.get("metadata", {}).get("shared_secret")))
        eq_(client.shared_secret, decrypted_secret)

    def test_borrow(self):
        # This client is registered, but isn't one of the allowed URLs for the collection
        # (maybe it was registered for a different shared collection).
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")

        # Trying to borrow raises an exception.
        assert_raises(AuthorizationFailedException,
                      self.shared_collection.borrow, self.collection,
                      other_client, self.pool)

        # A client that's registered with the collection can borrow.
        self.shared_collection.borrow(self.collection, self.client, self.pool)
        eq_([(self.client, self.pool)], self.api.checkouts)

        # If the client's checking out an existing hold, the hold must be for that client.
        hold, ignore = create(self._db,
                              Hold,
                              integration_client=other_client,
                              license_pool=self.pool)
        assert_raises(CannotLoan,
                      self.shared_collection.borrow,
                      self.collection,
                      self.client,
                      self.pool,
                      hold=hold)

        hold.integration_client = self.client
        self.shared_collection.borrow(self.collection,
                                      self.client,
                                      self.pool,
                                      hold=hold)
        eq_([(self.client, self.pool)], self.api.checkouts[1:])

    def test_revoke_loan(self):
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")
        loan, ignore = create(self._db,
                              Loan,
                              integration_client=other_client,
                              license_pool=self.pool)
        assert_raises(NotCheckedOut, self.shared_collection.revoke_loan,
                      self.collection, self.client, loan)

        loan.integration_client = self.client
        self.shared_collection.revoke_loan(self.collection, self.client, loan)
        eq_([(self.client, loan)], self.api.returns)

    def test_fulfill(self):
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")
        loan, ignore = create(self._db,
                              Loan,
                              integration_client=other_client,
                              license_pool=self.pool)
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan,
                      self.delivery_mechanism)

        loan.integration_client = self.client

        # If the API does not return content or a content link, the loan can't be fulfilled.
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan,
                      self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills)

        self.api.fulfillment = FulfillmentInfo(
            self.collection,
            self.pool.data_source.name,
            self.pool.identifier.type,
            self.pool.identifier.identifier,
            "http://content",
            "text/html",
            None,
            None,
        )
        fulfillment = self.shared_collection.fulfill(self.collection,
                                                     self.client, loan,
                                                     self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)],
            self.api.fulfills[1:])
        eq_(self.delivery_mechanism, loan.fulfillment)

    def test_revoke_hold(self):
        other_client, ignore = IntegrationClient.register(
            self._db, "http://other_library.org")
        hold, ignore = create(self._db,
                              Hold,
                              integration_client=other_client,
                              license_pool=self.pool)

        assert_raises(CannotReleaseHold, self.shared_collection.revoke_hold,
                      self.collection, self.client, hold)

        hold.integration_client = self.client
        self.shared_collection.revoke_hold(self.collection, self.client, hold)
        eq_([(self.client, hold)], self.api.released_holds)
 def test_api_for_licensepool(self):
     collection = self._collection(protocol=ODLAPI.NAME)
     edition, pool = self._edition(with_license_pool=True, collection=collection)
     shared_collection = SharedCollectionAPI(self._db)
     assert isinstance(shared_collection.api_for_licensepool(pool), ODLAPI)
class TestSharedCollectionAPI(DatabaseTest):

    def setup(self):
        super(TestSharedCollectionAPI, self).setup()
        self.collection = self._collection(protocol="Mock")
        self.shared_collection = SharedCollectionAPI(
            self._db, api_map = {
                "Mock" : MockAPI
            }
        )
        self.api = self.shared_collection.api(self.collection)
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration
        ).value = json.dumps(["http://library.org"])
        self.client, ignore = IntegrationClient.register(self._db, "http://library.org")
        edition, self.pool = self._edition(
            with_license_pool=True, collection=self.collection
        )
        [self.delivery_mechanism] = self.pool.delivery_mechanisms

    def test_initialization_exception(self):
        class MisconfiguredAPI(object):
            def __init__(self, _db, collection):
                raise CannotLoadConfiguration("doomed!")

        api_map = { self._default_collection.protocol: MisconfiguredAPI }
        shared_collection = SharedCollectionAPI(
            self._db, api_map=api_map
        )
        # Although the SharedCollectionAPI was created, it has no functioning
        # APIs.
        eq_({}, shared_collection.api_for_collection)

        # Instead, the CannotLoadConfiguration exception raised by the
        # constructor has been stored in initialization_exceptions.
        e = shared_collection.initialization_exceptions[self._default_collection.id]
        assert isinstance(e, CannotLoadConfiguration)
        eq_("doomed!", e.message)

    def test_api_for_licensepool(self):
        collection = self._collection(protocol=ODLAPI.NAME)
        edition, pool = self._edition(with_license_pool=True, collection=collection)
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api_for_licensepool(pool), ODLAPI)

    def test_api_for_collection(self):
        collection = self._collection()
        shared_collection = SharedCollectionAPI(self._db)
        # The collection isn't a shared collection, so looking up its API
        # raises an exception.
        assert_raises(CirculationException, shared_collection.api, collection)

        collection.protocol = ODLAPI.NAME
        shared_collection = SharedCollectionAPI(self._db)
        assert isinstance(shared_collection.api(collection), ODLAPI)

    def test_register(self):
        # An auth document URL is required to register.
        assert_raises(InvalidInputException, self.shared_collection.register,
                      self.collection, None)

        # If the url doesn't return a valid auth document, there's an exception.
        auth_response = "not json"
        def do_get(*args, **kwargs):
            return MockRequestsResponse(200, content=auth_response)
        assert_raises(RemoteInitiatedServerError, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)

        # The auth document also must have a link to the library's catalog.
        auth_response = json.dumps({"links": []})
        assert_raises(RemoteInitiatedServerError, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)

        # If no external library URLs are configured, no one can register.
        auth_response = json.dumps({"links": [{"href": "http://library.org", "rel": "start"}]})
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration
        ).value = None
        assert_raises(AuthorizationFailedException, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)

        # If the library's URL isn't in the configuration, it can't register.
        auth_response = json.dumps({"links": [{"href": "http://differentlibrary.org", "rel": "start"}]})
        ConfigurationSetting.for_externalintegration(
            BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration
        ).value = json.dumps(["http://library.org"])
        assert_raises(AuthorizationFailedException, self.shared_collection.register,
                      self.collection, "http://differentlibrary.org/auth", do_get=do_get)

        # Or if the public key is missing from the auth document.
        auth_response = json.dumps({"links": [{"href": "http://library.org", "rel": "start"}]})
        assert_raises(RemoteInitiatedServerError, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)

        auth_response = json.dumps({"public_key": { "type": "not RSA", "value": "123" },
                                    "links": [{"href": "http://library.org", "rel": "start"}]})
        assert_raises(RemoteInitiatedServerError, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)

        auth_response = json.dumps({"public_key": { "type": "RSA" },
                                    "links": [{"href": "http://library.org", "rel": "start"}]})
        assert_raises(RemoteInitiatedServerError, self.shared_collection.register,
                      self.collection, "http://library.org/auth", do_get=do_get)


        # Here's an auth document with a valid key.
        key = RSA.generate(2048)
        public_key = key.publickey().exportKey()
        encryptor = PKCS1_OAEP.new(key)
        auth_response = json.dumps({"public_key": { "type": "RSA", "value": public_key },
                                    "links": [{"href": "http://library.org", "rel": "start"}]})
        response = self.shared_collection.register(self.collection, "http://library.org/auth", do_get=do_get)

        # An IntegrationClient has been created.
        client = get_one(self._db, IntegrationClient, url=IntegrationClient.normalize_url("http://library.org/"))
        decrypted_secret = encryptor.decrypt(base64.b64decode(response.get("metadata", {}).get("shared_secret")))
        eq_(client.shared_secret, decrypted_secret)

    def test_borrow(self):
        # This client is registered, but isn't one of the allowed URLs for the collection
        # (maybe it was registered for a different shared collection).
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")

        # Trying to borrow raises an exception.
        assert_raises(AuthorizationFailedException, self.shared_collection.borrow,
                      self.collection, other_client, self.pool)

        # A client that's registered with the collection can borrow.
        self.shared_collection.borrow(self.collection, self.client, self.pool)
        eq_([(self.client, self.pool)], self.api.checkouts)

        # If the client's checking out an existing hold, the hold must be for that client.
        hold, ignore = create(self._db, Hold, integration_client=other_client, license_pool=self.pool)
        assert_raises(CannotLoan, self.shared_collection.borrow,
                      self.collection, self.client, self.pool, hold=hold)

        hold.integration_client = self.client
        self.shared_collection.borrow(self.collection, self.client, self.pool, hold=hold)
        eq_([(self.client, self.pool)], self.api.checkouts[1:])

    def test_revoke_loan(self):
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")
        loan, ignore = create(self._db, Loan, integration_client=other_client, license_pool=self.pool)
        assert_raises(NotCheckedOut, self.shared_collection.revoke_loan,
                      self.collection, self.client, loan)

        loan.integration_client = self.client
        self.shared_collection.revoke_loan(self.collection, self.client, loan)
        eq_([(self.client, loan)], self.api.returns)

    def test_fulfill(self):
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")
        loan, ignore = create(self._db, Loan, integration_client=other_client, license_pool=self.pool)
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan, self.delivery_mechanism)

        loan.integration_client = self.client

        # If the API does not return content or a content link, the loan can't be fulfilled.
        assert_raises(CannotFulfill, self.shared_collection.fulfill,
                      self.collection, self.client, loan, self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills)

        self.api.fulfillment = FulfillmentInfo(
            self.collection,
            self.pool.data_source.name,
            self.pool.identifier.type,
            self.pool.identifier.identifier,
            "http://content",
            "text/html",
            None,
            None,
        )
        fulfillment = self.shared_collection.fulfill(self.collection, self.client, loan, self.delivery_mechanism)
        eq_([(self.client, loan, self.delivery_mechanism)], self.api.fulfills[1:])
        eq_(self.delivery_mechanism, loan.fulfillment)

    def test_revoke_hold(self):
        other_client, ignore = IntegrationClient.register(self._db, "http://other_library.org")
        hold, ignore = create(self._db, Hold, integration_client=other_client, license_pool=self.pool)

        assert_raises(CannotReleaseHold, self.shared_collection.revoke_hold,
                      self.collection, self.client, hold)

        hold.integration_client = self.client
        self.shared_collection.revoke_hold(self.collection, self.client, hold)
        eq_([(self.client, hold)], self.api.released_holds)