Files
rar-autopass/tests/test_import_mapare_operatie.py
Claude Agent a40b20b3b4 feat(import): mapare operatie->cod RAR inline in preview + camp denumire_op
Inchide deadlock-ul din canalul de import web: operatiile nemapate dintr-un
batch in staging nu aveau unde sa fie mapate din UI. Editorul "Mapari de
rezolvat" citea doar din submissions comise, iar commit-ul arunca randurile
needs_mapping -> utilizatorul ramanea blocat fara a putea trimite.

- camp canonic nou `denumire_op`: coloana descriptiva (ex. "Reparatie Motor")
  alimenteaza denumirea operatiei, deci sugestia fuzzy devine utila (inainte
  denumire = codul opac). Aplicat in cele 3 locuri de resolve (preview, commit
  web, commit API).
- panou inline "Operatii de mapat la cod RAR" in preview: fiecare operatie
  nemapata cu sugestie preselectata + dropdown + auto-send + salveaza.
- ruta POST /_import/{id}/mapare-operatie: salveaza maparea (persistenta,
  operations_mapping) si re-randeaza preview-ul; randurile trec din
  needs_mapping in ok fara re-upload, maparea se retine pentru fisiere viitoare.
- fix bug pre-existent de semnatura coloane: semnatura se calcula din campurile
  mapate (json_mapare.keys), nu din antetul complet -> ignorarea unei coloane
  schimba semnatura si maparea retinuta nu mai era gasita la preview/re-upload.
  Acum mereu din antetul complet (web + API), consecvent cu preview/commit.

Teste noi: tests/test_import_mapare_operatie.py (6). Suita: 400 passed.

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

249 lines
8.6 KiB
Python

