5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata) inchise dupa /code-review high. 8 buguri reparate TDD: - HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim) - HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare peste existing, codes pozitional - HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus() - HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile - MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs='' - MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard - MED typo nome_prestatie -> nume_prestatie in select /repune - MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default off). Marime model corectata ~50MB->~230MB (estimare PRD gresita). Cleanup: hoist load_* din bucla bulk-fix; import re la top. Regresie: 1256 passed, 1 deselected (live), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
4.5 KiB
Python
124 lines
4.5 KiB
Python
"""Teste dashboard + audit CSV: nomenclator browser, stare RAR, export CSV."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import io
|
|
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"))
|
|
# Comportament in mod dev (fallback cont 1, fara login/CSRF); auth web e
|
|
# default ON in prod — testat separat in test_web_*.
|
|
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
|
|
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()
|
|
|
|
|
|
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]}
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Dashboard render + fragmente #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_dashboard_renders_with_rar_state(client):
|
|
r = client.get("/")
|
|
assert r.status_code == 200
|
|
# Dupa US-003 bara de status e incarcata via HTMX (hx-trigger=load, every 15s)
|
|
assert "/_fragments/status" in r.text, "Dashboard-ul trebuie sa referenceze fragmentul de status"
|
|
# Fragmentul de status contine starea de sanatate (text uman, nu brut tehnic)
|
|
rs = client.get("/_fragments/status")
|
|
assert rs.status_code == 200
|
|
# US-003 D6: strip sanatate unificat — "declaratiile" apare in orice stare (curg/blocat)
|
|
assert "declaratiile" in rs.text.lower(), (
|
|
f"Strip sanatate lipseste din fragment. HTML: {rs.text[:500]}"
|
|
)
|
|
# Tab-ul Nomenclator e accesat via /_fragments/nomenclator
|
|
rn = client.get("/_fragments/nomenclator")
|
|
assert rn.status_code == 200
|
|
assert "Nomenclator" in rn.text or "Cod" in rn.text or "OE-1" in rn.text
|
|
|
|
|
|
def test_nomenclator_fragment_lists_seed(client):
|
|
r = client.get("/_fragments/nomenclator")
|
|
assert r.status_code == 200
|
|
# seed fallback are 18 coduri; OE-1 + R-ODO trebuie sa apara
|
|
assert "OE-1" in r.text
|
|
assert "R-ODO" in r.text
|
|
|
|
|
|
def test_submissions_fragment_empty_state(client):
|
|
r = client.get("/_fragments/submissions")
|
|
assert r.status_code == 200
|
|
# US-005: empty state prietenos cu indemn la Import (nu mesajul tehnic vechi)
|
|
assert "Nicio trimitere" in r.text or "incepe cu Import" in r.text or "?tab=import" in r.text
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Audit CSV export #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_audit_export_sent_only(client):
|
|
# un submission queued (validare ok) + unul needs_data
|
|
client.post("/v1/prezentari", json=_body())
|
|
client.post("/v1/prezentari", json=_body(vin="BAD"))
|
|
|
|
# status implicit = sent -> niciun rand (nimic trimis inca), doar header
|
|
r = client.get("/v1/audit/export")
|
|
assert r.status_code == 200
|
|
assert r.headers["content-type"].startswith("text/csv")
|
|
assert "attachment" in r.headers["content-disposition"]
|
|
rows = list(csv.DictReader(io.StringIO(r.text)))
|
|
assert rows == []
|
|
|
|
# status=all -> ambele, cu coloane-cheie populate, fara b64_image
|
|
r = client.get("/v1/audit/export?status=all")
|
|
rows = list(csv.DictReader(io.StringIO(r.text)))
|
|
assert len(rows) == 2
|
|
assert "vin" in rows[0]
|
|
assert "b64_image" not in rows[0]
|
|
vins = {row["vin"] for row in rows}
|
|
assert "WVWZZZ1KZAW000123" in vins
|
|
# prestatii = coduri concatenate
|
|
assert any(row["prestatii"] == "OE-1" for row in rows)
|
|
|
|
|
|
def test_audit_export_marks_sent_after_update(client):
|
|
client.post("/v1/prezentari", json=_body())
|
|
# marcam manual sent (worker ar face asta dupa postPrezentare reusit)
|
|
from app.db import get_connection
|
|
|
|
conn = get_connection()
|
|
try:
|
|
conn.execute("UPDATE submissions SET status='sent', id_prezentare=68514 WHERE id=1")
|
|
finally:
|
|
conn.close()
|
|
|
|
rows = list(csv.DictReader(io.StringIO(client.get("/v1/audit/export").text)))
|
|
assert len(rows) == 1
|
|
assert rows[0]["status"] == "sent"
|
|
assert rows[0]["id_prezentare"] == "68514"
|