"""Teste US-005 (PRD 3.4): pagina Acasa cu ghid de pornire (checklist auto-bifat). TDD: testele sunt scrise INAINTE de implementare; la inceput pica (RED), dupa implementare trec (GREEN). Rute testate: - GET / (tab Acasa) -> ghid cu pasi bifati/nebifati in functie de starea contului - GET /_fragments/acasa -> fragment HTMX pentru tab-ul Acasa - GET /_fragments/submissions -> empty state prietenos cand coada e goala - GET /_fragments/mapari -> empty state prietenos cand nu sunt mapari pendinte """ from __future__ import annotations import os import re import tempfile import pytest from starlette.testclient import TestClient # ============================================================ # Helpers # ============================================================ def _create_account_user(email: str, password: str = "parolasecreta10"): """Creeaza cont + user. Intoarce (acct_id, user_id).""" from app.accounts import create_account from app.users import create_user from app.db import get_connection conn = get_connection() try: acct_id = create_account(conn, f"Service Test {email}", active=True) user_id = create_user(conn, acct_id, email, password) return acct_id, user_id finally: conn.close() def _login(client, email: str, password: str = "parolasecreta10") -> None: """Face login real prin HTTP si seteaza cookie-ul de sesiune pe client.""" resp = client.get("/login") 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 pe /login" csrf = m.group(1) resp = client.post("/login", data={ "email": email, "parola": password, "csrf_token": csrf, }) assert resp.status_code == 303, f"Login esuat: {resp.status_code} {resp.text[:200]}" def _set_rar_creds(acct_id: int) -> None: """Seteaza rar_creds_enc pe cont (simuleaza configurarea credentialelor RAR).""" from app.db import get_connection from app.crypto import encrypt_creds conn = get_connection() try: enc = encrypt_creds({"email": "test@rar.ro", "password": "parola_rar"}) conn.execute( "UPDATE accounts SET rar_creds_enc=? WHERE id=?", (enc, acct_id), ) finally: conn.close() def _add_submission(acct_id: int) -> None: """Adauga un submission minimal pentru cont (simuleaza un import efectuat).""" import json from app.db import get_connection conn = get_connection() try: conn.execute( "INSERT INTO submissions (idempotency_key, account_id, status, payload_json) " "VALUES (?, ?, 'queued', ?)", (f"test_key_{acct_id}", acct_id, json.dumps({"test": True})), ) finally: conn.close() # ============================================================ # Fixture # ============================================================ @pytest.fixture() def client(monkeypatch): """Client cu BD izolata si autentificare web activata.""" tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "onboarding_test.db")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true") from app.config import get_settings get_settings.cache_clear() from app.web import ratelimit ratelimit._hits.clear() # izolare: limiterul login e global in-proces from app.main import app with TestClient(app, follow_redirects=False) as c: yield c ratelimit._hits.clear() get_settings.cache_clear() # ============================================================ # test_checklist_pas_creds_neconfigurat # ============================================================ def test_checklist_pas_creds_neconfigurat(client): """Cont fara creds RAR -> pasul 'Conecteaza contul RAR' e NEbifat.""" acct_id, _ = _create_account_user("nocreds@test.com") _login(client, "nocreds@test.com") resp = client.get("/") assert resp.status_code == 200 html = resp.text # Pasul de conectare RAR trebuie sa apara assert "Conecteaza" in html or "cont RAR" in html or "RAR" in html, \ "Ghidul nu contine referinta la conectarea contului RAR" # Cand nu sunt creds, pasul NU trebuie sa fie bifat # Bifarea e semnalata printr-o clasa 'bifat' sau o checkmark langa text-ul RAR # Verificam ca nu apare combinatia "bifat" + "RAR" sau "done" + "RAR" in proximitate # (implementarea exacta e in template, dar pattern-ul de baza: fara `pas-bifat` langa RAR) assert not re.search( r'pas-bifat[^<]*Conecteaza|Conecteaza[^<]*pas-bifat', html, re.DOTALL | re.IGNORECASE ), "Pasul RAR nu trebuie sa fie bifat cand contul nu are creds" # ============================================================ # test_checklist_pas_creds_bifat_cand_exista # ============================================================ def test_checklist_pas_creds_bifat_cand_exista(client): """Dupa setarea rar_creds_enc pe cont -> pasul 'Conecteaza contul RAR' e bifat.""" acct_id, _ = _create_account_user("withcreds@test.com") _set_rar_creds(acct_id) _login(client, "withcreds@test.com") resp = client.get("/") assert resp.status_code == 200 html = resp.text # Cand exista creds, pasul trebuie sa fie bifat # Verificam prezenta unui indicator de bifat (clasa 'bifat' sau 'pas-bifat' sau 'done') # Cel putin unul dintre pattern-urile de bifat trebuie sa apara assert re.search( r'pas-bifat|class="[^"]*bifat|done.*RAR|RAR.*done|checkmark.*RAR|RAR.*checkmark', html, re.DOTALL | re.IGNORECASE ), "Pasul RAR trebuie sa fie bifat cand contul are creds configurate" # ============================================================ # test_checklist_ascuns_cand_totul_gata # ============================================================ def test_checklist_ascuns_cand_totul_gata(client): """Creds setate + cel putin un submission -> ghidul se colapseaza/devine discret.""" acct_id, _ = _create_account_user("allset@test.com") _set_rar_creds(acct_id) _add_submission(acct_id) _login(client, "allset@test.com") resp = client.get("/") assert resp.status_code == 200 html = resp.text # Cand totul e gata, ghidul compact/discret trebuie sa apara # Fie "Totul e configurat" fie un link discret catre coada assert "Totul e configurat" in html or "totul e configurat" in html.lower(), \ "Cand toti pasii sunt gata, trebuie sa apara mesajul discret 'Totul e configurat'" # Cardul mare de pasi nu trebuie sa ocupe ecranul # Verificam ca nu mai apare titlul mare al ghidului (Primii pasi) # SAU ca ghidul e marcat ca colapsat (clasa 'ghid-complet' sau similar) # Pattern: fie ghid-complet, fie lipsa titlului complet "Primii pasi" in forma de card mare assert "ghid-complet" in html or "Totul e configurat" in html, \ "Ghidul trebuie sa se colapseze cand toti pasii esentiali sunt finalizati" # ============================================================ # test_linkuri_ghid_duc_la_taburi # ============================================================ def test_linkuri_ghid_duc_la_taburi(client): """Link-urile din ghid contin ?tab=cont si ?tab=import.""" acct_id, _ = _create_account_user("links@test.com") _login(client, "links@test.com") resp = client.get("/") assert resp.status_code == 200 html = resp.text # Ghidul trebuie sa contina link catre tab-ul Cont assert "?tab=cont" in html, \ "Ghidul nu contine link catre tab-ul Cont (?tab=cont)" # Ghidul trebuie sa contina link catre tab-ul Import assert "?tab=import" in html, \ "Ghidul nu contine link catre tab-ul Import (?tab=import)" # ============================================================ # test_empty_state_coada_gol # ============================================================ def test_empty_state_coada_gol(client): """Tab Coada fara submissions -> indemn prietenos catre Import, nu mesaj tehnic.""" acct_id, _ = _create_account_user("emptyq@test.com") _login(client, "emptyq@test.com") resp = client.get("/_fragments/submissions") assert resp.status_code == 200 html = resp.text # Nu trebuie sa apara mesajul tehnic vechi cu POST /v1/prezentari assert "POST /v1/prezentari" not in html, \ "Empty state coada nu trebuie sa contina mesajul tehnic vechi 'POST /v1/prezentari'" # Trebuie sa contina un indemn catre Import assert "import" in html.lower() or "Import" in html, \ "Empty state coada trebuie sa contina indemn catre Import" # Trebuie sa contina link catre ?tab=import assert "?tab=import" in html, \ "Empty state coada trebuie sa contina link ?tab=import" # ============================================================ # test_empty_state_mapari_gol # ============================================================ def test_empty_state_mapari_gol(client): """Tab Mapari fara pending -> mesaj prietenos cu indemn (nu lista goala fara context).""" acct_id, _ = _create_account_user("emptym@test.com") _login(client, "emptym@test.com") resp = client.get("/_fragments/mapari") assert resp.status_code == 200 html = resp.text # Trebuie sa apara un mesaj prietenos cand nu sunt mapari pendinte # Nu verificam exact textul, dar trebuie sa existe un indemn/explicatie assert "Nicio operatie nemapata" in html or "totul" in html.lower() or "import" in html.lower(), \ "Empty state mapari trebuie sa contina mesaj prietenos" # Trebuie sa contina un indemn catre Import sau o explicatie clara # (cel putin link catre import sau mentionarea cuvantului) assert "import" in html.lower() or "?tab=import" in html, \ "Empty state mapari trebuie sa contina indemn catre Import"