diff --git a/tests/test_gn_auth_auth_flow.py b/tests/test_gn_auth_auth_flow.py
index cb164cd..98d792a 100644
--- a/tests/test_gn_auth_auth_flow.py
+++ b/tests/test_gn_auth_auth_flow.py
@@ -191,3 +191,111 @@ class TestUserProfileWithoutToken:
f"Expected 401 from /auth/user/ with garbage token, "
f"got {resp.status_code}: {resp.text}"
)
+
+
+# ---------------------------------------------------------------------------
+# POST /auth/user/masquerade/ — role-based privilege check
+#
+# The masquerade endpoint requires the "system:user:masquerade" privilege
+# which only system-administrators hold. Both admin and basic users can
+# obtain a token with "masquerade" scope (the test client supports it), but
+# gn-auth's can_masquerade decorator checks the user's roles and raises
+# ForbiddenAccess (→ 403) for users without the privilege.
+# ---------------------------------------------------------------------------
+
+@pytest.fixture(scope="session")
+def admin_masquerade_token(gn_auth_url, http, oauth2_credentials):
+ """Admin Bearer token with masquerade scope."""
+ 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 masquerade",
+ "client_id": client_id,
+ "client_secret": client_secret,
+ },
+ timeout=30,
+ )
+ assert resp.status_code == 200, f"Admin masquerade token request failed: {resp.text}"
+ return resp.json()["access_token"]
+
+
+@pytest.fixture(scope="session")
+def basic_masquerade_token(gn_auth_url, http, basic_oauth2_credentials):
+ """Unprivileged user Bearer token with masquerade scope."""
+ email, password, client_id, client_secret = basic_oauth2_credentials
+ resp = http.post(
+ f"{gn_auth_url}/auth/token",
+ json={
+ "grant_type": "password",
+ "username": email,
+ "password": password,
+ "scope": "profile group resource user masquerade",
+ "client_id": client_id,
+ "client_secret": client_secret,
+ },
+ timeout=30,
+ )
+ assert resp.status_code == 200, f"Basic masquerade token request failed: {resp.text}"
+ return resp.json()["access_token"]
+
+
+@pytest.fixture(scope="session")
+def basic_user_id(gn_auth_url, http, basic_access_token):
+ """User ID of the unprivileged test user, fetched via GET /auth/user/."""
+ resp = http.get(
+ f"{gn_auth_url}/auth/user/",
+ headers={"Authorization": f"Bearer {basic_access_token}"},
+ timeout=30,
+ )
+ assert resp.status_code == 200, f"Could not fetch basic user profile: {resp.text}"
+ return resp.json()["user_id"]
+
+
+class TestMasqueradePrivilege:
+ """POST /auth/user/masquerade/ enforces the system:user:masquerade privilege."""
+
+ def test_basic_user_cannot_masquerade_returns_403(
+ self, gn_auth_url, http, basic_masquerade_token, basic_user_id,
+ access_token):
+ # Basic user tries to masquerade as the admin; the admin's user_id is
+ # obtained via the profile endpoint.
+ admin_profile = http.get(
+ f"{gn_auth_url}/auth/user/",
+ headers={"Authorization": f"Bearer {access_token}"},
+ timeout=30,
+ )
+ assert admin_profile.status_code == 200
+ admin_id = admin_profile.json()["user_id"]
+
+ resp = http.post(
+ f"{gn_auth_url}/auth/user/masquerade/",
+ json={"masquerade_as": admin_id},
+ headers={"Authorization": f"Bearer {basic_masquerade_token}"},
+ timeout=30,
+ )
+ assert resp.status_code == 403, (
+ f"Expected 403 when unprivileged user attempts masquerade, "
+ f"got {resp.status_code}: {resp.text}"
+ )
+
+ def test_admin_can_masquerade_as_basic_user(
+ self, gn_auth_url, http, admin_masquerade_token, basic_user_id):
+ resp = http.post(
+ f"{gn_auth_url}/auth/user/masquerade/",
+ json={"masquerade_as": basic_user_id},
+ headers={"Authorization": f"Bearer {admin_masquerade_token}"},
+ timeout=30,
+ )
+ assert resp.status_code == 200, (
+ f"Expected 200 when admin masquerades as basic user, "
+ f"got {resp.status_code}: {resp.text}"
+ )
+ data = resp.json()
+ assert "masquerade_as" in data, f"Missing 'masquerade_as' in response: {data}"
+ assert data["masquerade_as"]["user"]["user_id"] == basic_user_id, (
+ "Masquerade response user_id does not match target user"
+ )
|