diff --git a/src/diracx/client/aio/operations/_patch.py b/src/diracx/client/aio/operations/_patch.py index 0d255ecd1..6f532f953 100644 --- a/src/diracx/client/aio/operations/_patch.py +++ b/src/diracx/client/aio/operations/_patch.py @@ -45,7 +45,7 @@ def build_token_request(**kwargs: Any) -> HttpRequest: accept = _headers.pop("Accept", "application/json") # Construct URL - _url = "/auth/token" + _url = "/api/auth/token" _url: str = _format_url_section(_url) _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") diff --git a/src/diracx/client/operations/_patch.py b/src/diracx/client/operations/_patch.py index aacbfd00a..7a42f26e5 100644 --- a/src/diracx/client/operations/_patch.py +++ b/src/diracx/client/operations/_patch.py @@ -48,7 +48,7 @@ def build_token_request(vo: str, **kwargs: Any) -> HttpRequest: accept = _headers.pop("Accept", "application/json") # Construct URL - _url = "/auth/token" + _url = "/api/auth/token" _url: str = _format_url_section(_url) _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") diff --git a/src/diracx/routers/__init__.py b/src/diracx/routers/__init__.py index ac103d09f..41356259b 100644 --- a/src/diracx/routers/__init__.py +++ b/src/diracx/routers/__init__.py @@ -133,9 +133,11 @@ def create_app_inner( dependencies = [] if isinstance(router, DiracxRouter) and router.diracx_require_auth: dependencies.append(Depends(verify_dirac_access_token)) + # Most routers are mounted under /api/ + path_root = getattr(router, "diracx_path_root", "/api") app.include_router( router, - prefix=f"/{system_name}", + prefix=f"{path_root}/{system_name}", tags=[system_name], dependencies=dependencies, ) diff --git a/src/diracx/routers/auth.py b/src/diracx/routers/auth.py index 8bfbd7c12..e63c3f4ec 100644 --- a/src/diracx/routers/auth.py +++ b/src/diracx/routers/auth.py @@ -52,7 +52,7 @@ ) from .fastapi_classes import DiracxRouter -oidc_scheme = OpenIdConnect(openIdConnectUrl="/api/.well-known/openid-configuration") +oidc_scheme = OpenIdConnect(openIdConnectUrl="/.well-known/openid-configuration") @add_settings_annotation diff --git a/src/diracx/routers/fastapi_classes.py b/src/diracx/routers/fastapi_classes.py index 793caa9a0..92cc05a44 100644 --- a/src/diracx/routers/fastapi_classes.py +++ b/src/diracx/routers/fastapi_classes.py @@ -34,7 +34,8 @@ async def lifespan(app: DiracFastAPI): generate_unique_id_function=lambda route: f"{route.tags[0]}_{route.name}", title="Dirac", lifespan=lifespan, - root_path="/api", + openapi_url="/api/openapi.json", + docs_url="/api/docs", ) # FIXME: when autorest will support 3.1.0 # From 0.99.0, FastAPI is using openapi 3.1.0 by default @@ -60,6 +61,8 @@ def __init__( *, dependencies=None, require_auth: bool = True, + path_root: str = "/api", ): super().__init__(dependencies=dependencies) self.diracx_require_auth = require_auth + self.diracx_path_root = path_root diff --git a/src/diracx/routers/well_known.py b/src/diracx/routers/well_known.py index 461ed8474..9f7b93f1f 100644 --- a/src/diracx/routers/well_known.py +++ b/src/diracx/routers/well_known.py @@ -7,7 +7,7 @@ from .dependencies import Config from .fastapi_classes import DiracxRouter -router = DiracxRouter(require_auth=False) +router = DiracxRouter(require_auth=False, path_root="") @router.get("/openid-configuration") diff --git a/tests/client/test_regenerate.py b/tests/client/test_regenerate.py index 2f0712d7a..7616cfcc0 100644 --- a/tests/client/test_regenerate.py +++ b/tests/client/test_regenerate.py @@ -18,7 +18,7 @@ def test_regenerate_client(test_client, tmp_path): WARNING: This test will modify the source code of the client! """ - r = test_client.get("/openapi.json") + r = test_client.get("/api/openapi.json") r.raise_for_status() openapi_spec = tmp_path / "openapi.json" diff --git a/tests/conftest.py b/tests/conftest.py index 3403b275f..6e8b0df48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -156,11 +156,6 @@ async def create_db_schemas(app=app): # already been ran app.lifetime_functions.append(create_db_schemas) - # For test purpose, we remove root_path as it can generate issues. - # TestClient sends HTTP requests directly to the DiracFastAPI, - # bypassing the proxy server. - app.root_path = "" - yield app @@ -301,7 +296,7 @@ def cli_env(monkeypatch, tmp_path, demo_urls): diracx_url = demo_urls["diracx"] # Ensure the demo is working - r = requests.get(f"{diracx_url}/openapi.json") + r = requests.get(f"{diracx_url}/api/openapi.json") r.raise_for_status() assert r.json()["info"]["title"] == "Dirac" diff --git a/tests/routers/auth/test_legacy_exchange.py b/tests/routers/auth/test_legacy_exchange.py index 611f4edf9..eec93a25e 100644 --- a/tests/routers/auth/test_legacy_exchange.py +++ b/tests/routers/auth/test_legacy_exchange.py @@ -17,7 +17,7 @@ def legacy_credentials(monkeypatch): async def test_valid(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:lhcb_user"}, headers=legacy_credentials, ) @@ -25,7 +25,7 @@ async def test_valid(test_client, legacy_credentials): access_token = r.json()["access_token"] r = test_client.get( - "/auth/userinfo", headers={"Authorization": f"Bearer {access_token}"} + "/api/auth/userinfo", headers={"Authorization": f"Bearer {access_token}"} ) assert r.status_code == 200 user_info = r.json() @@ -37,7 +37,7 @@ async def test_valid(test_client, legacy_credentials): async def test_disabled(test_client): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:lhcb_user"}, headers={"Authorization": "Bearer diracx:legacy:ChangeME"}, ) @@ -46,7 +46,7 @@ async def test_disabled(test_client): async def test_no_credentials(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:lhcb_user"}, headers={"Authorization": "Bearer invalid"}, ) @@ -56,7 +56,7 @@ async def test_no_credentials(test_client, legacy_credentials): async def test_invalid_credentials(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:lhcb_user"}, headers={"Authorization": "Bearer invalid"}, ) @@ -66,7 +66,7 @@ async def test_invalid_credentials(test_client, legacy_credentials): async def test_wrong_credentials(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:lhcb_user"}, headers={"Authorization": "Bearer diracx:legacy:ChangeME"}, ) @@ -76,7 +76,7 @@ async def test_wrong_credentials(test_client, legacy_credentials): async def test_unknown_vo(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:unknown group:lhcb_user"}, headers=legacy_credentials, ) @@ -86,7 +86,7 @@ async def test_unknown_vo(test_client, legacy_credentials): async def test_unknown_group(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "chaen", "scope": "vo:lhcb group:unknown"}, headers=legacy_credentials, ) @@ -96,7 +96,7 @@ async def test_unknown_group(test_client, legacy_credentials): async def test_unknown_user(test_client, legacy_credentials): r = test_client.get( - "/auth/legacy-exchange", + "/api/auth/legacy-exchange", params={"preferred_username": "unknown", "scope": "vo:lhcb group:lhcb_user"}, headers=legacy_credentials, ) diff --git a/tests/routers/auth/test_standard.py b/tests/routers/auth/test_standard.py index be7f6a526..e513ff9b4 100644 --- a/tests/routers/auth/test_standard.py +++ b/tests/routers/auth/test_standard.py @@ -102,7 +102,7 @@ async def test_authorization_flow(test_client, auth_httpx_mock: HTTPXMock): ) r = test_client.get( - "/auth/authorize", + "/api/auth/authorize", params={ "response_type": "code", "code_challenge": code_challenge, @@ -158,7 +158,7 @@ async def test_authorization_flow(test_client, auth_httpx_mock: HTTPXMock): ) # Ensure the token request doesn't work a second time - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) assert r.status_code == 400, r.json() assert r.json()["detail"] == "Code was already used" @@ -166,7 +166,7 @@ async def test_authorization_flow(test_client, auth_httpx_mock: HTTPXMock): async def test_device_flow(test_client, auth_httpx_mock: HTTPXMock): # Initiate the device flow (would normally be done from CLI) r = test_client.post( - "/auth/device", + "/api/auth/device", params={ "client_id": DIRAC_CLIENT_ID, "audience": "Dirac server", @@ -183,7 +183,7 @@ async def test_device_flow(test_client, auth_httpx_mock: HTTPXMock): # Check that token requests return "authorization_pending" r = test_client.post( - "/auth/token", + "/api/auth/token", data={ "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": data["device_code"], @@ -234,7 +234,7 @@ async def test_device_flow(test_client, auth_httpx_mock: HTTPXMock): ) # Ensure the token request doesn't work a second time - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) assert r.status_code == 400, r.json() assert r.json()["detail"] == "Code was already used" @@ -275,7 +275,7 @@ async def test_refresh_token_rotation(test_client, auth_httpx_mock: HTTPXMock): # User uses the initial refresh token to get a new one # The server should detect the breach and revoke every token bound to User - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) data = r.json() assert r.status_code == 400, data assert ( @@ -287,7 +287,7 @@ async def test_refresh_token_rotation(test_client, auth_httpx_mock: HTTPXMock): # In theory, new_refresh_token has not been revoked since it is the latest one # But because a breach was detected, it should also be revoked request_data["refresh_token"] = new_refresh_token - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) data = r.json() assert r.status_code == 400, data assert ( @@ -326,7 +326,7 @@ async def test_refresh_token_expired( # Try to get a new access token using the invalid refresh token # The server should detect that it is not encoded properly - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) data = r.json() assert r.status_code == 401, data assert data["detail"] == "Invalid JWT: expired_token: The token is expired" @@ -372,7 +372,7 @@ async def test_refresh_token_invalid(test_client, auth_httpx_mock: HTTPXMock): # Try to get a new access token using the invalid refresh token # The server should detect that it is not encoded properly - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) data = r.json() assert r.status_code == 401, data assert data["detail"] == "Invalid JWT: bad_signature: " @@ -392,7 +392,7 @@ async def test_list_refresh_tokens(test_client, auth_httpx_mock: HTTPXMock): # Normal user lists his/her refresh tokens r = test_client.get( - "/auth/refresh-tokens", + "/api/auth/refresh-tokens", headers={"Authorization": f"Bearer {normal_user_access_token}"}, ) data = r.json() @@ -406,7 +406,7 @@ async def test_list_refresh_tokens(test_client, auth_httpx_mock: HTTPXMock): # Token manager lists refresh tokens: should get his/her own and the normal user's one r = test_client.get( - "/auth/refresh-tokens", + "/api/auth/refresh-tokens", headers={"Authorization": f"Bearer {token_manager_access_token}"}, ) data = r.json() @@ -425,7 +425,7 @@ async def test_list_refresh_tokens(test_client, auth_httpx_mock: HTTPXMock): # Normal user lists his/her refresh tokens again r = test_client.get( - "/auth/refresh-tokens", + "/api/auth/refresh-tokens", headers={"Authorization": f"Bearer {response_data['access_token']}"}, ) data = r.json() @@ -434,7 +434,7 @@ async def test_list_refresh_tokens(test_client, auth_httpx_mock: HTTPXMock): # Token manager lists refresh tokens: should get his/her own and the normal user's one r = test_client.get( - "/auth/refresh-tokens", + "/api/auth/refresh-tokens", headers={"Authorization": f"Bearer {token_manager_access_token}"}, ) data = r.json() @@ -471,7 +471,7 @@ async def test_revoke_refresh_tokens_normal_user( # Normal user tries to delete a random and non-existing RT: should raise an error r = test_client.delete( - "/auth/refresh-tokens/does-not-exists", + "/api/auth/refresh-tokens/does-not-exists", headers={"Authorization": f"Bearer {normal_user_access_token}"}, ) data = r.json() @@ -479,7 +479,7 @@ async def test_revoke_refresh_tokens_normal_user( # Normal user tries to delete token manager's RT: should not work r = test_client.delete( - f"/auth/refresh-tokens/{token_manager_refresh_payload['jti']}", + f"/api/auth/refresh-tokens/{token_manager_refresh_payload['jti']}", headers={"Authorization": f"Bearer {normal_user_access_token}"}, ) data = r.json() @@ -487,7 +487,7 @@ async def test_revoke_refresh_tokens_normal_user( # Normal user tries to delete his/her RT: should work r = test_client.delete( - f"/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", + f"/api/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", headers={"Authorization": f"Bearer {normal_user_access_token}"}, ) data = r.json() @@ -495,7 +495,7 @@ async def test_revoke_refresh_tokens_normal_user( # Normal user tries to delete his/her RT again: should work r = test_client.delete( - f"/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", + f"/api/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", headers={"Authorization": f"Bearer {normal_user_access_token}"}, ) data = r.json() @@ -530,7 +530,7 @@ async def test_revoke_refresh_tokens_token_manager( # Token manager tries to delete token manager's RT: should work r = test_client.delete( - f"/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", + f"/api/auth/refresh-tokens/{normal_user_refresh_payload['jti']}", headers={"Authorization": f"Bearer {token_manager_access_token}"}, ) data = r.json() @@ -538,7 +538,7 @@ async def test_revoke_refresh_tokens_token_manager( # Token manager tries to delete his/her RT: should work r = test_client.delete( - f"/auth/refresh-tokens/{token_manager_refresh_payload['jti']}", + f"/api/auth/refresh-tokens/{token_manager_refresh_payload['jti']}", headers={"Authorization": f"Bearer {token_manager_access_token}"}, ) data = r.json() @@ -551,7 +551,7 @@ def _get_tokens( """Get a pair of tokens (access, refresh) through a device flow code""" # User Initiates a device flow (would normally be done from CLI) r = test_client.post( - "/auth/device", + "/api/auth/device", params={ "client_id": DIRAC_CLIENT_ID, "audience": "Dirac server", @@ -572,7 +572,7 @@ def _get_tokens( # User gets a TokenResponse: should contain an access and a refresh tokens r = test_client.post( - "/auth/token", + "/api/auth/token", data={ "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": data["device_code"], @@ -585,7 +585,7 @@ def _get_tokens( def _get_and_check_token_response(test_client, request_data): """Get a token and check that mandatory fields are present""" # Check that token request now works - r = test_client.post("/auth/token", data=request_data) + r = test_client.post("/api/auth/token", data=request_data) assert r.status_code == 200, r.json() response_data = r.json() assert response_data["access_token"] diff --git a/tests/routers/jobs/test_sandboxes.py b/tests/routers/jobs/test_sandboxes.py index 5b0938e7c..b376a84a0 100644 --- a/tests/routers/jobs/test_sandboxes.py +++ b/tests/routers/jobs/test_sandboxes.py @@ -19,7 +19,7 @@ def test_upload_then_download( # Initiate the upload r = normal_user_client.post( - "/jobs/sandbox", + "/api/jobs/sandbox", json={ "checksum_algorithm": "sha256", "checksum": checksum, @@ -39,7 +39,7 @@ def test_upload_then_download( assert r.status_code == 204, r.text # Make sure we can download it and get the same data back - r = normal_user_client.get("/jobs/sandbox", params={"pfn": sandbox_pfn}) + r = normal_user_client.get("/api/jobs/sandbox", params={"pfn": sandbox_pfn}) assert r.status_code == 200, r.text download_info = r.json() assert download_info["expires_in"] > 5 @@ -54,7 +54,7 @@ def test_upload_then_download( # Make sure another user can't download the sandbox r = normal_user_client.get( - "/jobs/sandbox", + "/api/jobs/sandbox", params={"pfn": sandbox_pfn}, headers={"Authorization": f"Bearer {other_user_token}"}, ) @@ -68,7 +68,7 @@ def test_upload_oversized(normal_user_client: TestClient): # Initiate the upload r = normal_user_client.post( - "/jobs/sandbox", + "/api/jobs/sandbox", json={ "checksum_algorithm": "sha256", "checksum": checksum, diff --git a/tests/routers/test_config_manager.py b/tests/routers/test_config_manager.py index 59d390d99..981b975e0 100644 --- a/tests/routers/test_config_manager.py +++ b/tests/routers/test_config_manager.py @@ -4,12 +4,12 @@ def test_unauthenticated(with_app): with TestClient(with_app) as client: - response = client.get("/config/lhcb/") + response = client.get("/api/config/lhcb/") assert response.status_code == status.HTTP_403_FORBIDDEN def test_get_config(normal_user_client): - r = normal_user_client.get("/config/lhcb") + r = normal_user_client.get("/api/config/lhcb") assert r.status_code == status.HTTP_200_OK, r.json() assert r.json(), r.text @@ -17,7 +17,7 @@ def test_get_config(normal_user_client): etag = r.headers["ETag"] r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": etag, "If-Modified-Since": last_modified, @@ -29,7 +29,7 @@ def test_get_config(normal_user_client): # If only an invalid ETAG is passed, we expect a response r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": "wrongEtag", }, @@ -39,7 +39,7 @@ def test_get_config(normal_user_client): # If an past ETAG and an past timestamp as give, we expect an response r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": "pastEtag", "If-Modified-Since": "Mon, 1 Apr 2000 00:42:42 GMT", @@ -50,7 +50,7 @@ def test_get_config(normal_user_client): # If an future ETAG and an new timestamp as give, we expect 304 r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": "futureEtag", "If-Modified-Since": "Mon, 1 Apr 9999 00:42:42 GMT", @@ -61,7 +61,7 @@ def test_get_config(normal_user_client): # If an invalid ETAG and an invalid modified time, we expect a response r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": "futureEtag", "If-Modified-Since": "wrong format", @@ -72,7 +72,7 @@ def test_get_config(normal_user_client): # If the correct ETAG and a past timestamp as give, we expect 304 r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": etag, "If-Modified-Since": "Mon, 1 Apr 2000 00:42:42 GMT", @@ -83,7 +83,7 @@ def test_get_config(normal_user_client): # If the correct ETAG and a new timestamp as give, we expect 304 r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": etag, "If-Modified-Since": "Mon, 1 Apr 9999 00:42:42 GMT", @@ -94,7 +94,7 @@ def test_get_config(normal_user_client): # If the correct ETAG and an invalid modified time, we expect 304 r = normal_user_client.get( - "/config/lhcb", + "/api/config/lhcb", headers={ "If-None-Match": etag, "If-Modified-Since": "wrong format", diff --git a/tests/routers/test_generic.py b/tests/routers/test_generic.py index 71d0f578e..d34b12cd1 100644 --- a/tests/routers/test_generic.py +++ b/tests/routers/test_generic.py @@ -1,5 +1,5 @@ def test_openapi(test_client): - r = test_client.get("/openapi.json") + r = test_client.get("/api/openapi.json") assert r.status_code == 200 assert r.json() diff --git a/tests/routers/test_job_manager.py b/tests/routers/test_job_manager.py index 4bbdd2b16..6f0b41578 100644 --- a/tests/routers/test_job_manager.py +++ b/tests/routers/test_job_manager.py @@ -72,13 +72,13 @@ def test_insert_and_list_parametric_jobs(normal_user_client): job_definitions = [TEST_PARAMETRIC_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 3 # Parameters.JOB_ID is 3 submitted_job_ids = sorted([job_dict["JobID"] for job_dict in r.json()]) - r = normal_user_client.post("/jobs/search") + r = normal_user_client.post("/api/jobs/search") assert r.status_code == 200, r.json() listed_jobs = r.json() @@ -97,13 +97,13 @@ def test_insert_and_list_parametric_jobs(normal_user_client): ], ) def test_insert_and_list_bulk_jobs(job_definitions, normal_user_client): - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == len(job_definitions) submitted_job_ids = sorted([job_dict["JobID"] for job_dict in r.json()]) - r = normal_user_client.post("/jobs/search") + r = normal_user_client.post("/api/jobs/search") assert r.status_code == 200, r.json() listed_jobs = r.json() @@ -116,27 +116,27 @@ def test_insert_and_list_bulk_jobs(job_definitions, normal_user_client): def test_insert_and_search(normal_user_client): # job_definitions = [TEST_JDL%(normal_user_client.dirac_token_payload)] job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == len(job_definitions) submitted_job_ids = sorted([job_dict["JobID"] for job_dict in r.json()]) # Test /jobs/search - r = normal_user_client.post("/jobs/search") + r = normal_user_client.post("/api/jobs/search") assert r.status_code == 200, r.json() assert [x["JobID"] for x in r.json()] == submitted_job_ids assert {x["VerifiedFlag"] for x in r.json()} == {True} r = normal_user_client.post( - "/jobs/search", + "/api/jobs/search", json={"search": [{"parameter": "Status", "operator": "eq", "value": "NEW"}]}, ) assert r.status_code == 200, r.json() assert r.json() == [] r = normal_user_client.post( - "/jobs/search", + "/api/jobs/search", json={ "search": [ { @@ -151,7 +151,7 @@ def test_insert_and_search(normal_user_client): assert [x["JobID"] for x in r.json()] == submitted_job_ids r = normal_user_client.post( - "/jobs/search", json={"parameters": ["JobID", "Status"]} + "/api/jobs/search", json={"parameters": ["JobID", "Status"]} ) assert r.status_code == 200, r.json() assert r.json() == [ @@ -160,7 +160,7 @@ def test_insert_and_search(normal_user_client): # Test /jobs/summary r = normal_user_client.post( - "/jobs/summary", json={"grouping": ["Status", "OwnerGroup"]} + "/api/jobs/summary", json={"grouping": ["Status", "OwnerGroup"]} ) assert r.status_code == 200, r.json() @@ -169,7 +169,7 @@ def test_insert_and_search(normal_user_client): ] r = normal_user_client.post( - "/jobs/summary", + "/api/jobs/summary", json={ "grouping": ["Status"], "search": [ @@ -185,7 +185,7 @@ def test_insert_and_search(normal_user_client): assert r.json() == [{"Status": JobStatus.RECEIVED.value, "count": 1}] r = normal_user_client.post( - "/jobs/summary", + "/api/jobs/summary", json={ "grouping": ["Status"], "search": [{"parameter": "Status", "operator": "eq", "value": "NEW"}], @@ -200,7 +200,7 @@ def test_user_cannot_submit_parametric_jdl_greater_than_max_parametric_jobs( ): """Test that a user cannot submit a parametric JDL greater than the max parametric jobs""" job_definitions = [TEST_LARGE_PARAMETRIC_JDL] - res = normal_user_client.post("/jobs/", json=job_definitions) + res = normal_user_client.post("/api/jobs/", json=job_definitions) assert res.status_code == HTTPStatus.BAD_REQUEST, res.json() @@ -209,7 +209,7 @@ def test_user_cannot_submit_list_of_jdl_greater_than_max_number_of_jobs( ): """Test that a user cannot submit a list of JDL greater than the max number of jobs""" job_definitions = [TEST_JDL for _ in range(100)] - res = normal_user_client.post("/jobs/", json=job_definitions) + res = normal_user_client.post("/api/jobs/", json=job_definitions) assert res.status_code == HTTPStatus.BAD_REQUEST, res.json() @@ -220,12 +220,12 @@ def test_user_cannot_submit_list_of_jdl_greater_than_max_number_of_jobs( def test_user_cannot_submit_multiple_jdl_if_at_least_one_of_them_is_parametric( normal_user_client, job_definitions ): - res = normal_user_client.post("/jobs/", json=job_definitions) + res = normal_user_client.post("/api/jobs/", json=job_definitions) assert res.status_code == HTTPStatus.BAD_REQUEST, res.json() def test_user_without_the_normal_user_property_cannot_submit_job(admin_user_client): - res = admin_user_client.post("/jobs/", json=[TEST_JDL]) + res = admin_user_client.post("/api/jobs/", json=[TEST_JDL]) assert res.status_code == HTTPStatus.FORBIDDEN, res.json() @@ -233,13 +233,13 @@ def test_get_job_status(normal_user_client: TestClient): """Test that the job status is returned correctly.""" # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 # Parameters.JOB_ID is 3 job_id = r.json()[0]["JobID"] # Act - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") # Assert assert r.status_code == 200, r.json() @@ -252,7 +252,7 @@ def test_get_job_status(normal_user_client: TestClient): def test_get_status_of_nonexistent_job(normal_user_client: TestClient): """Test that the job status is returned correctly.""" # Act - r = normal_user_client.get("/jobs/1/status") + r = normal_user_client.get("/api/jobs/1/status") # Assert assert r.status_code == 404, r.json() @@ -263,7 +263,7 @@ def test_get_job_status_in_bulk(normal_user_client: TestClient): """Test that we can get the status of multiple jobs in one request""" # Arrange job_definitions = [TEST_PARAMETRIC_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 3 # Parameters.JOB_ID is 3 submitted_job_ids = sorted([job_dict["JobID"] for job_dict in r.json()]) @@ -271,7 +271,9 @@ def test_get_job_status_in_bulk(normal_user_client: TestClient): assert (isinstance(submitted_job_id, int) for submitted_job_id in submitted_job_ids) # Act - r = normal_user_client.get("/jobs/status", params={"job_ids": submitted_job_ids}) + r = normal_user_client.get( + "/api/jobs/status", params={"job_ids": submitted_job_ids} + ) # Assert print(r.json()) @@ -288,12 +290,12 @@ async def test_get_job_status_history(normal_user_client: TestClient): # Arrange job_definitions = [TEST_JDL] before = datetime.now(timezone.utc) - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) after = datetime.now(timezone.utc) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.RECEIVED.value assert r.json()[str(job_id)]["MinorStatus"] == "Job accepted" @@ -303,7 +305,7 @@ async def test_get_job_status_history(normal_user_client: TestClient): NEW_MINOR_STATUS = "JobPath" beforebis = datetime.now(timezone.utc) r = normal_user_client.put( - f"/jobs/{job_id}/status", + f"/api/jobs/{job_id}/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": NEW_STATUS, @@ -318,7 +320,7 @@ async def test_get_job_status_history(normal_user_client: TestClient): # Act r = normal_user_client.get( - f"/jobs/{job_id}/status/history", + f"/api/jobs/{job_id}/status/history", ) # Assert @@ -347,18 +349,18 @@ async def test_get_job_status_history(normal_user_client: TestClient): def test_get_job_status_history_in_bulk(normal_user_client: TestClient): # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.RECEIVED.value assert r.json()[str(job_id)]["MinorStatus"] == "Job accepted" assert r.json()[str(job_id)]["ApplicationStatus"] == "Unknown" # Act - r = normal_user_client.get("/jobs/status/history", params={"job_ids": [job_id]}) + r = normal_user_client.get("/api/jobs/status/history", params={"job_ids": [job_id]}) # Assert assert r.status_code == 200, r.json() @@ -373,11 +375,11 @@ def test_get_job_status_history_in_bulk(normal_user_client: TestClient): def test_set_job_status(normal_user_client: TestClient): # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.RECEIVED.value assert r.json()[str(job_id)]["MinorStatus"] == "Job accepted" @@ -387,7 +389,7 @@ def test_set_job_status(normal_user_client: TestClient): NEW_STATUS = JobStatus.CHECKING.value NEW_MINOR_STATUS = "JobPath" r = normal_user_client.put( - f"/jobs/{job_id}/status", + f"/api/jobs/{job_id}/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": NEW_STATUS, @@ -401,7 +403,7 @@ def test_set_job_status(normal_user_client: TestClient): assert r.json()[str(job_id)]["Status"] == NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS @@ -411,7 +413,7 @@ def test_set_job_status(normal_user_client: TestClient): def test_set_job_status_invalid_job(normal_user_client: TestClient): # Act r = normal_user_client.put( - "/jobs/1/status", + "/api/jobs/1/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": JobStatus.CHECKING.value, @@ -430,7 +432,7 @@ def test_set_job_status_offset_naive_datetime_return_bad_request( ): # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] @@ -438,7 +440,7 @@ def test_set_job_status_offset_naive_datetime_return_bad_request( # Act date = datetime.utcnow().isoformat(sep=" ") r = normal_user_client.put( - f"/jobs/{job_id}/status", + f"/api/jobs/{job_id}/status", json={ date: { "Status": JobStatus.CHECKING.value, @@ -457,11 +459,11 @@ def test_set_job_status_cannot_make_impossible_transitions( ): # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.RECEIVED.value assert r.json()[str(job_id)]["MinorStatus"] == "Job accepted" @@ -471,7 +473,7 @@ def test_set_job_status_cannot_make_impossible_transitions( NEW_STATUS = JobStatus.RUNNING.value NEW_MINOR_STATUS = "JobPath" r = normal_user_client.put( - f"/jobs/{job_id}/status", + f"/api/jobs/{job_id}/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": NEW_STATUS, @@ -485,7 +487,7 @@ def test_set_job_status_cannot_make_impossible_transitions( assert r.json()[str(job_id)]["Status"] != NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] != NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS @@ -495,11 +497,11 @@ def test_set_job_status_cannot_make_impossible_transitions( def test_set_job_status_force(normal_user_client: TestClient): # Arrange job_definitions = [TEST_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 1 job_id = r.json()[0]["JobID"] - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.RECEIVED.value assert r.json()[str(job_id)]["MinorStatus"] == "Job accepted" @@ -509,7 +511,7 @@ def test_set_job_status_force(normal_user_client: TestClient): NEW_STATUS = JobStatus.RUNNING.value NEW_MINOR_STATUS = "JobPath" r = normal_user_client.put( - f"/jobs/{job_id}/status", + f"/api/jobs/{job_id}/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": NEW_STATUS, @@ -524,7 +526,7 @@ def test_set_job_status_force(normal_user_client: TestClient): assert r.json()[str(job_id)]["Status"] == NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS @@ -534,13 +536,13 @@ def test_set_job_status_force(normal_user_client: TestClient): def test_set_job_status_bulk(normal_user_client: TestClient): # Arrange job_definitions = [TEST_PARAMETRIC_JDL] - r = normal_user_client.post("/jobs/", json=job_definitions) + r = normal_user_client.post("/api/jobs/", json=job_definitions) assert r.status_code == 200, r.json() assert len(r.json()) == 3 job_ids = sorted([job_dict["JobID"] for job_dict in r.json()]) for job_id in job_ids: - r = normal_user_client.get(f"/jobs/{job_id}/status") + r = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r.status_code == 200, r.json() assert r.json()[str(job_id)]["Status"] == JobStatus.SUBMITTING.value assert r.json()[str(job_id)]["MinorStatus"] == "Bulk transaction confirmation" @@ -549,7 +551,7 @@ def test_set_job_status_bulk(normal_user_client: TestClient): NEW_STATUS = JobStatus.CHECKING.value NEW_MINOR_STATUS = "JobPath" r = normal_user_client.put( - "/jobs/status", + "/api/jobs/status", json={ job_id: { datetime.now(timezone.utc).isoformat(): { @@ -567,7 +569,7 @@ def test_set_job_status_bulk(normal_user_client: TestClient): assert r.json()[str(job_id)]["Status"] == NEW_STATUS assert r.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS - r_get = normal_user_client.get(f"/jobs/{job_id}/status") + r_get = normal_user_client.get(f"/api/jobs/{job_id}/status") assert r_get.status_code == 200, r_get.json() assert r_get.json()[str(job_id)]["Status"] == NEW_STATUS assert r_get.json()[str(job_id)]["MinorStatus"] == NEW_MINOR_STATUS @@ -577,7 +579,7 @@ def test_set_job_status_bulk(normal_user_client: TestClient): def test_set_job_status_with_invalid_job_id(normal_user_client: TestClient): # Act r = normal_user_client.put( - "/jobs/999999999/status", + "/api/jobs/999999999/status", json={ datetime.now(tz=timezone.utc).isoformat(): { "Status": JobStatus.CHECKING.value,