"""Teste US-002 (PRD 3.4): bara de status persistenta cu etichete umane. TDD: testele se scriu INAINTE de implementare; la inceput pica (RED), dupa implementare trec (GREEN). Rute testate: - GET /_fragments/status -> bara de status cu etichete umane, scoped pe cont """ from __future__ import annotations import os import re import tempfile import pytest from starlette.testclient import TestClient def _create_account_user(email: str = "user@test.com", 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, "Service Test Status", 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) -> 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 _insert_submission(status: str, account_id: int) -> None: """Insereaza un submission cu status dat pentru un cont dat.""" from app.db import get_connection import json conn = get_connection() try: conn.execute( "INSERT INTO submissions (idempotency_key, account_id, status, payload_json) " "VALUES (?, ?, ?, ?)", ( f"test-key-{status}-{account_id}-{os.urandom(4).hex()}", account_id, status, json.dumps({"vin": "TEST", "status": status}), ), ) conn.commit() finally: conn.close() @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, "status_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_status_fragment_text_uman # ============================================================ def test_status_fragment_text_uman(client): """GET /_fragments/status (autentificat) -> contine text uman de sanatate, NU stari tehnice brute. Actualizat US-003 (PRD 5.15): strip sanatate unificat in loc de bife individuale worker/RAR. Textul 'Trimitere automata' a fost inlocuit cu 'declaratiile curg normal' / 'Blocat: ...'. """ _create_account_user("status@test.com", "parolasecreta10") _login(client, "status@test.com", "parolasecreta10") resp = client.get("/_fragments/status") assert resp.status_code == 200 html = resp.text # US-003 D6: strip sanatate cu text uman compus (nu bife individuale) assert "declaratiile" in html.lower(), ( f"Fragmentul nu contine textul de sanatate ('declaratiile'). HTML (primele 500 ch): {html[:500]}" ) # NU trebuie sa contina text tehnic brut assert "worker viu" not in html.lower(), ( f"Fragmentul contine 'worker viu' (text tehnic brut). HTML (primele 500 ch): {html[:500]}" ) assert "viu" not in html, ( "Fragmentul contine eticheta bruta 'viu'" ) # ============================================================ # test_status_blocate_defalcare # ============================================================ def test_status_blocate_defalcare(client): """Cu submissions blocate in DB, fragmentul arata defalcarea pe motiv (texte umane).""" acct_id, _ = _create_account_user("blocate@test.com", "parolasecreta10") _login(client, "blocate@test.com", "parolasecreta10") # Insereaza submissions blocate din fiecare tip _insert_submission("needs_mapping", acct_id) _insert_submission("needs_mapping", acct_id) _insert_submission("needs_data", acct_id) _insert_submission("error", acct_id) # Pill-urile s-au mutat in bara de filtre din sectiunea Trimiteri (nu in bara de status). resp = client.get("/?tab=acasa") assert resp.status_code == 200 html = resp.text # Pill-urile au etichetele scurte per categorie (nu etichetele lungi din eticheta_stare) assert "Lipsa cod" in html, "Acasa nu arata pill-ul pentru needs_mapping" assert "Date incomplete" in html, "Acasa nu arata pill-ul pentru needs_data" assert "Eroare" in html, "Acasa nu arata pill-ul pentru error" # Pill-urile arata numarul total per categorie (2 needs_mapping, 1 needs_data, 1 error) assert 'class="pill-cat"' in html, "Pill-urile trebuie sa fie elemente cu clasa pill-cat" assert "" # ============================================================ # test_status_se_reincarca_htmx # ============================================================ def test_status_se_reincarca_htmx(client): """Fragmentul contine atribut hx-trigger cu poll periodic (every 15s).""" _create_account_user("htmx@test.com", "parolasecreta10") _login(client, "htmx@test.com", "parolasecreta10") resp = client.get("/_fragments/status") assert resp.status_code == 200 html = resp.text # Trebuie sa contina hx-trigger periodic assert "hx-trigger" in html, ( f"Fragmentul nu contine atribut hx-trigger. HTML: {html[:500]}" ) assert "every 15s" in html, ( f"Fragmentul nu contine poll 'every 15s'. HTML: {html[:500]}" ) # Trebuie sa aiba endpoint corect pentru auto-refresh assert "/_fragments/status" in html, ( "Fragmentul nu contine referinta la /_fragments/status pentru hx-get" ) # Trebuie sa aiba un id stabil pe containerul radacina assert 'id="status-bar"' in html, ( "Fragmentul nu are id='status-bar' pe containerul radacina" ) # ============================================================ # US-014: banner "Necesita atentia ta" actionabil # ============================================================ def _insert_submission_vehicul(status, account_id, vin, nr): from app.db import get_connection import json conn = get_connection() try: conn.execute( "INSERT INTO submissions (idempotency_key, account_id, status, payload_json) VALUES (?, ?, ?, ?)", (f"k-{os.urandom(5).hex()}", account_id, status, json.dumps({"vin": vin, "nr_inmatriculare": nr, "data_prestatie": "2026-06-15", "odometru_final": "1", "prestatii": [{"cod_prestatie": "OE-1"}]})), ) conn.commit() finally: conn.close() def test_categorie_blocata_linkeaza_la_trimiteri_filtrate(client): """Pill-ul error filtreaza tabelul prin filtreazaStare(this, 'error') in bara de filtre.""" acct_id, _ = _create_account_user("link@test.com", "parolasecreta10") _login(client, "link@test.com", "parolasecreta10") _insert_submission("error", acct_id) html = client.get("/?tab=acasa").text # Pill-ul scrie campul de filtru si re-trimite form-ul (nu mai navigheaza prin deep-link) assert "filtreazaStare(this, 'error')" in html assert 'data-status="error"' in html assert "tab=acasa&status=error" not in html def test_status_nu_arata_identificator_rand_blocat(client): """US-003 (PRD 5.10): VIN/nr inmatriculare nu mai apar in bara de status. Lista de ID-uri a fost inlocuita cu pill-uri cu numar total (fara PII nominal).""" acct_id, _ = _create_account_user("ident@test.com", "parolasecreta10") _login(client, "ident@test.com", "parolasecreta10") _insert_submission_vehicul("error", acct_id, "WVWZZZ1KZAW000123", "B123ABC") html = client.get("/_fragments/status").text # Bara de status arata doar contoare, nu lista cu VIN/nr per rand (fara PII nominal) assert "B123ABC" not in html, "Nr inmatriculare nu trebuie sa mai apara in bara de status" assert "WVWZZZ1KZAW000123" not in html, "VIN integral nu trebuie expus" assert "0123" not in html, "VIN partial nu trebuie sa mai apara in bara de status" def test_scoped_pe_cont(client): from app.accounts import create_account from app.db import get_connection acct_id, _ = _create_account_user("own@test.com", "parolasecreta10") conn = get_connection() try: other = create_account(conn, "Alt", active=True) finally: conn.close() _login(client, "own@test.com", "parolasecreta10") _insert_submission_vehicul("error", other, "OTHERVIN000009999", "X999ZZZ") html = client.get("/_fragments/status").text # randul altui cont NU apare in banner-ul meu assert "X999ZZZ" not in html assert "9999" not in html