"""Teste US-010 (PRD 3.3b): rol admin + bootstrap + guard require_admin. Fisiere testate: app/users.py (helper-e admin), app/web/session.py (require_admin), tools/account.py (subcomanda set-admin). """ from __future__ import annotations import os import tempfile import pytest from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from starlette.middleware.sessions import SessionMiddleware from starlette.testclient import TestClient # --------------------------------------------------------------------------- # Fixture DB # --------------------------------------------------------------------------- @pytest.fixture() def conn(monkeypatch): tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_admin_role.db")) from app.config import get_settings get_settings.cache_clear() from app.db import get_connection, init_db init_db() c = get_connection() yield c c.close() get_settings.cache_clear() @pytest.fixture() def account_id(conn): """Cont de test (nu default id=1).""" from app.accounts import create_account return create_account(conn, "Service Test Admin") @pytest.fixture() def user_id(conn, account_id): """User de test pe contul de test.""" from app.users import create_user return create_user(conn, account_id, "admin_test@exemplu.ro", "parola_sigura_123") @pytest.fixture() def env_cli(monkeypatch): """Fixture pentru CLI: DB separata + clear settings cache.""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_admin_cli.db")) from app.config import get_settings get_settings.cache_clear() from app.db import init_db init_db() yield get_settings.cache_clear() # --------------------------------------------------------------------------- # Helper app minimal pentru require_admin # --------------------------------------------------------------------------- def _make_app_admin() -> FastAPI: """App FastAPI minimal cu ruta protejata de require_admin.""" mini = FastAPI() mini.add_middleware( SessionMiddleware, secret_key="test-secret-admin", session_cookie="autopass_session", https_only=False, same_site="strict", ) from app.web.session import LoginRequired, AdminRequired @mini.exception_handler(LoginRequired) async def login_required_handler(request: Request, exc: LoginRequired): return JSONResponse(status_code=401, content={"detail": "neautentificat"}) @mini.exception_handler(AdminRequired) async def admin_required_handler(request: Request, exc: AdminRequired): return JSONResponse(status_code=403, content={"detail": "acces interzis (necesita admin)"}) @mini.get("/set-session") def set_sess(request: Request, account_id: int = 1, user_id: int = 1): from app.web.session import set_session set_session(request, account_id, user_id) return {"ok": True} @mini.get("/admin-only") def admin_only(request: Request): from app.web.session import require_admin aid = require_admin(request) return {"account_id": aid} return mini # --------------------------------------------------------------------------- # Teste helper-e app/users.py # --------------------------------------------------------------------------- def test_count_admins_initial_zero(conn): """Fara niciun user cu is_admin=1, count_admins returneaza 0.""" from app.users import count_admins assert count_admins(conn) == 0 def test_set_admin_marcheaza_userii_contului(conn, account_id, user_id): """set_admin(conn, account_id) seteaza is_admin=1 pe toti userii contului.""" from app.users import set_admin, count_admins assert count_admins(conn) == 0 set_admin(conn, account_id, is_admin=True) assert count_admins(conn) == 1 def test_create_user_is_admin_flag(conn, account_id): """create_user cu is_admin=True seteaza coloana la 1.""" from app.users import create_user, count_admins create_user(conn, account_id, "newadmin@exemplu.ro", "parola_sigura_456", is_admin=True) assert count_admins(conn) == 1 def test_is_account_admin(conn, account_id, user_id): """is_account_admin returneaza False inainte si True dupa set_admin.""" from app.users import is_account_admin, set_admin assert is_account_admin(conn, account_id) is False set_admin(conn, account_id, is_admin=True) assert is_account_admin(conn, account_id) is True def test_list_admin_emails(conn, account_id): """list_admin_emails returneaza emailurile userilor cu is_admin=1.""" from app.users import create_user, set_admin, list_admin_emails uid = create_user(conn, account_id, "admin1@exemplu.ro", "parola_sigura_789") # Inainte de set_admin -> lista goala assert list_admin_emails(conn) == [] set_admin(conn, account_id, is_admin=True) emails = list_admin_emails(conn) assert "admin1@exemplu.ro" in emails def test_set_admin_cont_inexistent_valueerror(conn): """set_admin pe cont care nu exista ridica ValueError.""" from app.users import set_admin with pytest.raises(ValueError, match="cont inexistent"): set_admin(conn, 9999, is_admin=True) def test_set_admin_cont_fara_users_silentios(conn): """set_admin pe cont existent fara useri e no-op silentios (nu ridica exceptie).""" from app.accounts import create_account from app.users import set_admin, count_admins acct_fara_user = create_account(conn, "Cont Fara User") # Nu trebuie sa ridice set_admin(conn, acct_fara_user, is_admin=True) assert count_admins(conn) == 0 # niciun user modificat # --------------------------------------------------------------------------- # Teste require_admin (app/web/session.py) prin TestClient # --------------------------------------------------------------------------- @pytest.fixture() def client_admin(monkeypatch): """TestClient pe app cu require_admin; DB cu cont admin + cont non-admin.""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_require_admin.db")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true") from app.config import get_settings get_settings.cache_clear() from app.db import get_connection, init_db init_db() conn = get_connection() from app.accounts import create_account from app.users import create_user, set_admin # Cont admin (id=2) acct_admin = create_account(conn, "Admin Corp") uid_admin = create_user(conn, acct_admin, "admin@corp.ro", "parola_admin_001") set_admin(conn, acct_admin, is_admin=True) # Cont non-admin (id=3) acct_user = create_account(conn, "User Corp") uid_user = create_user(conn, acct_user, "user@corp.ro", "parola_user_001") conn.close() app = _make_app_admin() with TestClient(app, follow_redirects=False) as c: yield c, acct_admin, uid_admin, acct_user, uid_user get_settings.cache_clear() def test_require_admin_blocheaza_non_admin(client_admin): """User logat NON-admin pe ruta admin -> 403.""" client, acct_admin, uid_admin, acct_user, uid_user = client_admin client.get(f"/set-session?account_id={acct_user}&user_id={uid_user}") resp = client.get("/admin-only") assert resp.status_code == 403 assert "admin" in resp.json()["detail"] def test_require_admin_lasa_admin(client_admin): """User logat ADMIN pe ruta admin -> 200 cu account_id.""" client, acct_admin, uid_admin, acct_user, uid_user = client_admin client.get(f"/set-session?account_id={acct_admin}&user_id={uid_admin}") resp = client.get("/admin-only") assert resp.status_code == 200 assert resp.json()["account_id"] == acct_admin def test_require_admin_nelogat_ridica_login_required(client_admin): """Fara sesiune, require_admin ridica LoginRequired (-> 401 in app-ul nostru de test).""" client, *_ = client_admin resp = client.get("/admin-only") # In app-ul de test, LoginRequired -> 401 assert resp.status_code == 401 # --------------------------------------------------------------------------- # Teste CLI tools/account.py set-admin # --------------------------------------------------------------------------- def _run_account(argv): from tools.account import main return main(argv) def test_cli_set_admin_marcheaza_contul(env_cli, capsys): """CLI set-admin --account N seteaza is_admin=1 pe userii contului.""" # Creeaza cont cu user from app.db import get_connection from app.accounts import create_account from app.users import create_user, is_account_admin conn = get_connection() acct_id = create_account(conn, "Service CLI Admin") create_user(conn, acct_id, "cli_admin@corp.ro", "parola_cli_admin_1") conn.close() rc = _run_account(["set-admin", "--account", str(acct_id)]) out = capsys.readouterr().out assert rc == 0 assert str(acct_id) in out or "admin" in out.lower() conn2 = get_connection() assert is_account_admin(conn2, acct_id) is True conn2.close() def test_cli_set_admin_remove(env_cli, capsys): """CLI set-admin --account N --remove scoate adminul.""" from app.db import get_connection from app.accounts import create_account from app.users import create_user, set_admin, is_account_admin conn = get_connection() acct_id = create_account(conn, "Service CLI Remove") create_user(conn, acct_id, "remove@corp.ro", "parola_remove_001") set_admin(conn, acct_id, is_admin=True) conn.close() rc = _run_account(["set-admin", "--account", str(acct_id), "--remove"]) assert rc == 0 conn2 = get_connection() assert is_account_admin(conn2, acct_id) is False conn2.close() def test_cli_set_admin_cont_inexistent_exit_2(env_cli, capsys): """CLI set-admin pe cont inexistent -> exit code 2 + mesaj pe stderr.""" rc = _run_account(["set-admin", "--account", "9999"]) err = capsys.readouterr().err assert rc == 2 assert "inexistent" in err or "eroare" in err.lower()