about summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2026-08-28 16:45:55 +0000
committerFrederick Muriuki Muriithi2026-08-28 14:59:06 -0500
commit7656a8f22a53aa029dd5c6b832ed5b6975fc5485 (patch)
treeac4edb04fa4caff3fb929ab47473696037e32f56 /tests
parent5eea39e00246ccb7da60b074786b93c4297b0a1e (diff)
downloadgn-integration-tests-7656a8f22a53aa029dd5c6b832ed5b6975fc5485.tar.gz
test(gn3/case-attr): level 4 bug test — resource-owner edit returns 201
Adds provisioning fixtures and a parametrized test that assigns
resource-owner role to a freshly created test user on the BXD
population resource, then expects 201 from both the flat and v1 edit
endpoints.

The test is RED with current code because __population_privileges__
calls GET /auth/resource/<id>/roles without a Bearer token, so
resource_privs is always empty and can_edit's resource_spec branch
is never satisfied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_gn3_case_attr_access.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/tests/test_gn3_case_attr_access.py b/tests/test_gn3_case_attr_access.py
index dcce6ea..7cb234f 100644
--- a/tests/test_gn3_case_attr_access.py
+++ b/tests/test_gn3_case_attr_access.py
@@ -4,6 +4,7 @@ Case-attribute names are publicly accessible (no token required).
 All write endpoints require a valid token, and a token with appropriate
 edit privileges.
 """
+import uuid
 import pytest
 
 pytestmark = pytest.mark.gn3
@@ -87,3 +88,163 @@ def test_no_privilege_reject_returns_401(gn3_url, http, basic_access_token, path
         f"Expected 401 for unprivileged POST {path!r}, "
         f"got {resp.status_code}. Body: {resp.text[:200]}"
     )
+
+
+# ---------------------------------------------------------------------------
+# Provisioning helpers for Levels 4–5
+# ---------------------------------------------------------------------------
+
+@pytest.fixture(scope="session")
+def _admin_full_scope_token(gn_auth_url, http, oauth2_credentials):
+    """Admin Bearer token with user+role scope for provisioning test users."""
+    email, password, client_id, client_secret = oauth2_credentials
+    resp = http.post(
+        f"{gn_auth_url}/auth/token",
+        json={
+            "grant_type": "password",
+            "username": email,
+            "password": password,
+            "scope": "profile group resource user role",
+            "client_id": client_id,
+            "client_secret": client_secret,
+        },
+        timeout=30,
+    )
+    assert resp.status_code == 200, (
+        f"Admin full-scope token request failed: {resp.text}"
+    )
+    return resp.json()["access_token"]
+
+
+@pytest.fixture(scope="session")
+def bxd_resource_id(gn_auth_url, http):
+    """Resource UUID for the BXD population (SpeciesId=1, InbredSetId=1).
+
+    Skips the test suite if the population is not registered in gn-auth.
+    """
+    resp = http.get(
+        f"{gn_auth_url}/auth/resource/populations/resource-id"
+        f"/{_SPECIES_ID}/{_INBREDSET_ID}",
+        timeout=30,
+    )
+    if resp.status_code == 404:
+        pytest.skip(
+            f"BXD population (species={_SPECIES_ID}, pop={_INBREDSET_ID}) "
+            "is not registered as a resource in gn-auth — skipping Level 4+ tests."
+        )
+    assert resp.status_code == 200, (
+        f"Unexpected {resp.status_code} fetching BXD resource-id: {resp.text}"
+    )
+    return resp.json()["resource-id"]
+
+
+@pytest.fixture
+def provisioned_token(gn_auth_url, http, _admin_full_scope_token, oauth2_credentials):
+    """Fixture factory: create a test user, assign role on a resource, yield token, clean up.
+
+    Usage inside a test::
+
+        def test_something(gn3_url, http, provisioned_token, bxd_resource_id):
+            token = provisioned_token("resource-owner", bxd_resource_id)
+            resp = http.post(url, headers={"Authorization": f"Bearer {token}"})
+    """
+    *_, client_id, client_secret = oauth2_credentials
+    admin_hdrs = {"Authorization": f"Bearer {_admin_full_scope_token}"}
+    created: list = []  # [(user_id, role_name, resource_id), ...]
+
+    def _make(role_name: str, resource_id: str) -> str:
+        unique = str(uuid.uuid4())[:8]
+        email = f"test-{role_name}-{unique}@regression.genenetwork.org"
+        password = "GnTest1234!"
+
+        create_resp = http.post(
+            f"{gn_auth_url}/auth/user/create",
+            json={"email": email, "name": f"Test {role_name}", "password": password},
+            headers=admin_hdrs,
+            timeout=30,
+        )
+        assert create_resp.status_code == 201, (
+            f"Failed to create test user for role {role_name!r}: "
+            f"{create_resp.status_code} {create_resp.text}"
+        )
+        user_id = create_resp.json()["user_id"]
+        created.append((user_id, role_name, resource_id))
+
+        assign_resp = http.post(
+            f"{gn_auth_url}/auth/system/administration/resources/{resource_id}/assign-owner",
+            json={"user_id": user_id},
+            headers=admin_hdrs,
+            timeout=30,
+        )
+        assert assign_resp.status_code == 200, (
+            f"Failed to assign resource-owner to user {user_id} on {resource_id}: "
+            f"{assign_resp.status_code} {assign_resp.text}"
+        )
+
+        token_resp = http.post(
+            f"{gn_auth_url}/auth/token",
+            json={
+                "grant_type": "password",
+                "username": email,
+                "password": password,
+                "scope": "profile group resource",
+                "client_id": client_id,
+                "client_secret": client_secret,
+            },
+            timeout=30,
+        )
+        assert token_resp.status_code == 200, (
+            f"Failed to get token for test user: {token_resp.status_code} {token_resp.text}"
+        )
+        return token_resp.json()["access_token"]
+
+    yield _make
+
+    for user_id, _role_name, resource_id in created:
+        http.post(
+            f"{gn_auth_url}/auth/system/administration/resources/{resource_id}/revoke-owner",
+            json={"user_id": user_id},
+            headers=admin_hdrs,
+            timeout=30,
+        )
+        http.post(
+            f"{gn_auth_url}/auth/user/delete",
+            json={"user_ids": [user_id]},
+            headers=admin_hdrs,
+            timeout=30,
+        )
+
+
+# ---------------------------------------------------------------------------
+# Level 4: edit with resource-owner privilege
+# (documents the __population_privileges__ bug)
+# ---------------------------------------------------------------------------
+
+@pytest.mark.case_attr_access
+@pytest.mark.auth_flow
+@pytest.mark.parametrize("path", [
+    f"/case-attribute/{_INBREDSET_ID}/edit",
+    f"/v1/species/{_SPECIES_ID}/populations/{_INBREDSET_ID}/case-attributes/edit",
+])
+def test_resource_owner_can_edit_returns_201(
+        gn3_url, http, provisioned_token, bxd_resource_id, path):
+    """A resource-owner on the BXD population must be allowed to queue edits (201).
+
+    BUG: Currently returns 401 because __population_privileges__ calls
+    GET /auth/resource/<id>/roles without a Bearer token, so resource_privs is
+    always empty and the resource_spec branch of can_edit is never reachable.
+    This test is RED until that bug is fixed.
+    """
+    token = provisioned_token("resource-owner", bxd_resource_id)
+    resp = http.post(
+        f"{gn3_url}{path}",
+        json={"edit-data": []},
+        headers={"Authorization": f"Bearer {token}"},
+        timeout=30,
+    )
+    assert resp.status_code == 201, (
+        f"Expected 201 for resource-owner POST {path!r}, "
+        f"got {resp.status_code}. "
+        f"If 401: likely the __population_privileges__ bug (resource_privs always empty). "
+        f"Body: {resp.text[:300]}"
+    )