"""Teste UI web import (U5) — upload → mapare coloane → preview → confirmare. Verifica: - Dashboard randeaza sectiunea de upload - Upload xlsx → mapare noua → fragment _mapcoloane returnat - Upload xlsx cu mapare existenta → preview direct - Salvare mapare coloane → preview randat - Preview afiseaza rezumat stari si randul tabelului - Confirmare cu N corect → succes (in coada) - Confirmare cu N gresit → eroare explicita - Reset → drop zone gol - Erori upload (fisier invalid, prea mare, header neclar) - Sheet selector la multi-sheet xlsx """ from __future__ import annotations import io import os import tempfile import pytest @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 from fastapi.testclient import TestClient with TestClient(app) as c: yield c get_settings.cache_clear() def _make_xlsx_bytes(rows: list[dict]) -> bytes: """Construieste un xlsx minimal cu openpyxl pentru fixture teste.""" openpyxl = pytest.importorskip("openpyxl") wb = openpyxl.Workbook() ws = wb.active if not rows: return b"" headers = list(rows[0].keys()) ws.append(headers) for row in rows: ws.append([row.get(h) for h in headers]) buf = io.BytesIO() wb.save(buf) return buf.getvalue() def _make_csv_bytes(rows: list[dict], sep: str = ";") -> bytes: """Construieste un CSV minimal pentru fixture teste.""" import csv import io buf = io.StringIO() if not rows: return b"" writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()), delimiter=sep) writer.writeheader() writer.writerows(rows) return buf.getvalue().encode("utf-8") _SAMPLE_ROWS = [ { "VIN": "WVWZZZ1KZAW000123", "Nr inmatriculare": "B001TST", "Data prestatie": "15.06.2026", "Odometru final": "123456", "Operatie": "Revizie", }, { "VIN": "WVWZZZ1KZAW000456", "Nr inmatriculare": "B002TST", "Data prestatie": "16.06.2026", "Odometru final": "200000", "Operatie": "Revizie", }, ] def _seed_op_mapping(client, cod_op: str = "Revizie", cod_prest: str = "OE-1") -> None: """Seeda o mapare de operatii cod_op → cod_prestatie via API.""" client.post("/v1/mapari", json={ "cod_op_service": cod_op, "cod_prestatie": cod_prest, "auto_send": True, }) # --------------------------------------------------------------------------- # # Dashboard # # --------------------------------------------------------------------------- # def test_dashboard_contine_drop_zone(client): """Tab-ul Import randeaza sectiunea de upload cu drop zone si mesaj warmth. Dupa US-003 sectiunea de import e in tab-ul Import (?tab=import), nu pe pagina principala. """ r = client.get("/?tab=import") assert r.status_code == 200 assert "Primul fisier" in r.text assert "drop-zone" in r.text assert "NU se trimite nimic" in r.text assert "import-section" in r.text # --------------------------------------------------------------------------- # # Upload xlsx — mapare noua # # --------------------------------------------------------------------------- # def test_upload_xlsx_fara_mapare_arata_formular_mapare(client): """Upload xlsx fara mapare salvata → fragment mapare coloane.""" xlsx = _make_xlsx_bytes(_SAMPLE_ROWS) r = client.post( "/_import/upload", files={"file": ("test.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, ) assert r.status_code == 200 # Formular de mapare coloane assert "Mapare coloane" in r.text assert "mapare-coloane" in r.text # URL in form action # Coloanele din fisier apar in formular assert "VIN" in r.text assert "Data prestatie" in r.text # Sugestii fuzzy pentru VIN assert "vin" in r.text.lower() def test_upload_csv_fara_mapare_arata_formular_mapare(client): """Upload CSV cu separator ; → formular mapare coloane.""" csv_bytes = _make_csv_bytes(_SAMPLE_ROWS) r = client.post( "/_import/upload", files={"file": ("test.csv", csv_bytes, "text/csv")}, ) assert r.status_code == 200 assert "Mapare coloane" in r.text # --------------------------------------------------------------------------- # # Salvare mapare coloane → preview # # --------------------------------------------------------------------------- # def _upload_and_get_import_id(client, rows=None) -> int: """Helper: incarca fisier si extrage import_id din raspuns.""" xlsx = _make_xlsx_bytes(rows or _SAMPLE_ROWS) r = client.post( "/_import/upload", files={"file": ("test.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, ) assert r.status_code == 200 # Extrage import_id din URL-ul form action din raspuns text = r.text # Form action contine /_import/{id}/mapare-coloane import re m = re.search(r"/_import/(\d+)/mapare-coloane", text) assert m, f"Nu s-a gasit import_id in raspuns: {text[:500]}" return int(m.group(1)) def test_salvare_mapare_coloane_arata_preview(client): """Dupa salvarea maparii de coloane, raspunsul contine preview-ul.""" # Asigura ca nomenclatorul are OE-1 (seeding automat la init_db) import_id = _upload_and_get_import_id(client) r = client.post( f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) assert r.status_code == 200 # Preview trebuie sa contina elementele cheie assert "Preview" in r.text assert "confirm-form" in r.text assert "n-confirmat" in r.text # Rezumat stari assert "gata de trimis" in r.text or "ok" in r.text def test_preview_arata_randul_vin(client): """Preview contine VIN-ul din fisier.""" import_id = _upload_and_get_import_id(client) client.post( f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) r = client.get(f"/_import/{import_id}/preview") assert r.status_code == 200 assert "WVWZZZ1KZAW000123" in r.text # --------------------------------------------------------------------------- # # Upload cu mapare existenta → preview direct # # --------------------------------------------------------------------------- # def test_upload_cu_mapare_existenta_sare_direct_la_preview(client): """Al doilea upload cu acelasi antet → preview imediat (mapare retinuta).""" # Primul upload + salvare mapare import_id1 = _upload_and_get_import_id(client) client.post( f"/_import/{import_id1}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) # Al doilea upload cu acelasi antet xlsx = _make_xlsx_bytes(_SAMPLE_ROWS) r = client.post( "/_import/upload", files={"file": ("test2.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, ) assert r.status_code == 200 # Trebuie sa ajunga direct la preview, nu la mapare assert "Preview" in r.text assert "confirm-form" in r.text assert "Mapare retinuta aplicata automat" in r.text # --------------------------------------------------------------------------- # # Confirmare (gate HARD) # # --------------------------------------------------------------------------- # def _setup_preview(client) -> int: """Upload + mapare + seeda operatii + intoarce import_id gata de confirmare.""" _seed_op_mapping(client) # "Revizie" → "OE-1" import_id = _upload_and_get_import_id(client) client.post( f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) return import_id def test_confirmare_n_corect_pune_in_coada(client): """Confirmare cu N corect → randurile ok ajung in coada.""" import_id = _setup_preview(client) # Compute preview pentru a afla n_ok r_prev = client.get(f"/_import/{import_id}/preview") assert r_prev.status_code == 200 # Citeste summary din DB from app.db import get_connection conn = get_connection() try: batch = conn.execute( "SELECT ok FROM import_batches WHERE id=?", (import_id,) ).fetchone() n_ok = batch["ok"] finally: conn.close() assert n_ok > 0, "Asteptat cel putin un rand ok dupa seeding corect" r = client.post( f"/_import/{import_id}/confirma", data={"n_confirmat": str(n_ok), "confirmed_by": "test@test.ro"}, ) assert r.status_code == 200 # Succes → drop zone cu mesaj assert "S-au pus in coada" in r.text or "prezentari" in r.text # Sectiunea se reseteaza la drop zone assert "drop-zone" in r.text # Verifica ca submissions au fost create conn2 = get_connection() try: n = conn2.execute( "SELECT COUNT(*) FROM submissions WHERE batch_id=?", (import_id,) ).fetchone()[0] assert n == n_ok, f"Asteptat {n_ok} submissions, gasit {n}" finally: conn2.close() def test_confirmare_n_gresit_arata_eroare(client): """Confirmare cu N gresit → eroare clara, nu enqueue.""" import_id = _setup_preview(client) client.get(f"/_import/{import_id}/preview") # calculeaza si stocheaza starea r = client.post( f"/_import/{import_id}/confirma", data={"n_confirmat": "99", "confirmed_by": ""}, ) assert r.status_code == 200 # Trebuie sa arate eroare de confirmare sau preview cu eroare assert ( "difera" in r.text or "Numarul confirmat" in r.text or "Niciun rand ok" in r.text ) # --------------------------------------------------------------------------- # # Reset # # --------------------------------------------------------------------------- # def test_reset_arata_drop_zone_gol(client): """GET /_import/reset → drop zone gol fara mesaje.""" r = client.get("/_import/reset") assert r.status_code == 200 assert "drop-zone" in r.text assert "Primul fisier" in r.text assert "import-section" in r.text # --------------------------------------------------------------------------- # # Erori upload # # --------------------------------------------------------------------------- # def test_upload_fisier_invalid_arata_eroare(client): """Upload fisier invalid → mesaj de eroare in drop zone.""" r = client.post( "/_import/upload", files={"file": ("test.xlsx", b"not a real xlsx file", "application/octet-stream")}, ) assert r.status_code == 200 # Trebuie sa arate drop zone cu eroare assert "drop-zone" in r.text or "import-section" in r.text # Eroare vizibila assert "nerecunoscut" in r.text.lower() or "invalid" in r.text.lower() or "eroare" in r.text.lower() def test_upload_fisier_csv_antet_o_coloana_arata_eroare(client): """CSV cu o singura coloana reala → header acceptat sau eroare gestionata.""" bad = b"date_fara_header\nval1\nval2\n" r = client.post( "/_import/upload", files={"file": ("test.csv", bad, "text/csv")}, ) # Fie detecteaza header OK fie arata eroare assert r.status_code == 200 assert "import-section" in r.text # --------------------------------------------------------------------------- # # Multi-sheet xlsx # # --------------------------------------------------------------------------- # def test_upload_multi_sheet_arata_selector(client): """xlsx cu mai multe foi → selector de foaie in drop zone.""" openpyxl = pytest.importorskip("openpyxl") wb = openpyxl.Workbook() ws1 = wb.active ws1.title = "Date" ws1.append(["VIN", "Data"]) ws1.append(["WVW001", "15.06.2026"]) ws2 = wb.create_sheet("Raport") ws2.append(["Total"]) ws2.append(["1"]) buf = io.BytesIO() wb.save(buf) xlsx = buf.getvalue() r = client.post( "/_import/upload", files={"file": ("multi.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, ) assert r.status_code == 200 # Trebuie sa arate selector de foi assert "foi" in r.text.lower() or "sheet" in r.text.lower() or "foaie" in r.text.lower() # Foile trebuie sa apara ca optiuni assert "Date" in r.text or "Raport" in r.text # --------------------------------------------------------------------------- # # Elemente a11y (D10/D11/D12) # # --------------------------------------------------------------------------- # def test_preview_contine_banner_declarant(client): """Preview contine bannerul declarant (D12) cu text despre ireversibil.""" import_id = _setup_preview(client) r = client.get(f"/_import/{import_id}/preview") assert r.status_code == 200 assert "declarantul" in r.text assert "ireversibil" in r.text assert "banner" in r.text def test_preview_contine_checkboxuri_needs_review(client): """Randurile needs_review au checkbox 'verificat' (D11).""" # Cream un rand cu VIN numeric → needs_review rows_with_review = [ { "VIN": "1234567890", # VIN numeric → coercion flag → needs_review "Nr inmatriculare": "B001TST", "Data prestatie": "15.06.2026", "Odometru final": "123456", "Operatie": "OE-1", } ] import_id = _upload_and_get_import_id(client, rows=rows_with_review) client.post( f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) r = client.get(f"/_import/{import_id}/preview") assert r.status_code == 200 # Checkbox reviewed_rows prezent pentru randul needs_review assert "reviewed_rows" in r.text assert "needs_review" in r.text def test_preview_duplicate_in_file_are_text(client): """Randurile duplicate_in_file arata text 'dubla cu randul N' (D10 — nu doar culoare).""" _seed_op_mapping(client, "Revizie", "OE-1") # Doua randuri identice → duplicate_in_file dup_rows = [ {"VIN": "WVWZZZ1KZAW000123", "Nr inmatriculare": "B001TST", "Data prestatie": "15.06.2026", "Odometru final": "123456", "Operatie": "Revizie"}, {"VIN": "WVWZZZ1KZAW000123", "Nr inmatriculare": "B001TST", "Data prestatie": "15.06.2026", "Odometru final": "123456", "Operatie": "Revizie"}, ] import_id = _upload_and_get_import_id(client, rows=dup_rows) client.post( f"/_import/{import_id}/mapare-coloane", data={ "colname": ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"], "canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"], "format_data": "DD.MM.YYYY", }, ) r = client.get(f"/_import/{import_id}/preview") assert r.status_code == 200 # Text explicit pentru duplicate_in_file (nu doar culoare — cerinta daltonism D10) assert "dubla cu randul" in r.text assert "duplicate_in_file" in r.text