From 0eb06200d7c4e24e63bf730be29609bd4c7269c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 00:00:00 +0000 Subject: test(gn3/case-attr): level 5 — data-curator approve/reject Add fixtures and tests for approve/reject access control using a systemwide-data-curator token. conftest.py: - data_curator_oauth2_credentials: reads role "systemwide-data-curator" from GN_TEST_USERS_FILE; skips if absent. - data_curator_token: requests scope "profile group resource role user". tests/test_gn3_case_attr_access.py: - test_data_curator_can_approve_returns_200_or_201: parametrized over flat (/case-attribute/…) and v1 endpoints; asserts 200 or 201 (either proves the auth check passed). - test_data_curator_can_reject_returns_200_or_201: same for reject paths. Reviewed-By: Frederick M. Muriithi --- conftest.py | 51 +++++++++++++++++++++++++++++++++++ tests/test_gn3_case_attr_access.py | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/conftest.py b/conftest.py index 250f72d..26931fc 100644 --- a/conftest.py +++ b/conftest.py @@ -204,3 +204,54 @@ def basic_access_token(gn_auth_url, basic_oauth2_credentials, http): data = resp.json() assert "access_token" in data return data["access_token"] + + +@pytest.fixture(scope="session") +def data_curator_oauth2_credentials(): + """Return (email, password, client_id, client_secret) for the systemwide-data-curator user. + + Requires GN_TEST_USERS_FILE + GN_TEST_CLIENT_FILE (set by the CI + auth-flow job). Skips if not available or if the role is absent. + """ + users_file = os.environ.get("GN_TEST_USERS_FILE") + client_file = os.environ.get("GN_TEST_CLIENT_FILE") + if not (users_file and client_file): + pytest.skip( + "Set GN_TEST_USERS_FILE and GN_TEST_CLIENT_FILE to run " + "auth-flow tests that require the systemwide-data-curator user." + ) + users_data = _read_json_file(users_file) + client_data = _read_json_file(client_file) + curator = _user_by_role(users_data, "systemwide-data-curator") + if curator is None: + pytest.fail( + f"No user with role='systemwide-data-curator' found in {users_file}" + ) + return ( + curator["email"], + curator["password"], + client_data["client"]["client_id"], + client_data["client"]["client_secret"], + ) + + +@pytest.fixture(scope="session") +def data_curator_token(gn_auth_url, data_curator_oauth2_credentials, http): + """Bearer token for the systemwide-data-curator test user.""" + email, password, client_id, client_secret = data_curator_oauth2_credentials + resp = http.post( + f"{gn_auth_url}/auth/token", + json={ + "grant_type": "password", + "username": email, + "password": password, + "scope": "profile group resource role user", + "client_id": client_id, + "client_secret": client_secret, + }, + timeout=30, + ) + assert resp.status_code == 200, f"Data-curator token request failed: {resp.text}" + data = resp.json() + assert "access_token" in data + return data["access_token"] diff --git a/tests/test_gn3_case_attr_access.py b/tests/test_gn3_case_attr_access.py index 92d97e2..0e7706a 100644 --- a/tests/test_gn3_case_attr_access.py +++ b/tests/test_gn3_case_attr_access.py @@ -248,3 +248,57 @@ def test_resource_owner_can_edit_returns_201( f"If 401: likely the __population_privileges__ bug (resource_privs always empty). " f"Body: {resp.text[:300]}" ) + + +# --------------------------------------------------------------------------- +# Level 5: approve/reject with systemwide-data-curator privilege +# --------------------------------------------------------------------------- + +@pytest.mark.case_attr_access +@pytest.mark.auth_flow +@pytest.mark.parametrize("path", [ + f"/case-attribute/{_INBREDSET_ID}/approve/{_CHANGE_ID}", + f"/v1/species/{_SPECIES_ID}/populations/{_INBREDSET_ID}/case-attributes/diffs/{_CHANGE_ID}/approve", +]) +def test_data_curator_can_approve_returns_200_or_201( + gn3_url, http, data_curator_token, path): + """A systemwide-data-curator must be allowed to approve diffs (200/201). + + The endpoint returns 201 when the change is successfully applied and 200 + when there is no matching pending diff. Either response proves the auth + check passed. + """ + resp = http.post( + f"{gn3_url}{path}", + headers={"Authorization": f"Bearer {data_curator_token}"}, + timeout=30, + ) + assert resp.status_code in (200, 201), ( + f"Expected 200 or 201 for systemwide-data-curator POST {path!r}, " + f"got {resp.status_code}. Body: {resp.text[:300]}" + ) + + +@pytest.mark.case_attr_access +@pytest.mark.auth_flow +@pytest.mark.parametrize("path", [ + f"/case-attribute/{_INBREDSET_ID}/reject/{_CHANGE_ID}", + f"/v1/species/{_SPECIES_ID}/populations/{_INBREDSET_ID}/case-attributes/diffs/{_CHANGE_ID}/reject", +]) +def test_data_curator_can_reject_returns_200_or_201( + gn3_url, http, data_curator_token, path): + """A systemwide-data-curator must be allowed to reject diffs (200/201). + + The endpoint returns 201 when the change is successfully rejected and 200 + when there is no matching pending diff. Either response proves the auth + check passed. + """ + resp = http.post( + f"{gn3_url}{path}", + headers={"Authorization": f"Bearer {data_curator_token}"}, + timeout=30, + ) + assert resp.status_code in (200, 201), ( + f"Expected 200 or 201 for systemwide-data-curator POST {path!r}, " + f"got {resp.status_code}. Body: {resp.text[:300]}" + ) -- cgit 1.4.1