Files
resolutionflow/backend/tests/test_admin.py
chihlasm 71ba0b95a5 fix: high-severity security hardening (Phase B permissions audit)
Phase B addresses 7 high-severity gaps from the permissions audit:

- B1: Enforce tree access check on session start via can_access_tree
- B2: Replace all inline permission helpers with centralized permissions.py
- B3: Fix require_engineer_or_admin to check is_team_admin before role
- B4: Add is_active field on User with enforcement in get_current_active_user
- B5: Add admin user management endpoints (list, get, role, team-admin, deactivate, activate)
- B6: Add rate limiting on auth/invite endpoints via slowapi (disabled in DEBUG)
- B7: Implement refresh token rotation with JTI-based revocation and meaningful logout

Also reduces access token TTL from 15 to 5 minutes and updates CLAUDE.md
with SaaS/MSP context for future planning sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:44:05 -05:00

130 lines
4.5 KiB
Python

"""Integration tests for admin user management endpoints."""
import pytest
from httpx import AsyncClient
class TestAdminEndpoints:
"""Test suite for admin user management endpoints."""
@pytest.mark.asyncio
async def test_list_users_as_admin(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test listing users as a super admin."""
response = await client.get(
"/api/v1/admin/users", headers=admin_auth_headers
)
assert response.status_code == 200
users = response.json()
assert len(users) >= 2 # admin + test_user
@pytest.mark.asyncio
async def test_list_users_as_non_admin(
self, client: AsyncClient, auth_headers: dict
):
"""Test that non-admin users cannot list users."""
response = await client.get(
"/api/v1/admin/users", headers=auth_headers
)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_get_user_as_admin(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test getting user details as admin."""
user_id = test_user["user_data"]["id"]
response = await client.get(
f"/api/v1/admin/users/{user_id}", headers=admin_auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["email"] == test_user["email"]
@pytest.mark.asyncio
async def test_change_user_role(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test changing a user's role to viewer."""
user_id = test_user["user_data"]["id"]
response = await client.put(
f"/api/v1/admin/users/{user_id}/role",
json={"role": "viewer"},
headers=admin_auth_headers
)
assert response.status_code == 200
assert response.json()["role"] == "viewer"
@pytest.mark.asyncio
async def test_change_role_invalid(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test that invalid role values are rejected."""
user_id = test_user["user_data"]["id"]
response = await client.put(
f"/api/v1/admin/users/{user_id}/role",
json={"role": "admin"},
headers=admin_auth_headers
)
assert response.status_code == 422
@pytest.mark.asyncio
async def test_cannot_change_own_role(
self, client: AsyncClient, admin_auth_headers: dict, test_admin: dict
):
"""Test that admin cannot change their own role."""
admin_id = test_admin["user_data"]["id"]
response = await client.put(
f"/api/v1/admin/users/{admin_id}/role",
json={"role": "viewer"},
headers=admin_auth_headers
)
assert response.status_code == 400
assert "own role" in response.json()["detail"].lower()
@pytest.mark.asyncio
async def test_deactivate_user(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test deactivating a user."""
user_id = test_user["user_data"]["id"]
response = await client.put(
f"/api/v1/admin/users/{user_id}/deactivate",
headers=admin_auth_headers
)
assert response.status_code == 200
assert response.json()["is_active"] is False
@pytest.mark.asyncio
async def test_activate_user(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict
):
"""Test reactivating a user."""
user_id = test_user["user_data"]["id"]
# Deactivate first
await client.put(
f"/api/v1/admin/users/{user_id}/deactivate",
headers=admin_auth_headers
)
# Then reactivate
response = await client.put(
f"/api/v1/admin/users/{user_id}/activate",
headers=admin_auth_headers
)
assert response.status_code == 200
assert response.json()["is_active"] is True
@pytest.mark.asyncio
async def test_cannot_deactivate_self(
self, client: AsyncClient, admin_auth_headers: dict, test_admin: dict
):
"""Test that admin cannot deactivate themselves."""
admin_id = test_admin["user_data"]["id"]
response = await client.put(
f"/api/v1/admin/users/{admin_id}/deactivate",
headers=admin_auth_headers
)
assert response.status_code == 400
assert "own account" in response.json()["detail"].lower()