"""Teste flux mapare operatie inline in preview (import web in staging).
Acopera gap-ul: operatiile nemapate dintr-un import in staging nu aveau unde sa
fie mapate din UI (editorul "Mapari de rezolvat" citea doar din submissions
comise, iar commit-ul arunca randurile needs_mapping). Acum:
- camp canonic nou `denumire_op`: coloana descriptiva alimenteaza denumirea
operatiei -> sugestia fuzzy devine utila (nu codul opac);
- preview-ul expune `unmapped_ops` + panou inline de mapare;
- POST /_import/{id}/mapare-operatie salveaza maparea (persistenta) si
re-rezolva preview-ul -> randurile trec din needs_mapping in ok, fara re-upload.
"""
from __future__ import annotations
import csv as csv_mod
import io
import os
import re
import tempfile
import pytest
@pytest.fixture()
def client(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
# Mod dev: fallback cont 1, fara login/CSRF (ca in test_import_ui).
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()
_HEADER = ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Cod operatie", "Denumire"]
# Cod intern opac + descriere lizibila care se potriveste cu nomenclatorul RAR
# (OE-3 = "REVIZIE PERIODICA"; fuzzy "Revizie periodica" -> OE-3 la 100%).
_ROWS = [
["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "OP-REV", "Revizie periodica"],
["WVWZZZ1KZAW002222", "CJ200AB", "2026-05-20", "98765", "OP-REV", "Revizie periodica"],
]
_CANON = ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie", "denumire_op"]
def _csv_bytes(header, rows, sep=";") -> bytes:
buf = io.StringIO()
w = csv_mod.writer(buf, delimiter=sep)
w.writerow(header)
for r in rows:
w.writerow(r)
return buf.getvalue().encode("utf-8")
def _upload(client, header=_HEADER, rows=None) -> int:
rows = _ROWS if rows is None else rows
r = client.post(
"/_import/upload",
files={"file": ("t.csv", _csv_bytes(header, rows), "text/csv")},
)
assert r.status_code == 200, r.text
m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
assert m, f"form mapare-coloane lipsa: {r.text[:300]}"
return int(m.group(1))
def _map_columns(client, import_id, canon=None):
return client.post(
f"/_import/{import_id}/mapare-coloane",
data={
"colname": _HEADER,
"canon": canon or _CANON,
"format_data": "YYYY-MM-DD",
},
)
# --------------------------------------------------------------------------- #
# 1. denumire_op alimenteaza denumirea -> sugestie fuzzy utila #
# --------------------------------------------------------------------------- #
def test_denumire_op_alimenteaza_denumirea_si_sugestia(client):
import_id = _upload(client)
_map_columns(client, import_id)
from app.db import get_connection
from app.web.routes import _web_compute_preview
conn = get_connection()
try:
result = _web_compute_preview(conn, import_id, account_id=1)
finally:
conn.close()
assert not isinstance(result, str), result
ops = result["unmapped_ops"]
assert len(ops) == 1, ops
op = ops[0]
assert op["cod_op_service"] == "OP-REV"
# Cheia: denumirea e descrierea reala, NU codul opac.
assert op["denumire"] == "Revizie periodica"
assert op["blocked"] == 2
# Sugestia fuzzy gaseste OE-3 (REVIZIE PERIODICA) sus, cu scor real.
assert op["suggestions"], "fara sugestii"
top = op["suggestions"][0]
assert top["cod_prestatie"] == "OE-3"
assert top["score"] >= 60
def test_fara_denumire_op_denumirea_e_codul(client):
"""Control: daca NU mapezi coloana descriptiva, denumirea ramane codul opac."""
import_id = _upload(client)
# operatie mapat, descrierea ignorata
_map_columns(client, import_id, canon=[
"vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie", "",
])
from app.db import get_connection
from app.web.routes import _web_compute_preview
conn = get_connection()
try:
result = _web_compute_preview(conn, import_id, account_id=1)
finally:
conn.close()
op = result["unmapped_ops"][0]
assert op["denumire"] == "OP-REV" # fara denumire_op -> codul
# --------------------------------------------------------------------------- #
# 2. Preview expune panoul inline #
# --------------------------------------------------------------------------- #
def test_preview_arata_panoul_de_mapare(client):
import_id = _upload(client)
r = _map_columns(client, import_id)
assert r.status_code == 200
assert "Operatii de mapat la cod RAR" in r.text
assert "OP-REV" in r.text
assert "/_import/%d/mapare-operatie" % import_id in r.text
# --------------------------------------------------------------------------- #
# 3. POST mapare-operatie deblocheaza randurile (needs_mapping -> ok) #
# --------------------------------------------------------------------------- #
def test_mapare_operatie_deblocheaza_randurile(client):
import_id = _upload(client)
r = _map_columns(client, import_id)
assert "needs_mapping" in r.text
# Inainte: 2 needs_mapping, 0 ok
from app.db import get_connection
conn = get_connection()
try:
b = conn.execute(
"SELECT ok, needs_mapping FROM import_batches WHERE id=?", (import_id,)
).fetchone()
assert (b["ok"], b["needs_mapping"]) == (0, 2)
finally:
conn.close()
# Mapeaza OP-REV -> OE-3 (auto_send)
rm = client.post(f"/_import/{import_id}/mapare-operatie", data={
"cod_op_service": "OP-REV",
"cod_prestatie": "OE-3",
"auto_send": "true",
})
assert rm.status_code == 200, rm.text
# Preview re-randat: randurile sunt acum ok, panoul a disparut
assert "2 gata de trimis" in rm.text or "s-ok" in rm.text
assert f"/_import/{import_id}/mapare-operatie" not in rm.text
conn = get_connection()
try:
b = conn.execute(
"SELECT ok, needs_mapping FROM import_batches WHERE id=?", (import_id,)
).fetchone()
assert (b["ok"], b["needs_mapping"]) == (2, 0), dict(b)
# Maparea s-a persistat (operations_mapping)
m = conn.execute(
"SELECT cod_prestatie, auto_send FROM operations_mapping "
"WHERE account_id=1 AND cod_op_service='OP-REV'"
).fetchone()
assert m is not None and m["cod_prestatie"] == "OE-3" and m["auto_send"] == 1
# import_rows reflecta noua stare (commit-ul citeste de aici)
statuses = {
row["resolved_status"]
for row in conn.execute(
"SELECT resolved_status FROM import_rows WHERE batch_id=?", (import_id,)
)
}
assert statuses == {"ok"}, statuses
finally:
conn.close()
def test_mapare_operatie_cod_necunoscut_nu_salveaza(client):
import_id = _upload(client)
_map_columns(client, import_id)
rm = client.post(f"/_import/{import_id}/mapare-operatie", data={
"cod_op_service": "OP-REV",
"cod_prestatie": "NUEXISTA",
"auto_send": "true",
})
assert rm.status_code == 200
assert "necunoscut" in rm.text.lower()
# Inca needs_mapping, nimic salvat
assert "Operatii de mapat la cod RAR" in rm.text
from app.db import get_connection
conn = get_connection()
try:
m = conn.execute(
"SELECT 1 FROM operations_mapping WHERE cod_op_service='OP-REV'"
).fetchone()
assert m is None
finally:
conn.close()
# --------------------------------------------------------------------------- #
# 4. A doua incarcare: maparea retinuta -> direct ok (zero config) #
# --------------------------------------------------------------------------- #
def test_a_doua_incarcare_foloseste_maparea_retinuta(client):
# Prima incarcare + mapare coloane + mapare operatie
import_id = _upload(client)
_map_columns(client, import_id)
client.post(f"/_import/{import_id}/mapare-operatie", data={
"cod_op_service": "OP-REV", "cod_prestatie": "OE-3", "auto_send": "true",
})
# A doua incarcare acelasi antet -> preview direct, fara operatii de mapat
r = client.post(
"/_import/upload",
files={"file": ("t2.csv", _csv_bytes(_HEADER, _ROWS), "text/csv")},
)
assert r.status_code == 200, r.text
assert "/mapare-operatie" not in r.text
assert "gata de trimis" in r.text