Catalog central pur app/errors.py ca sursa unica cod->{problema,fix},
consumat de API+UI+worker. Aditiv (field/message pastrate la octet) +
rar_error stocat superset. Scope: fluxul de declarare; login/signup/CSRF
neatinse. labels.parse_erori degradeaza gratios; UI progresiv AA light+dark.
631 teste.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
201 lines
6.7 KiB
Python
201 lines
6.7 KiB
Python
"""Teste TDD pentru POST /v1/prezentari/valideaza (dry-run, PRD 5.2 US-001)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
with TestClient(app) as c:
|
|
yield c
|
|
get_settings.cache_clear()
|
|
|
|
|
|
# --- helpere ---
|
|
|
|
def _prez(**over):
|
|
"""Prezentare valida implicita."""
|
|
p = {
|
|
"vin": "WVWZZZ1KZAW000123",
|
|
"nr_inmatriculare": "B999TST",
|
|
"data_prestatie": "2026-06-15",
|
|
"odometru_final": "123456",
|
|
"prestatii": [{"cod_prestatie": "OE-1"}],
|
|
}
|
|
p.update(over)
|
|
return p
|
|
|
|
|
|
def _body_v(prezentari=None, **over):
|
|
"""Body pentru /valideaza — rar_credentials optional."""
|
|
if prezentari is None:
|
|
prezentari = [_prez(**over)]
|
|
return {"prezentari": prezentari}
|
|
|
|
|
|
# --- teste ---
|
|
|
|
def test_payload_valid_returneaza_queued(client):
|
|
r = client.post("/v1/prezentari/valideaza", json=_body_v())
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["valid"] is True
|
|
assert res["status_estimat"] == "queued"
|
|
assert res["erori"] == []
|
|
assert res["index"] == 0
|
|
|
|
|
|
def test_vin_invalid_returneaza_needs_data(client):
|
|
# VIN cu O/I/Q interzisi
|
|
r = client.post("/v1/prezentari/valideaza", json=_body_v(vin="WVWZZZ1OZIQ45678"))
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "needs_data"
|
|
assert res["valid"] is False
|
|
fields = [e["field"] for e in res["erori"]]
|
|
assert "vin" in fields
|
|
|
|
|
|
def test_data_viitoare_needs_data(client):
|
|
r = client.post("/v1/prezentari/valideaza", json=_body_v(data_prestatie="2099-01-01"))
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "needs_data"
|
|
assert res["valid"] is False
|
|
fields = [e["field"] for e in res["erori"]]
|
|
assert "data_prestatie" in fields
|
|
|
|
|
|
def test_cod_op_nemapat_returneaza_needs_mapping(client):
|
|
prez = _prez()
|
|
prez["prestatii"] = [{"cod_op_service": "REP_MOTOR_NECUNOSCUT", "denumire": "Reparatie motor"}]
|
|
r = client.post("/v1/prezentari/valideaza", json={"prezentari": [prez]})
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "needs_mapping"
|
|
assert res["valid"] is False
|
|
assert len(res["nemapate"]) == 1
|
|
assert res["nemapate"][0]["cod_op_service"] == "REP_MOTOR_NECUNOSCUT"
|
|
|
|
|
|
def test_mapare_existenta_rezolva_codul(client):
|
|
# Salveaza mapare op->cod
|
|
r_map = client.post("/v1/mapari", json={
|
|
"cod_op_service": "REP_MOTOR",
|
|
"cod_prestatie": "OE-1",
|
|
"auto_send": True,
|
|
})
|
|
assert r_map.status_code == 200
|
|
|
|
prez = _prez()
|
|
prez["prestatii"] = [{"cod_op_service": "REP_MOTOR", "denumire": "Reparatie motor"}]
|
|
r = client.post("/v1/prezentari/valideaza", json={"prezentari": [prez]})
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "queued"
|
|
assert res["valid"] is True
|
|
assert len(res["prestatii_rezolvate"]) == 1
|
|
assert res["prestatii_rezolvate"][0]["cod_prestatie"] == "OE-1"
|
|
|
|
|
|
def test_fara_creds_merge(client):
|
|
# rar_credentials absent -> 200 (optional in schema)
|
|
r = client.post("/v1/prezentari/valideaza", json=_body_v())
|
|
assert r.status_code == 200
|
|
|
|
|
|
def test_nu_scrie_in_coada(client):
|
|
# Verifica zero efecte secundare: COUNT(*) neschimbat
|
|
r_before = client.get("/v1/prezentari")
|
|
assert r_before.status_code == 200
|
|
nr_before = len(r_before.json()["submissions"])
|
|
|
|
client.post("/v1/prezentari/valideaza", json=_body_v())
|
|
|
|
r_after = client.get("/v1/prezentari")
|
|
assert r_after.status_code == 200
|
|
nr_after = len(r_after.json()["submissions"])
|
|
|
|
assert nr_after == nr_before
|
|
|
|
|
|
def test_multi_prezentari_rezultate_per_index(client):
|
|
prezentari = [
|
|
_prez(), # valid -> queued
|
|
_prez(vin="WVWZZZ1OZIQ45678"), # VIN invalid -> needs_data
|
|
]
|
|
r = client.post("/v1/prezentari/valideaza", json={"prezentari": prezentari})
|
|
assert r.status_code == 200
|
|
results = r.json()["results"]
|
|
assert len(results) == 2
|
|
# index corect per pozitie
|
|
assert results[0]["index"] == 0
|
|
assert results[1]["index"] == 1
|
|
# statusuri diferite
|
|
assert results[0]["status_estimat"] == "queued"
|
|
assert results[1]["status_estimat"] == "needs_data"
|
|
|
|
|
|
def test_shape_invalid_422(client):
|
|
# PrestatieItem fara cod_prestatie si fara cod_op_service -> 422 de shape Pydantic
|
|
prez = _prez()
|
|
prez["prestatii"] = [{"denumire": "ceva"}] # lipseste cod_prestatie si cod_op_service
|
|
r = client.post("/v1/prezentari/valideaza", json={"prezentari": [prez]})
|
|
assert r.status_code == 422
|
|
# Handlerul global dropeaza input/ctx — fara echo parola (desi creds lipseste, testam structura)
|
|
body = r.json()
|
|
for err in body.get("detail", []):
|
|
assert "input" not in err
|
|
assert "ctx" not in err
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# US-003: 3 niveluri in raspunsul /valideaza #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_erori_au_3niveluri(client):
|
|
"""/valideaza cu VIN invalid -> erori[i] au cod/problema/cauza/fix (pass-through US-002)."""
|
|
r = client.post("/v1/prezentari/valideaza", json=_body_v(vin="WVWZZZ1OZIQ45678"))
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "needs_data"
|
|
erori = res["erori"]
|
|
assert len(erori) >= 1
|
|
for e in erori:
|
|
assert "cod" in e, f"lipseste 'cod' in {e}"
|
|
assert "problema" in e, f"lipseste 'problema' in {e}"
|
|
assert "cauza" in e, f"lipseste 'cauza' in {e}"
|
|
assert "fix" in e, f"lipseste 'fix' in {e}"
|
|
|
|
|
|
def test_nemapate_au_3niveluri(client):
|
|
"""/valideaza cu cod_op nemapat -> nemapate[i] au cod_op_service+denumire PASTRATE
|
|
+ cod==COD_NEMAPAT + cele 3 niveluri."""
|
|
prez = _prez()
|
|
prez["prestatii"] = [{"cod_op_service": "OP_TEST_NEMAPAT", "denumire": "Operatie test"}]
|
|
r = client.post("/v1/prezentari/valideaza", json={"prezentari": [prez]})
|
|
assert r.status_code == 200
|
|
res = r.json()["results"][0]
|
|
assert res["status_estimat"] == "needs_mapping"
|
|
nemapate = res["nemapate"]
|
|
assert len(nemapate) == 1
|
|
n = nemapate[0]
|
|
# Campuri originale pastrate
|
|
assert n["cod_op_service"] == "OP_TEST_NEMAPAT"
|
|
assert n["denumire"] == "Operatie test"
|
|
# 3 niveluri adaugate
|
|
assert n["cod"] == "COD_NEMAPAT"
|
|
assert n["problema"]
|
|
assert n["cauza"]
|
|
assert n["fix"]
|