"""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 "Cont RAR" e bifat: glifa ✓ (s-sent) langa link-ul Cont RAR # (Acasa compacta PRD 3.5 — checklist pe un rand, bife cu glifa). assert "✓" in html, "Lipseste glifa de bifat cand contul are creds" assert "Cont RAR" in html, "Lipseste pasul 'Cont RAR' din checklist" # ============================================================ # 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 toti pasii esentiali sunt gata, checklist-ul "Primii pasi" dispare # (Acasa compacta PRD 3.5: nu mai concureaza cu caseta de upload). assert "Primii pasi" not in html, \ "Checklist-ul 'Primii pasi' trebuie sa dispara cand toti pasii esentiali sunt gata" # Upload-ul ramane dominant pe pagina chiar si dupa setup complet assert 'hx-post="/_import/upload"' in html # ============================================================ # test_linkuri_ghid_duc_la_taburi # ============================================================ def test_linkuri_ghid_duc_la_taburi(client): """Ghidul Acasa duce la Cont; importul e direct pe pagina (nu mai e tab separat).""" 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)" # Importul e acum direct pe Acasa (caseta de upload), nu un link catre alt tab assert 'hx-post="/_import/upload"' in html, \ "Acasa trebuie sa contina caseta de upload (importul e operatia principala)" # ============================================================ # 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 (acum pe Acasa) assert "import" in html.lower() or "Import" in html, \ "Empty state coada trebuie sa contina indemn catre Import" # Trebuie sa contina link catre Acasa (unde traieste importul acum) assert "?tab=acasa" in html, \ "Empty state coada trebuie sa contina link catre Acasa (importul e acolo)" # ============================================================ # 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"