Files
rar-autopass/tests/test_web_erori.py
Claude Agent fa52468a80 fix(ux): mesaje mediu RAR mai clare + referinta corecta 'Meniu cont › Cont'
Banner upload compact/directiv (fara 'mediu global' derutant), text Cont
onest (importul web fara mediu activ nu are creds -> esueaza), si aliniere
referinta navigare la 'Meniu cont › Cont' in erori/integrare (nu exista
'tab-ul Cont' in UI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:45:16 +00:00

286 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 Meniu cont 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 []"