"""Teste US-012 (PRD 3.3b): notificare email admin la signup (degradat) + bootstrap admin. TDD: testele se scriu INAINTE de implementare; la inceput pica (RED), dupa implementare trec (GREEN). """ from __future__ import annotations import os import tempfile import pytest from starlette.testclient import TestClient # --------------------------------------------------------------------------- # Fixture client (pattern identic cu test_web_signup.py) # --------------------------------------------------------------------------- @pytest.fixture() def client(monkeypatch): tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db")) monkeypatch.setenv("AUTOPASS_SIGNUP_RATE_MAX", "100") from app.config import get_settings get_settings.cache_clear() from app.web import ratelimit ratelimit._hits.clear() from app.main import app with TestClient(app) as c: yield c get_settings.cache_clear() def _csrf(c: TestClient) -> str: """Obtine un token CSRF proaspat de la GET /signup.""" import re resp = c.get("/signup") 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 HTML" return m.group(1) def _do_signup(c: TestClient, name: str, email: str, parola: str = "parolasecreta") -> object: from tests.conftest import make_test_cui token = _csrf(c) return c.post("/signup", data={ "name": name, "cui": make_test_cui(email), "email": email, "parola": parola, "consent": "1", "csrf_token": token, }) # --------------------------------------------------------------------------- # Teste notify_signup (unitare, fara TestClient) # --------------------------------------------------------------------------- def test_notify_noop_fara_smtp(monkeypatch): """smtp_host None -> notify_signup nu ridica si nu incearca sa trimita.""" import smtplib # Daca smtplib.SMTP e apelat, testul pica def fake_smtp(*a, **kw): raise AssertionError("smtplib.SMTP nu trebuia apelat fara smtp_host configurat") monkeypatch.setattr(smtplib, "SMTP", fake_smtp) # Asigura smtp_host = None from app.config import get_settings get_settings.cache_clear() monkeypatch.setenv("AUTOPASS_SMTP_HOST", "") # string gol -> None in Settings get_settings.cache_clear() from app.email import notify_signup # Nu trebuie sa ridice notify_signup(["admin@test.com"], account_id=1, email="nou@test.com") get_settings.cache_clear() def test_notify_nu_blocheaza_la_eroare(monkeypatch): """smtp_host setat, SMTP ridica exceptie -> notify_signup returneaza normal (best-effort).""" import smtplib class FakeSMTP: def __init__(self, *a, **kw): raise ConnectionRefusedError("simulam eroare retea") monkeypatch.setattr(smtplib, "SMTP", FakeSMTP) monkeypatch.setenv("AUTOPASS_SMTP_HOST", "smtp.test.local") monkeypatch.setenv("AUTOPASS_SMTP_PORT", "587") monkeypatch.setenv("AUTOPASS_SMTP_FROM", "autopass@test.local") from app.config import get_settings get_settings.cache_clear() from app.email import notify_signup # Nu trebuie sa ridice, chiar daca SMTP esueaza notify_signup(["admin@test.com"], account_id=5, email="nou@test.com") get_settings.cache_clear() # --------------------------------------------------------------------------- # Teste bootstrap admin (prin TestClient) # --------------------------------------------------------------------------- def test_primul_signup_devine_admin(client): """Primul signup -> userul are is_admin=1 (count_admins==1). Al doilea signup cu alt email -> is_admin=0 (count_admins ramane 1).""" # Primul signup resp = _do_signup(client, "Primul Service", "primul@test.com") assert resp.status_code == 200 assert "rfak_" in resp.text from app.db import get_connection from app.users import count_admins conn = get_connection() try: n_admins = count_admins(conn) assert n_admins == 1, f"Dupa primul signup, count_admins trebuie sa fie 1, nu {n_admins}" user = conn.execute( "SELECT is_admin FROM users WHERE email='primul@test.com'" ).fetchone() assert user is not None assert user["is_admin"] == 1, "Primul user trebuie sa fie admin" finally: conn.close() # Al doilea signup resp2 = _do_signup(client, "Al Doilea Service", "aldoilea@test.com") assert resp2.status_code == 200 assert "rfak_" in resp2.text conn = get_connection() try: n_admins = count_admins(conn) assert n_admins == 1, f"Dupa al doilea signup, count_admins trebuie sa ramana 1, nu {n_admins}" user2 = conn.execute( "SELECT is_admin FROM users WHERE email='aldoilea@test.com'" ).fetchone() assert user2 is not None assert user2["is_admin"] == 0, "Al doilea user NU trebuie sa fie admin" finally: conn.close() # --------------------------------------------------------------------------- # Teste C16 (log SIGNUP pastrat) si best-effort E2E # --------------------------------------------------------------------------- def test_signup_inca_logheaza_si_notifica(client, capsys): """Signup reusit -> stdout contine 'SIGNUP cont=' (C16 pastrat).""" resp = _do_signup(client, "Service Log Test", "log@test.com") assert resp.status_code == 200 assert "rfak_" in resp.text captured = capsys.readouterr() assert "SIGNUP cont=" in captured.out, ( f"Linia de log C16 'SIGNUP cont=' lipseste din stdout. Capturat: {captured.out!r}" ) def test_signup_neblocat_de_notify(monkeypatch, client): """notify_signup ridica -> signup returneaza totusi 200 cu cheia (best-effort E2E).""" # Monkeypatch notify_signup sa ridice def notify_always_raises(*a, **kw): raise RuntimeError("simulam eroare fatala in notify") # Importam modulul inainte de monkeypatch import app.email as email_mod monkeypatch.setattr(email_mod, "notify_signup", notify_always_raises) resp = _do_signup(client, "Service Robust", "robust@test.com") assert resp.status_code == 200, ( f"Signup trebuia sa reuseasca indiferent de eroarea din notify. status={resp.status_code}" ) assert "rfak_" in resp.text, "Cheia API trebuia afisata chiar daca notify a esuat"