diff options
| author | Frederick Muriuki Muriithi | 2026-05-27 13:49:30 -0500 |
|---|---|---|
| committer | Frederick Muriuki Muriithi | 2026-05-27 13:49:30 -0500 |
| commit | 1655d8e45bbf8b0524af6529c142ada98ddace54 (patch) | |
| tree | 3016c16af0bb4f2e57fffb22264e22ac6b1fc6bb /tests/test_gn_auth_smoke.py | |
| download | gn-integration-tests-1655d8e45bbf8b0524af6529c142ada98ddace54.tar.gz | |
Initialise with smoke tests for various GeneNetwork services.
The code in this commit was written by claude code, and is yet to be reviewed. Expect changes once the code has been reviewed.
Diffstat (limited to 'tests/test_gn_auth_smoke.py')
| -rw-r--r-- | tests/test_gn_auth_smoke.py | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/tests/test_gn_auth_smoke.py b/tests/test_gn_auth_smoke.py new file mode 100644 index 0000000..7dd9645 --- /dev/null +++ b/tests/test_gn_auth_smoke.py @@ -0,0 +1,216 @@ +""" +Smoke tests for the gn-auth service. + +Run with no credentials against CD (or production): + + pytest -m "gn_auth and smoke" + +These tests verify the public-facing contract of the three authorization +endpoints called at request time by gn2 and gn3, plus the JWKS endpoint +used for JWT validation. +""" + +import pytest + + +pytestmark = [pytest.mark.gn_auth, pytest.mark.smoke] + +# --------------------------------------------------------------------------- +# Known public trait used in authorisation tests. +# HC_M2_0606_P (Hippocampus Consortium M430v2) is a public dataset with +# extensive test coverage across gn2/gn3. +# --------------------------------------------------------------------------- +_PUBLIC_TRAIT = "HC_M2_0606_P::1435395_s_at" +_PUBLIC_PHENOTYPE_TRAIT = "BXDPublish::10710" +_NONEXISTENT_TRAIT = "NONEXISTENT_DATASET_XYZ::00000" + + +# --------------------------------------------------------------------------- +# GET /auth/public-jwks +# --------------------------------------------------------------------------- + +class TestPublicJwks: + def test_returns_200(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/public-jwks", timeout=30) + assert resp.status_code == 200 + + def test_response_has_jwks_key(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/public-jwks", timeout=30) + data = resp.json() + assert "jwks" in data, f"Missing 'jwks' key in response: {data}" + + def test_at_least_one_rsa_key(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/public-jwks", timeout=30) + jwks = resp.json().get("jwks", []) + assert len(jwks) >= 1, "Expected at least one key in JWKS" + key = jwks[-1] # newest key + assert key.get("kty") == "RSA" + # "alg" is optional per RFC 7517 — gn-auth omits it + assert "n" in key and "e" in key, "RSA key missing modulus or exponent" + assert "kid" in key, "RSA key missing 'kid'" + + +# --------------------------------------------------------------------------- +# GET /auth/system/roles (no token → public-view only) +# --------------------------------------------------------------------------- + +class TestSystemRolesUnauthenticated: + def test_returns_200_without_token(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/system/roles", timeout=30) + assert resp.status_code == 200 + + def test_response_is_list(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/system/roles", timeout=30) + data = resp.json() + assert isinstance(data, list), f"Expected list, got: {type(data)}" + + def test_contains_public_view_role(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/system/roles", timeout=30) + roles = resp.json() + names = [r.get("role_name") for r in roles] + assert "public-view" in names, ( + f"Expected 'public-view' role when called without auth token; got: {names}" + ) + + def test_public_view_role_has_privileges(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/system/roles", timeout=30) + public_view = next( + (r for r in resp.json() if r.get("role_name") == "public-view"), None + ) + assert public_view is not None + assert isinstance(public_view.get("privileges"), list) + assert len(public_view["privileges"]) >= 1 + + def test_role_schema(self, gn_auth_url, http): + resp = http.get(f"{gn_auth_url}/auth/system/roles", timeout=30) + for role in resp.json(): + assert "role_id" in role, f"Role missing 'role_id': {role}" + assert "role_name" in role, f"Role missing 'role_name': {role}" + assert "privileges" in role, f"Role missing 'privileges': {role}" + + +# --------------------------------------------------------------------------- +# POST /auth/data/authorisation +# --------------------------------------------------------------------------- + +class TestDataAuthorisation: + def test_known_public_probeset_trait_returns_200(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_TRAIT]}, + timeout=30, + ) + assert resp.status_code == 200, ( + f"Expected 200 for known public trait '{_PUBLIC_TRAIT}', " + f"got {resp.status_code}: {resp.text}" + ) + + def test_known_public_trait_response_structure(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_TRAIT]}, + timeout=30, + ) + data = resp.json() + assert "authorisation" in data, f"Missing 'authorisation' key: {data}" + assert isinstance(data["authorisation"], list) + assert len(data["authorisation"]) >= 1 + + def test_known_public_trait_has_view_privilege(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_TRAIT]}, + timeout=30, + ) + resources = resp.json()["authorisation"] + all_privileges = [p for r in resources for p in r.get("privileges", [])] + assert any("view" in p for p in all_privileges), ( + f"Expected a view privilege for public trait '{_PUBLIC_TRAIT}'; " + f"got privileges: {all_privileges}" + ) + + def test_resource_entry_schema(self, gn_auth_url, http): + # Actual response schema (verified against CD 2026-05-27): + # {"resource_id": str, "resource_data": [...], "privileges": [...]} + # Note: "public" and "resource_name" fields are NOT present in the + # deployed response despite appearing in source comments. + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_TRAIT]}, + timeout=30, + ) + for resource in resp.json()["authorisation"]: + assert "resource_id" in resource, f"Missing resource_id: {resource}" + assert "resource_data" in resource, f"Missing resource_data: {resource}" + assert "privileges" in resource, f"Missing privileges: {resource}" + assert isinstance(resource["resource_data"], list) + assert isinstance(resource["privileges"], list) + + def test_known_public_phenotype_trait(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_PHENOTYPE_TRAIT]}, + timeout=30, + ) + assert resp.status_code == 200, ( + f"Expected 200 for '{_PUBLIC_PHENOTYPE_TRAIT}', " + f"got {resp.status_code}: {resp.text}" + ) + + def test_nonexistent_trait_returns_404(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_NONEXISTENT_TRAIT]}, + timeout=30, + ) + assert resp.status_code == 404, ( + f"Expected 404 for nonexistent trait '{_NONEXISTENT_TRAIT}', " + f"got {resp.status_code}: {resp.text}" + ) + + def test_empty_traits_list_is_handled(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": []}, + timeout=30, + ) + # Must not be a 500; 200 with empty result or 400/404 are all acceptable. + assert resp.status_code != 500, ( + f"Empty traits list caused 500: {resp.text}" + ) + + def test_multiple_traits_in_one_request(self, gn_auth_url, http): + resp = http.post( + f"{gn_auth_url}/auth/data/authorisation", + json={"traits": [_PUBLIC_TRAIT, _PUBLIC_PHENOTYPE_TRAIT]}, + timeout=30, + ) + assert resp.status_code == 200 + data = resp.json() + assert "authorisation" in data + + +# --------------------------------------------------------------------------- +# GET /auth/system/roles (with valid token — Phase 2) +# --------------------------------------------------------------------------- + +@pytest.mark.auth_flow +class TestSystemRolesAuthenticated: + def test_returns_200_with_token(self, gn_auth_url, http, access_token): + resp = http.get( + f"{gn_auth_url}/auth/system/roles", + headers={"Authorization": f"Bearer {access_token}"}, + timeout=30, + ) + assert resp.status_code == 200 + + def test_authenticated_user_has_more_than_public_view(self, gn_auth_url, http, access_token): + resp = http.get( + f"{gn_auth_url}/auth/system/roles", + headers={"Authorization": f"Bearer {access_token}"}, + timeout=30, + ) + roles = resp.json() + names = [r.get("role_name") for r in roles] + # A logged-in user should see public-view plus at least their own roles. + assert "public-view" in names |
