"""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 # US-004: panoul foloseste ruta plurala (un singur form pentru toate operatiile) assert "/_import/%d/mapare-operatii" % 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