from __future__ import annotations import os import uuid import pytest from sqlalchemy import create_engine, text pytestmark = pytest.mark.integration @pytest.fixture() def rbac_client(monkeypatch): database_url = os.environ.get("TEST_DATABASE_URL") admin_password = os.environ.get("TEST_ADMIN_PASSWORD") if not database_url or not admin_password: pytest.skip("TEST_DATABASE_URL and TEST_ADMIN_PASSWORD are required") monkeypatch.setenv("DATABASE_URL", database_url) monkeypatch.setenv("SECRET_KEY", "dataops-local-test-secret-key") from app import create_app app = create_app() app.config.update(TESTING=True) return app.test_client(), database_url, admin_password def _login(client, username, password): response = client.post( "/api/system/auth/login", json={"username": username, "password": password} ) return response, (response.get_json().get("data") or {}).get("token") def test_admin_manages_users_and_viewer_is_forbidden(rbac_client): client, database_url, admin_password = rbac_client username = f"viewer_{uuid.uuid4().hex[:10]}" user_id = None try: login, admin_token = _login(client, "admin", admin_password) assert login.status_code == 200 assert admin_token me = client.get( "/api/system/auth/me", headers={"Authorization": f"Bearer {admin_token}"}, ) assert me.status_code == 200 assert "users:manage" in me.get_json()["data"]["permissions"] created = client.post( "/api/system/users", json={ "username": username, "password": "ViewerPass123", "roles": ["viewer"], }, headers={"Authorization": f"Bearer {admin_token}"}, ) assert created.status_code == 201 user_id = created.get_json()["data"]["id"] viewer_login, viewer_token = _login(client, username, "ViewerPass123") assert viewer_login.status_code == 200 forbidden = client.get( "/api/system/users", headers={"Authorization": f"Bearer {viewer_token}"}, ) assert forbidden.status_code == 403 disabled = client.put( f"/api/system/users/{user_id}", json={"status": "disabled"}, headers={"Authorization": f"Bearer {admin_token}"}, ) assert disabled.status_code == 200 rejected, _ = _login(client, username, "ViewerPass123") assert rejected.status_code == 401 invalid = client.get( "/api/system/auth/me", headers={"Authorization": "Bearer invalid-token"}, ) assert invalid.status_code == 401 finally: if user_id: engine = create_engine(database_url) with engine.begin() as connection: connection.execute( text("DELETE FROM public.users WHERE id = CAST(:id AS uuid)"), {"id": user_id}, ) engine.dispose()