about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaude Sonnet 4.62026-08-27 15:48:04 +0000
committerFrederick Muriuki Muriithi2026-08-27 10:53:07 -0500
commit4a8ffa773ad487d1e0ad96ded074f4efcf8a438c (patch)
tree19f74d758691958aa03eb5a3227b0770c7ccd2a5
parentb128915f0e9258099f8881382dfb1307e8740d53 (diff)
downloadgn-auth-4a8ffa773ad487d1e0ad96ded074f4efcf8a438c.tar.gz
tests(admin): add TDD tests for POST /auth/user/<uid>/roles/revoke
Four tests covering the revoke endpoint (401/403/200/DB removal).
Success tests pre-assign the role via _assign_target_role and use
try/finally with _cleanup_target_role to restore DB state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--tests/unit/auth/test_admin_user_roles.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/tests/unit/auth/test_admin_user_roles.py b/tests/unit/auth/test_admin_user_roles.py
index 6c153b5..7ce0b1f 100644
--- a/tests/unit/auth/test_admin_user_roles.py
+++ b/tests/unit/auth/test_admin_user_roles.py
@@ -110,6 +110,34 @@ def test_assign_role_admin_returns_200(fxtr_app, mocker, fxtr_oauth2_clients):
         _revoke_assigned_role(conn)
 
 
+def _assign_target_role(conn):
+    """Pre-assign system-administrator to _TARGET_USER on SYSTEM_RESOURCE.
+
+    Required setup for revoke tests: the endpoint can only revoke what exists.
+    """
+    with db.cursor(conn) as cursor:
+        assign_user_role_by_name(
+            cursor, _TARGET_USER, SYSTEM_RESOURCE.resource_id,
+            _ASSIGN_BODY["role_name"])
+
+
+def _cleanup_target_role(conn):
+    """Remove _TARGET_USER's system-administrator row if still present.
+
+    No-op when the revoke endpoint already deleted it; guards against
+    test failures that leave the DB dirty.
+    """
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "DELETE FROM user_roles "
+            "WHERE user_id=? "
+            "AND role_id=(SELECT role_id FROM roles WHERE role_name=?) "
+            "AND resource_id=?",
+            (str(_TARGET_USER.user_id),
+             _ASSIGN_BODY["role_name"],
+             _ASSIGN_BODY["resource_id"]))
+
+
 @pytest.mark.unit_test
 def test_assign_role_persists_to_db(fxtr_app, mocker, fxtr_oauth2_clients):
     """
@@ -138,3 +166,97 @@ def test_assign_role_persists_to_db(fxtr_app, mocker, fxtr_oauth2_clients):
             assert cursor.fetchone()["cnt"] == 1
     finally:
         _revoke_assigned_role(conn)
+
+
+# ---------------------------------------------------------------------------
+# HTTP endpoint tests: POST /auth/user/<uid>/roles/revoke
+# ---------------------------------------------------------------------------
+
+@pytest.mark.unit_test
+def test_revoke_role_no_token_returns_401(fxtr_app):
+    """
+    GIVEN: no Authorization header
+    WHEN: POST /auth/user/<uid>/roles/revoke
+    THEN: 401 is returned
+    """
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+            json=_ASSIGN_BODY)
+    assert res.status_code == 401
+
+
+@pytest.mark.unit_test
+def test_revoke_role_non_admin_returns_403(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid token belonging to a non-admin user
+    WHEN: POST /auth/user/<uid>/roles/revoke
+    THEN: 403 is returned
+    """
+    _conn, clients = fxtr_oauth2_clients
+    user = conftest.TEST_USERS[3]  # unaff@iliated.user — no privileges
+    mocker.patch(
+        "gn_auth.auth.authorisation.users.views.require_oauth.acquire",
+        conftest.get_tokeniser(
+            user,
+            tuple(c for c in clients if c.user == user)[0]))
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+            json=_ASSIGN_BODY,
+            headers={"Authorization": "Bearer some-mocked-token"})
+    assert res.status_code == 403
+
+
+@pytest.mark.unit_test
+def test_revoke_role_admin_returns_200(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid token with resource:user:assign-role and the target user
+           holds the role on the resource
+    WHEN: POST /auth/user/<uid>/roles/revoke
+    THEN: 200 is returned
+    """
+    conn, clients = fxtr_oauth2_clients
+    _setup_admin_mock(conn, clients, mocker)
+    _assign_target_role(conn)
+    try:
+        with fxtr_app.test_client() as http:
+            res = http.post(
+                f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+                json=_ASSIGN_BODY,
+                headers={"Authorization": "Bearer some-mocked-token"})
+        assert res.status_code == 200
+    finally:
+        _cleanup_target_role(conn)
+
+
+@pytest.mark.unit_test
+def test_revoke_role_removes_from_db(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid token with resource:user:assign-role and the target user
+           holds the role on the resource
+    WHEN: POST /auth/user/<uid>/roles/revoke
+    THEN: the user_roles row is absent from the DB
+    """
+    conn, clients = fxtr_oauth2_clients
+    _setup_admin_mock(conn, clients, mocker)
+    _assign_target_role(conn)
+    try:
+        with fxtr_app.test_client() as http:
+            http.post(
+                f"/auth/user/{_TARGET_USER.user_id}/roles/revoke",
+                json=_ASSIGN_BODY,
+                headers={"Authorization": "Bearer some-mocked-token"})
+        with db.cursor(conn) as cursor:
+            cursor.execute(
+                "SELECT COUNT(*) AS cnt FROM user_roles "
+                "INNER JOIN roles ON user_roles.role_id=roles.role_id "
+                "WHERE user_roles.user_id=? "
+                "AND roles.role_name=? "
+                "AND user_roles.resource_id=?",
+                (str(_TARGET_USER.user_id),
+                 _ASSIGN_BODY["role_name"],
+                 _ASSIGN_BODY["resource_id"]))
+            assert cursor.fetchone()["cnt"] == 0
+    finally:
+        _cleanup_target_role(conn)