feat(T3): validare completa prezentari + 29 teste
- app/validation.py: reguli de continut (VIN ^[A-HJ-NPR-Z0-9]{17}$ fara O/I/Q,
nrInm ^[A-Z0-9]{1,10}$, dataPrestatie ∈ [2024-12-01, azi] TZ Bucuresti,
R-ODO/I-ODO -> odometruInitial obligatoriu, odometruInitial<=odometruFinal,
odometruFinal numeric, prestatii nevide, b64Image base64 valid)
- erori structurate {field, message} (aceeasi forma ca raspunsul RAR), fara exceptii
- modele Pydantic: normalizare strip/upper pe vin/nrInm/coduri
- router /v1/prezentari: validare inainte de enqueue; esec continut -> needs_data
(tinut, vizibil in dashboard cu motiv), NU 422; JSON malformat -> 422 (shape)
- tests/: 29 teste (per regula + rutare API + idempotenta)
Verify: pytest 29 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
71
tests/test_api.py
Normal file
71
tests/test_api.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Teste API /v1/prezentari — rutare validare (T3) + idempotenta."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(monkeypatch):
|
||||
# DB temporara izolata per test.
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
from app.config import get_settings
|
||||
get_settings.cache_clear() # reincarca settings cu noul env
|
||||
from app.main import app
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def _body(**over):
|
||||
prez = {
|
||||
"vin": "WVWZZZ1KZAW000123",
|
||||
"nr_inmatriculare": "B999TST",
|
||||
"data_prestatie": "2026-06-15",
|
||||
"odometru_final": "123456",
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez]}
|
||||
|
||||
|
||||
def test_prezentare_valida_queued(client):
|
||||
r = client.post("/v1/prezentari", json=_body())
|
||||
assert r.status_code == 200
|
||||
assert r.json()["results"][0]["status"] == "queued"
|
||||
|
||||
|
||||
def test_vin_invalid_needs_data(client):
|
||||
r = client.post("/v1/prezentari", json=_body(vin="WVWZZZ1OZIQ45678"))
|
||||
assert r.status_code == 200
|
||||
sid = r.json()["results"][0]
|
||||
assert sid["status"] == "needs_data"
|
||||
|
||||
|
||||
def test_data_viitoare_needs_data(client):
|
||||
r = client.post("/v1/prezentari", json=_body(data_prestatie="2099-01-01"))
|
||||
assert r.json()["results"][0]["status"] == "needs_data"
|
||||
|
||||
|
||||
def test_idempotenta_dedup(client):
|
||||
b = _body()
|
||||
r1 = client.post("/v1/prezentari", json=b)
|
||||
r2 = client.post("/v1/prezentari", json=b)
|
||||
id1 = r1.json()["results"][0]["submission_id"]
|
||||
res2 = r2.json()["results"][0]
|
||||
assert res2["submission_id"] == id1
|
||||
assert res2["deduped"] is True
|
||||
|
||||
|
||||
def test_json_malformat_422(client):
|
||||
# Lipseste vin -> validare de shape Pydantic -> 422 (NU needs_data).
|
||||
bad = {"rar_credentials": {"email": "x", "password": "y"},
|
||||
"prezentari": [{"nr_inmatriculare": "B1", "data_prestatie": "2026-06-15",
|
||||
"odometru_final": "1", "prestatii": [{"cod_prestatie": "OE-1"}]}]}
|
||||
r = client.post("/v1/prezentari", json=bad)
|
||||
assert r.status_code == 422
|
||||
139
tests/test_validation.py
Normal file
139
tests/test_validation.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Teste T3 — validare de domeniu prezentari (app.validation).
|
||||
|
||||
Un test per regula din plan.md sect. 2 / contract. Validarea normalizeaza
|
||||
(strip/upper) si intoarce erori structurate {field, message}; nu ridica exceptii.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models import PrezentareIn
|
||||
from app.validation import today_bucuresti, validate_prezentare
|
||||
|
||||
|
||||
def _base(**overrides) -> dict:
|
||||
"""Prezentare valida ca dict (normalizat prin PrezentareIn), cu suprascrieri."""
|
||||
data = {
|
||||
"vin": "WVWZZZ1KZAW000123",
|
||||
"nr_inmatriculare": "B999TST",
|
||||
"data_prestatie": "2026-06-15",
|
||||
"odometru_final": "123456",
|
||||
"odometru_initial": None,
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
"sistem_reparat": "null",
|
||||
}
|
||||
data.update(overrides)
|
||||
return PrezentareIn(**data).model_dump()
|
||||
|
||||
|
||||
def _fields(errors: list[dict]) -> set[str]:
|
||||
return {e["field"] for e in errors}
|
||||
|
||||
|
||||
def test_prezentare_valida_fara_erori():
|
||||
assert validate_prezentare(_base()) == []
|
||||
|
||||
|
||||
def test_normalizare_strip_upper():
|
||||
c = _base(vin=" wvwzzz1kzaw000123 ", nr_inmatriculare=" b999tst ")
|
||||
assert c["vin"] == "WVWZZZ1KZAW000123"
|
||||
assert c["nr_inmatriculare"] == "B999TST"
|
||||
assert validate_prezentare(c) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("vin", [
|
||||
"WVWZZ1KZAW000123", # 16 caractere
|
||||
"WVWZZZ1KZAW0001234", # 18 caractere
|
||||
"WVWZZZ1OZAW000123", # contine O
|
||||
"WVWZZZ1IZAW000123", # contine I
|
||||
"WVWZZZ1QZAW000123", # contine Q
|
||||
"WVW ZZ1KZAW00012", # spatiu
|
||||
])
|
||||
def test_vin_invalid(vin):
|
||||
errors = validate_prezentare(_base(vin=vin))
|
||||
assert "vin" in _fields(errors)
|
||||
|
||||
|
||||
def test_nrinmatriculare_prea_lung():
|
||||
errors = validate_prezentare(_base(nr_inmatriculare="ABCDEFGHIJK")) # 11
|
||||
assert "nr_inmatriculare" in _fields(errors)
|
||||
|
||||
|
||||
def test_nrinmatriculare_caracter_special():
|
||||
errors = validate_prezentare(_base(nr_inmatriculare="B-99"))
|
||||
assert "nr_inmatriculare" in _fields(errors)
|
||||
|
||||
|
||||
def test_data_prea_veche():
|
||||
errors = validate_prezentare(_base(data_prestatie="2024-11-30"))
|
||||
assert "data_prestatie" in _fields(errors)
|
||||
|
||||
|
||||
def test_data_la_limita_inferioara_ok():
|
||||
assert validate_prezentare(_base(data_prestatie="2024-12-01")) == []
|
||||
|
||||
|
||||
def test_data_in_viitor():
|
||||
maine = (today_bucuresti() + timedelta(days=1)).isoformat()
|
||||
errors = validate_prezentare(_base(data_prestatie=maine))
|
||||
assert "data_prestatie" in _fields(errors)
|
||||
|
||||
|
||||
def test_data_azi_ok():
|
||||
azi = today_bucuresti().isoformat()
|
||||
assert validate_prezentare(_base(data_prestatie=azi)) == []
|
||||
|
||||
|
||||
def test_data_format_invalid():
|
||||
errors = validate_prezentare(_base(data_prestatie="15-06-2026"))
|
||||
assert "data_prestatie" in _fields(errors)
|
||||
|
||||
|
||||
def test_odometru_final_nenumeric():
|
||||
errors = validate_prezentare(_base(odometru_final="abc"))
|
||||
assert "odometru_final" in _fields(errors)
|
||||
|
||||
|
||||
def test_rodo_fara_odometru_initial():
|
||||
errors = validate_prezentare(_base(prestatii=[{"cod_prestatie": "R-ODO"}]))
|
||||
assert "odometru_initial" in _fields(errors)
|
||||
|
||||
|
||||
def test_iodo_fara_odometru_initial():
|
||||
errors = validate_prezentare(_base(prestatii=[{"cod_prestatie": "I-ODO"}]))
|
||||
assert "odometru_initial" in _fields(errors)
|
||||
|
||||
|
||||
def test_rodo_cu_odometru_initial_ok():
|
||||
c = _base(prestatii=[{"cod_prestatie": "R-ODO"}], odometru_initial="100000")
|
||||
assert validate_prezentare(c) == []
|
||||
|
||||
|
||||
def test_odometru_initial_mai_mare_decat_final():
|
||||
c = _base(prestatii=[{"cod_prestatie": "R-ODO"}], odometru_initial="200000", odometru_final="100000")
|
||||
assert "odometru_initial" in _fields(errors := validate_prezentare(c))
|
||||
assert any("<=" in e["message"] for e in errors)
|
||||
|
||||
|
||||
def test_prestatii_goale():
|
||||
errors = validate_prezentare(_base(prestatii=[]))
|
||||
assert "prestatii" in _fields(errors)
|
||||
|
||||
|
||||
def test_b64image_invalid():
|
||||
errors = validate_prezentare(_base(b64_image="@@@not-base64@@@"))
|
||||
assert "b64_image" in _fields(errors)
|
||||
|
||||
|
||||
def test_b64image_valid_ok():
|
||||
import base64
|
||||
good = base64.b64encode(b"poza odometru").decode()
|
||||
assert validate_prezentare(_base(b64_image=good)) == []
|
||||
|
||||
|
||||
def test_erori_multiple_cumulate():
|
||||
errors = validate_prezentare(_base(vin="BAD", nr_inmatriculare="X-Y", data_prestatie="2024-01-01"))
|
||||
assert {"vin", "nr_inmatriculare", "data_prestatie"} <= _fields(errors)
|
||||
Reference in New Issue
Block a user