about summary refs log tree commit diff
path: root/tests/test_gn_auth_smoke.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_gn_auth_smoke.py')
-rw-r--r--tests/test_gn_auth_smoke.py216
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