diff --git a/swh/web/api/views/origin.py b/swh/web/api/views/origin.py --- a/swh/web/api/views/origin.py +++ b/swh/web/api/views/origin.py @@ -21,6 +21,7 @@ from swh.web.common import archive from swh.web.common.exc import BadInputExc from swh.web.common.origin_visits import get_origin_visits +from swh.web.common.typing import OriginInfo from swh.web.common.utils import origin_visit_types, reverse DOC_RETURN_ORIGIN = """ @@ -143,6 +144,7 @@ return api_lookup( archive.lookup_origin, ori_dict, + lookup_similar_urls=False, notfound_msg=error_msg, enrich_fn=enrich_origin, request=request, @@ -326,7 +328,7 @@ """ result = {} - origin_query = {"url": origin_url} + origin_query: OriginInfo = {"url": origin_url} notfound_msg = "No origin {} found".format(origin_url) url_args_next = {"origin_url": origin_url} per_page = int(request.query_params.get("per_page", "10")) @@ -347,6 +349,9 @@ for v in visits: yield v + # check origin existence in the archive + archive.lookup_origin(origin_query, lookup_similar_urls=False) + results = api_lookup( _lookup_origin_visits, origin_query, @@ -412,6 +417,9 @@ :swh_web_api:`origin/https://github.com/hylang/hy/visit/latest/` """ + # check origin existence in the archive + archive.lookup_origin({"url": origin_url}, lookup_similar_urls=False) + require_snapshot = request.query_params.get("require_snapshot", "false") return api_lookup( archive.lookup_origin_visit_latest, @@ -453,6 +461,9 @@ :swh_web_api:`origin/https://github.com/hylang/hy/visit/1/` """ + # check origin existence in the archive + archive.lookup_origin({"url": origin_url}, lookup_similar_urls=False) + return api_lookup( archive.lookup_origin_visit, origin_url, @@ -491,6 +502,9 @@ :swh_web_api:`origin/https://github.com/python/cpython/intrinsic-metadata` """ + # check origin existence in the archive + archive.lookup_origin({"url": origin_url}, lookup_similar_urls=False) + return api_lookup( archive.lookup_origin_intrinsic_metadata, origin_url, diff --git a/swh/web/common/archive.py b/swh/web/common/archive.py --- a/swh/web/common/archive.py +++ b/swh/web/common/archive.py @@ -232,18 +232,20 @@ return converters.from_swh(lic, hashess={"id"}) -def lookup_origin(origin: OriginInfo) -> OriginInfo: +def lookup_origin(origin: OriginInfo, lookup_similar_urls: bool = True) -> OriginInfo: """Return information about the origin matching dict origin. Args: origin: origin's dict with 'url' key + lookup_similar_urls: if :const:`True`, lookup origin with and + without trailing slash in its URL Returns: origin information as dict. """ origin_urls = [origin["url"]] - if origin["url"]: + if origin["url"] and lookup_similar_urls: # handle case when user provided an origin url with a trailing # slash while the url in storage does not have it (e.g. GitHub) if origin["url"].endswith("/"): diff --git a/swh/web/tests/api/views/test_origin.py b/swh/web/tests/api/views/test_origin.py --- a/swh/web/tests/api/views/test_origin.py +++ b/swh/web/tests/api/views/test_origin.py @@ -32,24 +32,26 @@ from swh.web.tests.utils import check_api_get_responses -def test_api_lookup_origin_visits_raise_error(api_client, mocker): +def test_api_lookup_origin_visits_raise_error(api_client, origin, mocker): mock_get_origin_visits = mocker.patch("swh.web.api.views.origin.get_origin_visits") err_msg = "voluntary error to check the bad request middleware." mock_get_origin_visits.side_effect = BadInputExc(err_msg) - url = reverse("api-1-origin-visits", url_args={"origin_url": "http://foo"}) + url = reverse("api-1-origin-visits", url_args={"origin_url": origin["url"]}) rv = check_api_get_responses(api_client, url, status_code=400) assert rv.data == {"exception": "BadInputExc", "reason": err_msg} -def test_api_lookup_origin_visits_raise_swh_storage_error_db(api_client, mocker): +def test_api_lookup_origin_visits_raise_swh_storage_error_db( + api_client, origin, mocker +): mock_get_origin_visits = mocker.patch("swh.web.api.views.origin.get_origin_visits") err_msg = "Storage exploded! Will be back online shortly!" mock_get_origin_visits.side_effect = StorageDBError(err_msg) - url = reverse("api-1-origin-visits", url_args={"origin_url": "http://foo"}) + url = reverse("api-1-origin-visits", url_args={"origin_url": origin["url"]}) rv = check_api_get_responses(api_client, url, status_code=503) assert rv.data == { "exception": "StorageDBError", @@ -57,13 +59,15 @@ } -def test_api_lookup_origin_visits_raise_swh_storage_error_api(api_client, mocker): +def test_api_lookup_origin_visits_raise_swh_storage_error_api( + api_client, origin, mocker +): mock_get_origin_visits = mocker.patch("swh.web.api.views.origin.get_origin_visits") err_msg = "Storage API dropped dead! Will resurrect asap!" mock_get_origin_visits.side_effect = StorageAPIError(err_msg) - url = reverse("api-1-origin-visits", url_args={"origin_url": "http://foo"}) + url = reverse("api-1-origin-visits", url_args={"origin_url": origin["url"]}) rv = check_api_get_responses(api_client, url, status_code=503) assert rv.data == { "exception": "StorageAPIError", @@ -837,3 +841,27 @@ assert {o["url"] for o in rv.data} == { o["url"] for o in archived_origins if o["type"] == visit_type } + + +@pytest.mark.parametrize( + "view_name, extra_args", + [ + ("api-1-origin", {}), + ("api-1-origin-visits", {}), + ("api-1-origin-visit", {"visit_id": 1}), + ("api-1-origin-visit-latest", {}), + ("api-origin-intrinsic-metadata", {}), + ], +) +def test_api_origin_by_url_with_extra_trailing_slash( + api_client, origin, view_name, extra_args +): + origin_url = origin["url"] + assert not origin_url.endswith("/") + origin_url = origin_url + "/" + url = reverse(view_name, url_args={"origin_url": origin_url, **extra_args}) + rv = check_api_get_responses(api_client, url, status_code=404) + assert rv.data == { + "exception": "NotFoundExc", + "reason": f"Origin with url {origin_url} not found!", + }