"""Teste US-004 — un singur „Salveaza maparile" pe panoul de operatii nemapate. Ruta noua: POST /_import/{id}/mapare-operatii (plural) - primeste perechi (cod_op_service, cod_prestatie) ca liste paralele - apeleaza save_mapping pentru fiecare pereche cu cod ales (reuse exact) - ignora perechile cu cod_prestatie gol (nu eroare, nu salvare) - D#12: validare per-item — cod invalid -> skip + sumar, restul salvate - O singura _web_compute_preview + re-randare #import-section la final - CSRF + scoped sesiune + guard batch committed (409) pastrate """ 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_mapare_operatie). 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() # ------------------------------------------------------------------ # # Helpers de setup # # ------------------------------------------------------------------ # _HEADER = ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Cod operatie", "Denumire"] _CANON = ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie", "denumire_op"] # Doua operatii distincte: OP-REV si OP-FR _ROWS_2OPS = [ ["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "OP-REV", "Revizie periodica"], ["WVWZZZ1KZAW002222", "CJ200AB", "2026-05-20", "98765", "OP-REV", "Revizie periodica"], ["WVWZZZ1KZAW003333", "IS300CD", "2026-04-10", "50000", "OP-FR", "Franare"], ] # O singura operatie _ROWS_1OP = [ ["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "OP-REV", "Revizie periodica"], ["WVWZZZ1KZAW002222", "CJ200AB", "2026-05-20", "98765", "OP-REV", "Revizie periodica"], ] 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, rows=None) -> int: """Incarca fisier CSV si intoarce import_id.""" rows = _ROWS_2OPS 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", }, ) def _get_batch_counts(import_id): from app.db import get_connection conn = get_connection() try: return conn.execute( "SELECT ok, needs_mapping FROM import_batches WHERE id=?", (import_id,) ).fetchone() finally: conn.close() def _get_row_statuses(import_id): from app.db import get_connection conn = get_connection() try: rows = conn.execute( "SELECT resolved_status FROM import_rows WHERE batch_id=? ORDER BY row_index", (import_id,), ).fetchall() return [r["resolved_status"] for r in rows] finally: conn.close() def _get_mapping(cod_op_service, account_id=1): from app.db import get_connection conn = get_connection() try: return conn.execute( "SELECT cod_prestatie FROM operations_mapping WHERE account_id=? AND cod_op_service=?", (account_id, cod_op_service), ).fetchone() finally: conn.close() # ------------------------------------------------------------------ # # 1. Salveaza multiple operatii intr-un singur POST # # ------------------------------------------------------------------ # def test_mapare_operatii_salveaza_multiple_intr_un_post(client): """POST mapare-operatii cu 2 operatii alese -> ambele salvate, randurile trec la ok.""" import_id = _upload(client) r = _map_columns(client, import_id) assert r.status_code == 200 # Inainte: 0 ok, 3 needs_mapping b = _get_batch_counts(import_id) assert b["needs_mapping"] == 3 assert b["ok"] == 0 # Un singur POST cu ambele operatii rm = client.post( f"/_import/{import_id}/mapare-operatii", data={ "cod_op_service": ["OP-REV", "OP-FR"], "cod_prestatie": ["OE-3", "OE-1"], }, ) assert rm.status_code == 200, rm.text # Raspuns e preview re-randat cu #import-section assert "import-section" in rm.text # Ambele mapari persistate assert _get_mapping("OP-REV") is not None assert _get_mapping("OP-REV")["cod_prestatie"] == "OE-3" assert _get_mapping("OP-FR") is not None assert _get_mapping("OP-FR")["cod_prestatie"] == "OE-1" # Toate randurile trecute la ok b2 = _get_batch_counts(import_id) assert b2["ok"] == 3, b2 assert b2["needs_mapping"] == 0, b2 statuses = _get_row_statuses(import_id) assert all(s == "ok" for s in statuses), statuses def test_panoul_mapare_are_un_singur_form(client): """Preview-ul randeaza panoul de mapare cu un singur