"""Teste US-006 (PRD 5.4): Componenta UI de eroare pe 3 niveluri. Pasul A (RED): teste scrise inainte de implementare. """ from __future__ import annotations import csv import io import json import os import re import tempfile import pytest # --------------------------------------------------------------------------- # Teste pure pentru parse_erori (fara HTTP) # --------------------------------------------------------------------------- from app.web.labels import parse_erori # noqa: E402 def test_parse_erori_array_3niveluri(): """Array imbogatit cu cod/problema/cauza/fix -> lista cu toate 3 nivelurile.""" rar_error = json.dumps([ { "field": "vin", "cod": "VIN_FORMAT", "problema": "VIN invalid", "cauza": "VIN-ul are 16 caractere; RAR cere exact 17.", "fix": "Verifica VIN-ul pe talon (pozitia E); 17 caractere majuscule.", "message": "VIN-ul are 16 caractere; RAR cere exact 17.", } ]) rezultat = parse_erori(rar_error) assert len(rezultat) == 1 e = rezultat[0] assert e["problema"] == "VIN invalid" assert "17" in e["cauza"] assert e["fix"] # non-gol assert e.get("field") == "vin" def test_parse_erori_unmapped(): """Dict unmapped 3-niveluri (cod=COD_NEMAPAT) -> 1 element corect.""" rar_error = json.dumps({ "cod": "COD_NEMAPAT", "problema": "Lipseste codul RAR al operatiei", "cauza": "Codul OP-99 nu are mapare RAR.", "fix": "Alege codul RAR pentru aceasta operatie in tab-ul Mapari.", "unmapped": [{"cod_op_service": "OP-99", "denumire": "Operatie test"}], }) rezultat = parse_erori(rar_error) assert len(rezultat) == 1 e = rezultat[0] assert e["problema"] == "Lipseste codul RAR al operatiei" assert e["fix"] def test_parse_erori_creds(): """Dict cu cod=RAR_CREDS_INVALIDE -> 1 element corect.""" rar_error = json.dumps({ "cod": "RAR_CREDS_INVALIDE", "problema": "Credentiale RAR invalide", "cauza": "Autentificarea la RAR a esuat (401).", "fix": "Verifica email-ul si parola contului RAR in tab-ul Cont.", }) rezultat = parse_erori(rar_error) assert len(rezultat) == 1 e = rezultat[0] assert e["problema"] == "Credentiale RAR invalide" assert e["fix"] def test_parse_erori_forma_veche_si_corupt(): """Forma veche [{ field, message }], string plain, None, invalid -> degradeaza fara exceptie.""" # Forma veche: lista cu field+message dar fara cod vechi = json.dumps([{"field": "vin", "message": "VIN invalid"}]) r = parse_erori(vechi) assert isinstance(r, list) assert len(r) >= 1 # Nu arunca pentru niciun element for e in r: assert "problema" in e # String plain r2 = parse_erori("Eroare generica de la RAR") assert isinstance(r2, list) assert len(r2) >= 1 assert r2[0]["problema"] # None r3 = parse_erori(None) assert isinstance(r3, list) assert r3 == [] # JSON corupt r4 = parse_erori("{invalid json[[[") assert isinstance(r4, list) assert len(r4) >= 1 # Nu arunca # Dict fara cod (forma veche dict) r5 = parse_erori(json.dumps({"auto_send": "cod-abc", "motiv": "auto_send oprit"})) assert isinstance(r5, list) assert len(r5) >= 1 # --------------------------------------------------------------------------- # Fixture HTTP # --------------------------------------------------------------------------- @pytest.fixture() def client(monkeypatch): tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "erori.db")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false") from app.config import get_settings get_settings.cache_clear() from app.main import app from fastapi.testclient import TestClient with TestClient(app) as c: yield c get_settings.cache_clear() def _csv_bytes(rows: list[dict]) -> bytes: buf = io.StringIO() writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()), delimiter=";") writer.writeheader() writer.writerows(rows) return buf.getvalue().encode("utf-8") def _seed_mapping(conn=None): """Mapeaza OP-1 -> R-FRANE (cont dev id=1).""" from app.db import get_connection c = conn or get_connection() try: c.execute("INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES ('R-FRANE','Reparatie frane')") c.execute( "INSERT OR IGNORE INTO operations_mapping (account_id, cod_op_service, cod_prestatie, auto_send) " "VALUES (1, 'OP-1', 'R-FRANE', 1)" ) c.commit() finally: if conn is None: c.close() def _creeaza_submission_needs_data(client, fix_text=None): """Creeaza un submission in starea needs_data si returneaza id-ul.""" import json as _json from app.db import get_connection _seed_mapping() # Insereaza direct un submission needs_data cu rar_error 3-niveluri fix_folosit = fix_text or "Verifica VIN-ul pe talon (pozitia E); 17 caractere majuscule." rar_error_3n = _json.dumps([{ "field": "vin", "cod": "VIN_FORMAT", "problema": "VIN invalid", "cauza": "VIN-ul are 16 caractere; RAR cere exact 17.", "fix": fix_folosit, "message": "VIN-ul are 16 caractere; RAR cere exact 17.", }]) payload = _json.dumps({ "vin": "VIN16CARACT000000", "nr_inmatriculare": "B001TST", "data_prestatie": "2026-06-15", "odometru_final": 145000, "cod_prestatie": "R-FRANE", "prestatii": [{"cod_prestatie": "R-FRANE"}], }) idem_key = f"test-erori-{fix_folosit[:20]}" conn = get_connection() try: cur = conn.execute( """INSERT INTO submissions (account_id, idempotency_key, payload_json, status, rar_error, retry_count) VALUES (1, ?, ?, 'needs_data', ?, 0)""", (idem_key, payload, rar_error_3n) ) conn.commit() return cur.lastrowid finally: conn.close() def test_detaliu_afiseaza_fix(client): """Fragmentul de detaliu al unui submission needs_data contine textul fix-ului.""" sub_id = _creeaza_submission_needs_data(client, "Verifica VIN-ul pe talon (pozitia E)") resp = client.get(f"/_fragments/trimitere/{sub_id}") assert resp.status_code == 200 html = resp.text # Fix-ul trebuie sa apara in HTML assert "pozitia E" in html, ( f"HTML-ul detaliu nu contine fix-ul ('pozitia E'). Primii 2000 chars:\n{html[:2000]}" ) def _import_preview_cu_vin_invalid(client): """Efectueaza un import cu un rand cu VIN invalid si returneaza HTML-ul preview.""" _seed_mapping() rows = [ # rand cu VIN de 16 caractere (invalid, trebuie 17) -> needs_data {"VIN": "VIN16CARACT00000", "Nr inmatriculare": "B001TST", "Data prestatie": "15.06.2026", "Odometru final": "145000", "Operatie": "OP-1"}, ] data = _csv_bytes(rows) r = client.post("/_import/upload", files={"file": ("test.csv", data, "text/csv")}) assert r.status_code == 200 m = re.search(r"/_import/(\d+)/mapare-coloane", r.text) assert m, f"Nu am gasit import_id. Raspuns: {r.text[:1000]}" import_id = int(m.group(1)) r = client.post(f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }) assert r.status_code == 200 return r.text def test_preview_rand_per_camp_fix(client): """Preview rand needs_data -> HTML contine fix-ul per camp (VIN).""" html = _import_preview_cu_vin_invalid(client) # Randul trebuie sa fie needs_data assert "needs_data" in html or "needs_review" in html or "VIN" in html.upper(), ( f"HTML-ul preview nu contine starea asteptata. Primii 2000 chars:\n{html[:2000]}" ) # Fix-ul pentru VIN trebuie sa apara in preview assert "talon" in html.lower() or "majuscule" in html.lower() or "pozitia" in html.lower(), ( f"HTML-ul preview nu contine fix-ul VIN. Primii 3000 chars:\n{html[:3000]}" ) # --------------------------------------------------------------------------- # Teste noi (BUG 1 + BUG 2) — adaugate TDD-style (RED inainte de fix) # --------------------------------------------------------------------------- from app.web.labels import motiv_uman # noqa: E402 from app.errors import eroare # noqa: E402 def test_motiv_uman_creds_3niveluri(): """BUG 1: motiv_uman pe dict 3-niveluri (RAR_CREDS_INVALIDE) -> problema, NU text garbled.""" rar_err = json.dumps(eroare("RAR_CREDS_INVALIDE", cauza="credentiale RAR invalide")) rezultat = motiv_uman("error", rar_err) # Nu trebuie sa contina "field:" / "cod:" (text garbled) assert "field:" not in rezultat, f"Text garbled cu 'field:': {rezultat!r}" assert "cod:" not in rezultat, f"Text garbled cu 'cod:': {rezultat!r}" # Trebuie sa returneze textul 'problema' (primul nivel) data = json.loads(rar_err) problema = data.get("problema") or "" assert problema, "eroare() nu a returnat 'problema' — verifica error_codes.py" assert rezultat == problema[:200], ( f"Asteptat {problema[:200]!r}, obtinut {rezultat!r}" ) def test_motiv_uman_unmapped_neschimbat(): """Ramura unmapped inca functioneaza dupa adaugarea ramurii 3-niveluri.""" rar_err = json.dumps({"unmapped": [{"cod_op_service": "OP-99", "denumire": "Test"}]}) rezultat = motiv_uman("needs_mapping", rar_err) assert rezultat.startswith("Cod RAR lipsa pentru:"), ( f"Ramura unmapped regresta. Obtinut: {rezultat!r}" ) assert "OP-99" in rezultat def test_parse_erori_gol_returneaza_lista_goala(): """BUG 2: parse_erori pe dict/lista goala -> [], nu 1 element cu problema=''.""" r1 = parse_erori("{}") assert r1 == [], f"parse_erori('{{}}') -> {r1!r}, asteptat []" r2 = parse_erori("[{}]") assert r2 == [], f"parse_erori('[{{}}]') -> {r2!r}, asteptat []"