"""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 'Trimitere automata', NU 'worker viu'.""" _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 # Trebuie sa contina textul uman din eticheta_worker (labels.py) assert "Trimitere automata" in html, ( f"Fragmentul nu contine 'Trimitere automata'. HTML (primele 500 ch): {html[:500]}" ) # NU trebuie sa contina textul brut tehnic assert "worker viu" not in html.lower(), ( f"Fragmentul contine 'worker viu' (text tehnic brut). HTML (primele 500 ch): {html[:500]}" ) # NU trebuie sa contina "mort" (stare tehnica bruta) # (poate aparea in 'oprita' -> acceptam; 'mort' singur -> nu) # Verificam ca nu apare 'mort' ca eticheta standalone 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) resp = client.get("/_fragments/status") assert resp.status_code == 200 html = resp.text # US-003 (PRD 5.10): Blocatele apar ca pill-uri (nu ca lista cu ID-uri) assert "Necesita atentie" in html, ( f"Fragmentul nu contine sectiunea 'Necesita atentie'. HTML: {html[:800]}" ) # Pill-urile au etichetele scurte per categorie (nu etichetele lungi din eticheta_stare) assert "Lipsa cod" in html, ( "Fragmentul nu arata pill-ul pentru needs_mapping" ) assert "Date incomplete" in html, ( "Fragmentul nu arata pill-ul pentru needs_data" ) assert "Eroare" in html, ( "Fragmentul nu arata pill-ul pentru error" ) # Pill-urile arata numarul total per categorie (2 needs_mapping, 1 needs_data, 1 error) assert "2" in html, "Pill-ul needs_mapping trebuie sa arate numarul 2" 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): """US-003 (PRD 5.10): pill-ul error are hx-get cu ?status=error. Deep-link-ul tab=acasa&status=error a fost eliminat (pill inlocuieste link-ul vechi).""" acct_id, _ = _create_account_user("link@test.com", "parolasecreta10") _login(client, "link@test.com", "parolasecreta10") _insert_submission("error", acct_id) html = client.get("/_fragments/status").text # Pill-ul are hx-get cu status=error (filtrare directa submissions) assert "/_fragments/submissions?status=error" in html # Deep-link-ul tab=acasa&status=error nu mai exista — pill-uri inlocuiesc link-urile 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 pill cu count, nu lista cu VIN/nr per rand 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" # Pill-ul cu count 1 apare in locul listei assert "status=error" in html, "Pill error trebuie sa aiba hx-get cu status=error" 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