Files
rar-autopass/tests/test_validare_dryrun.py
Claude Agent ae7960294f feat(api): endpoint dry-run POST /v1/prezentari/valideaza (PRD 5.2)
Valideaza payload + mapare si intoarce verdictul real (status_estimat
queued/needs_data/needs_mapping + erori [{field,message}] + coduri nemapate
+ prestatii rezolvate) FARA enqueue, fara creds, zero scriere DB. "Magical
moment" pentru integratori (ROAAUTO / soft propriu / punte VFP).

Cheia de design: helper pur partajat classify_prezentare (mapping.py) folosit
de AMBELE rute, ca dry-run-ul sa nu poata diverge de trimiterea reala
(invariant de corectitudine). create_prezentari refactorizat pe el cu
comportament identic (test_api.py verde).

Scope minim (decizie user): doar validare+mapare, fara idempotency/duplicat
(idempotency.py neatins); descoperibilitate in hub /integrare amanata.

VERIFY context curat PASS (577 teste; E2E API cu cele 3 verdicte + COUNT(*)=0
dupa dry-run). /code-review high: 0 findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:54:50 +00:00

160 lines
5.1 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