diff --git a/requirements-test.txt b/requirements-test.txt --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,5 @@ pytest +pytest-aiohttp pytest-postgresql dulwich >= 0.18.7 swh.loader.git >= 0.0.52 diff --git a/swh/vault/api/client.py b/swh/vault/api/client.py --- a/swh/vault/api/client.py +++ b/swh/vault/api/client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2018 The Software Heritage developers +# Copyright (C) 2016-2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -6,10 +6,14 @@ from swh.model import hashutil from swh.core.api import RPCClient +from swh.vault.backend import NotFoundExc + class RemoteVaultClient(RPCClient): """Client to the Software Heritage vault cache.""" + reraise_exceptions = [NotFoundExc] + # Web API endpoints def fetch(self, obj_type, obj_id): diff --git a/swh/vault/api/server.py b/swh/vault/api/server.py --- a/swh/vault/api/server.py +++ b/swh/vault/api/server.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2019 The Software Heritage developers +# Copyright (C) 2016-2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -56,7 +56,7 @@ obj_id = request.match_info["id"] if not request.app["backend"].is_available(obj_type, obj_id): - raise aiohttp.web.HTTPNotFound + raise NotFoundExc(f"{obj_type} {obj_id} is not available.") return encode_data(request.app["backend"].fetch(obj_type, obj_id)) @@ -79,14 +79,11 @@ sticky = request.query.get("sticky") in ("true", "1") if obj_type not in COOKER_TYPES: - raise aiohttp.web.HTTPNotFound + raise NotFoundExc(f"{obj_type} is an unknown type.") - try: - info = request.app["backend"].cook_request( - obj_type, obj_id, email=email, sticky=sticky - ) - except NotFoundExc: - raise aiohttp.web.HTTPNotFound + info = request.app["backend"].cook_request( + obj_type, obj_id, email=email, sticky=sticky + ) # TODO: return 201 status (Created) once the api supports it return encode_data(user_info(info)) @@ -99,7 +96,7 @@ info = request.app["backend"].task_info(obj_type, obj_id) if not info: - raise aiohttp.web.HTTPNotFound + raise NotFoundExc(f"{obj_type} {obj_id} was not found.") return encode_data(user_info(info)) @@ -152,7 +149,7 @@ batch = yield from decode_request(request) for obj_type, obj_id in batch: if obj_type not in COOKER_TYPES: - raise aiohttp.web.HTTPNotFound + raise NotFoundExc(f"{obj_type} is an unknown type.") batch_id = request.app["backend"].batch_cook(batch) return encode_data({"id": batch_id}) @@ -162,7 +159,7 @@ batch_id = request.match_info["batch_id"] bundles = request.app["backend"].batch_info(batch_id) if not bundles: - raise aiohttp.web.HTTPNotFound + raise NotFoundExc(f"Batch {batch_id} does not exist.") bundles = [user_info(bundle) for bundle in bundles] counter = collections.Counter(b["status"] for b in bundles) res = { @@ -180,6 +177,7 @@ def make_app(backend, **kwargs): app = RPCServerApp(**kwargs) app.router.add_route("GET", "/", index) + app.client_exception_classes = (NotFoundExc,) # Endpoints used by the web API app.router.add_route("GET", "/fetch/{type}/{id}", vault_fetch) diff --git a/swh/vault/tests/test_server.py b/swh/vault/tests/test_server.py new file mode 100644 --- /dev/null +++ b/swh/vault/tests/test_server.py @@ -0,0 +1,56 @@ +# Copyright (C) 2020 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import pytest + +from swh.core.api.serializers import msgpack_loads, msgpack_dumps + +from swh.vault.api.server import make_app + + +@pytest.fixture +def client(swh_vault, loop, aiohttp_client): + app = make_app(backend=swh_vault) + return loop.run_until_complete(aiohttp_client(app)) + + +async def test_index(client): + resp = await client.get("/") + assert resp.status == 200 + + +async def test_cook_notfound(client): + resp = await client.post("/cook/directory/000000") + assert resp.status == 400 + content = msgpack_loads(await resp.content.read()) + assert content["exception"]["type"] == "NotFoundExc" + assert content["exception"]["args"] == ["Object 000000 was not found."] + + +async def test_progress_notfound(client): + resp = await client.get("/progress/directory/000000") + assert resp.status == 400 + content = msgpack_loads(await resp.content.read()) + assert content["exception"]["type"] == "NotFoundExc" + assert content["exception"]["args"] == ["directory 000000 was not found."] + + +async def test_batch_cook_invalid_type(client): + data = msgpack_dumps([("foobar", [])]) + resp = await client.post( + "/batch_cook", data=data, headers={"Content-Type": "application/x-msgpack"} + ) + assert resp.status == 400 + content = msgpack_loads(await resp.content.read()) + assert content["exception"]["type"] == "NotFoundExc" + assert content["exception"]["args"] == ["foobar is an unknown type."] + + +async def test_batch_progress_notfound(client): + resp = await client.get("/batch_progress/1") + assert resp.status == 400 + content = msgpack_loads(await resp.content.read()) + assert content["exception"]["type"] == "NotFoundExc" + assert content["exception"]["args"] == ["Batch 1 does not exist."]