"""Teste US-011 (PRD 3.3b): panou admin web /admin — conturi in asteptare + activare. TDD strict: testele se scriu INAINTE de implementare; la inceput pica (RED), dupa implementare trec (GREEN). Fisiere testate: app/web/admin_routes.py, app/web/templates/admin.html. """ from __future__ import annotations import os import re import tempfile import pytest from starlette.testclient import TestClient # --------------------------------------------------------------------------- # Fixture client (web_auth_required=true -> CSRF enforce) # --------------------------------------------------------------------------- @pytest.fixture() def client(monkeypatch): """TestClient pe aplicatia completa, cu DB izolata si web_auth_required=true.""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "test_admin_panel.db")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true") # Ridica limita rate-limit pentru signup ca testele nu se blocheze intre ele monkeypatch.setenv("AUTOPASS_SIGNUP_RATE_MAX", "100") from app.config import get_settings get_settings.cache_clear() # Curata hits-urile rate-limit intre teste from app.web import ratelimit ratelimit._hits.clear() from app.main import app with TestClient(app, follow_redirects=False) as c: yield c get_settings.cache_clear() # --------------------------------------------------------------------------- # Helper-e # --------------------------------------------------------------------------- def _get_csrf(client: TestClient, url: str) -> str: """Extrage csrf_token din pagina HTML.""" resp = client.get(url, follow_redirects=True) m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) if not m: m = re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text) assert m, f"csrf_token negasit in {url}: {resp.text[:500]}" return m.group(1) def _signup(client: TestClient, name: str, email: str, password: str = "parola_test_001") -> int: """Creeaza cont via POST /signup si intoarce account_id.""" token = _get_csrf(client, "/signup") resp = client.post("/signup", data={ "name": name, "email": email, "parola": password, "csrf_token": token, }, follow_redirects=True) assert resp.status_code == 200, f"signup esuat: {resp.text[:300]}" # Extrage account_id din raspuns (pagina afiseaza cheia rfak_ + account_id) m = re.search(r"cont=(\d+)", resp.text) if not m: # fallback: citeste din DB from app.db import get_connection conn = get_connection() row = conn.execute( "SELECT account_id FROM users WHERE email=? COLLATE NOCASE", (email,) ).fetchone() conn.close() assert row, f"userul {email} nu a fost creat" return int(row["account_id"]) return int(m.group(1)) def _login(client: TestClient, email: str, password: str = "parola_test_001") -> None: """Autentifica userul (seteaza sesiunea cookie).""" token = _get_csrf(client, "/login") resp = client.post("/login", data={ "email": email, "parola": password, "csrf_token": token, }, follow_redirects=False) assert resp.status_code == 303, f"login esuat cu {email}: {resp.status_code} {resp.text[:200]}" def _make_admin(account_id: int) -> None: """Marcheaza contul ca admin direct in DB.""" from app.db import get_connection from app.users import set_admin conn = get_connection() try: set_admin(conn, account_id, is_admin=True) conn.commit() finally: conn.close() def _get_account_active(account_id: int) -> bool: """Citeste accounts.active din DB.""" from app.db import get_connection conn = get_connection() try: row = conn.execute("SELECT active FROM accounts WHERE id=?", (account_id,)).fetchone() return bool(row["active"]) if row else False finally: conn.close() # --------------------------------------------------------------------------- # Cazuri de test # --------------------------------------------------------------------------- def test_admin_vede_conturi_pending(client): """Admin logat -> GET /admin contine numele contului pending (active=0).""" # Creeaza un cont pending (active=0 implicit la signup) _signup(client, "Service Pending SRL", "pending@test.ro") # Creeaza contul admin admin_id = _signup(client, "Admin Corp SA", "admin@test.ro") _make_admin(admin_id) # Login ca admin _login(client, "admin@test.ro") resp = client.get("/admin") assert resp.status_code == 200, f"GET /admin a returnat {resp.status_code}" assert "Service Pending SRL" in resp.text, ( f"Contul pending nu apare in /admin. Raspuns: {resp.text[:600]}" ) def test_activare_din_admin(client): """POST /admin/activate cu CSRF -> accounts.active=1; redirect 303.""" # Cont pending pending_id = _signup(client, "Firma De Activat SRL", "firma@test.ro") assert not _get_account_active(pending_id), "contul trebuie sa fie inactiv initial" # Cont admin admin_id = _signup(client, "Admin Activator SA", "activator@test.ro") _make_admin(admin_id) _login(client, "activator@test.ro") # Obtine CSRF din pagina /admin resp = client.get("/admin") assert resp.status_code == 200 m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) if not m: m = re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text) assert m, "csrf_token negasit in /admin" csrf = m.group(1) resp2 = client.post("/admin/activate", data={ "account_id": str(pending_id), "csrf_token": csrf, }) assert resp2.status_code == 303, ( f"POST /admin/activate trebuia redirect 303, got {resp2.status_code}: {resp2.text[:300]}" ) assert _get_account_active(pending_id), "contul trebuia sa fie activat dupa POST /admin/activate" def test_non_admin_403(client): """User logat NON-admin -> GET /admin -> 403. Creeaza intai un cont admin (bootstrap: primul user devine admin), apoi un al doilea cont (non-admin) si verifica ca al doilea primeste 403. """ # Primul signup devine automat admin (bootstrap US-010) _signup(client, "Admin Bootstrap SA", "bootstrap@test.ro") # Al doilea user NU e admin _signup(client, "User Simplu SRL", "user@test.ro") _login(client, "user@test.ro") resp = client.get("/admin") assert resp.status_code == 403, ( f"User non-admin trebuia 403 pe /admin, got {resp.status_code}" ) def test_admin_nelogat_redirect(client): """Fara sesiune -> GET /admin -> 303 redirect la /login.""" resp = client.get("/admin") assert resp.status_code == 303, ( f"Nelogat pe /admin trebuia 303, got {resp.status_code}" ) loc = resp.headers.get("location", "") assert "/login" in loc, f"Redirect gresit: {loc}" def test_activate_fara_csrf_403(client): """Admin logat, POST /admin/activate fara token CSRF -> 403.""" pending_id = _signup(client, "Firma Fara CSRF SRL", "nocsrf@test.ro") admin_id = _signup(client, "Admin CSRF Test SA", "csrfadmin@test.ro") _make_admin(admin_id) _login(client, "csrfadmin@test.ro") # POST fara token (sau token gol) resp = client.post("/admin/activate", data={ "account_id": str(pending_id), "csrf_token": "", }) assert resp.status_code == 403, ( f"POST fara CSRF trebuia 403, got {resp.status_code}" )