"""Teste US-004: wizard import cu stepper vizual (4 pasi numerotati). TDD — testele sunt scrise INAINTE de implementare (RED), apoi se face GREEN. Verifica: - Pasul 1 activ (aria-current="step") in fragmentul de upload - Pasul 2 activ in fragmentul mapare-coloane - Pasul 3 activ in preview - Pasii 1 si 2 marcati ca "facuti" in preview (clasa/marcaj) - hx-target="#import-section" pastrat in fragmentele de import - csrf_token prezent in formularele de import """ from __future__ import annotations 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")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false") from app.config import get_settings get_settings.cache_clear() from app.web import ratelimit ratelimit._hits.clear() # izolare: limiterul login e global in-proces from app.main import app from fastapi.testclient import TestClient with TestClient(app) as c: yield c ratelimit._hits.clear() get_settings.cache_clear() # --------------------------------------------------------------------------- # Helpere # --------------------------------------------------------------------------- def _make_csv_bytes(rows: list[dict], sep: str = ";") -> bytes: import csv 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") def _make_xlsx_bytes(rows: list[dict]) -> bytes: 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() _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: client.post("/v1/mapari", json={ "cod_op_service": cod_op, "cod_prestatie": cod_prest, "auto_send": True, }) def _upload_and_get_import_id(client, rows=None) -> int: 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 m = re.search(r"/_import/(\d+)/mapare-coloane", r.text) assert m, f"Nu s-a gasit import_id in raspuns: {r.text[:500]}" return int(m.group(1)) def _get_preview_via_mapare(client, import_id: int) -> str: """Salveaza maparea de coloane si returneaza textul raspunsului preview.""" 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 return r.text # --------------------------------------------------------------------------- # US-004 Teste stepper # --------------------------------------------------------------------------- def test_stepper_pas1_la_upload(client): """Fragmentul de upload contine stepper-ul cu pasul 1 activ. Verifica prezenta marcajului aria-current='step' pe pasul 'Incarca fisier' sau clasa activa asociata pasului 1. """ r = client.get("/_import/reset") assert r.status_code == 200 text = r.text # Stepper-ul trebuie sa fie prezent assert "stepper" in text or "pasi-import" in text or "step" in text.lower(), \ "Stepper-ul nu a fost gasit in fragmentul de upload" # Pasul 1 trebuie sa aiba aria-current="step" assert 'aria-current="step"' in text, \ "aria-current='step' nu a fost gasit in fragmentul de upload (pasul 1)" # Textul pasului 1 trebuie sa fie prezent assert "Incarca" in text, "Textul pasului 1 'Incarca' nu a fost gasit" def test_stepper_pas1_via_tab_import(client): """Accesand /?tab=import, panoul contine stepper cu pasul 1 activ.""" r = client.get("/?tab=import") assert r.status_code == 200 text = r.text assert 'aria-current="step"' in text, \ "aria-current='step' nu a fost gasit in panoul Import (/?tab=import)" assert "Incarca" in text, "Textul pasului 1 'Incarca' nu a fost gasit in panoul Import" def test_stepper_pas2_la_mapare(client): """Fragmentul mapare-coloane contine stepper cu pasul 2 activ. Declanseaza un upload cu coloane NEMAPATE ca sa primesti _mapcoloane.html. """ # Upload fara mapare salvata → trebuie sa vina _mapcoloane.html 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 text = r.text # Trebuie sa fie formularul de mapare coloane assert "mapare-coloane" in text, "Nu s-a primit fragmentul de mapare coloane" # Stepper prezent assert "stepper" in text or "step" in text.lower(), \ "Stepper-ul nu a fost gasit in fragmentul mapare-coloane" # Pasul 2 trebuie sa aiba aria-current="step" cu textul "Potriveste" # (pasul 1 e facut, pasul 2 e activ) assert 'aria-current="step"' in text, \ "aria-current='step' nu a fost gasit in fragmentul mapare-coloane (pasul 2)" assert "Potriveste" in text, "Textul pasului 2 'Potriveste' nu a fost gasit" def test_stepper_pas3_la_preview(client): """Preview contine stepper cu pasul 3 activ. Declanseaza upload + salvare mapare → se ajunge la preview. """ _seed_op_mapping(client) import_id = _upload_and_get_import_id(client) text = _get_preview_via_mapare(client, import_id) # Preview trebuie sa fie prezent assert "Preview" in text or "confirm-form" in text, \ "Nu s-a primit fragmentul de preview" # Stepper prezent assert "stepper" in text or "step" in text.lower(), \ "Stepper-ul nu a fost gasit in preview" # Pasul 3 activ assert 'aria-current="step"' in text, \ "aria-current='step' nu a fost gasit in preview (pasul 3)" assert "Verifica" in text, "Textul pasului 3 'Verifica' nu a fost gasit in preview" def test_stepper_pas3_la_preview_direct_mapare_retinuta(client): """Upload cu mapare retinuta sare direct la preview cu pasul 3 activ. Primul upload + mapare memoreaza configuratia. Al doilea upload cu acelasi antet sare direct la preview (pas 3). Pasii 1 si 2 sunt implicit facuti (comportament stepper la pas=3). """ _seed_op_mapping(client) import_id1 = _upload_and_get_import_id(client) _get_preview_via_mapare(client, import_id1) # Al doilea upload — mapare retinuta → preview direct 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 text = r.text # Preview direct cu mesaj "Mapare retinuta" assert "Mapare retinuta" in text, "Preview direct (mapare retinuta) nu a fost randat" # Stepper prezent cu pasul 3 activ assert 'aria-current="step"' in text, \ "aria-current='step' nu a fost gasit in preview direct (mapare retinuta)" assert "Verifica" in text, "Textul pasului 3 'Verifica' nu a fost gasit in preview direct" def test_stepper_marcheaza_pasii_facuti(client): """In preview (pas 3), pasii 1 si 2 sunt marcati ca facuti (clasa 'facut'). Verifica prin prezenta clasei CSS sau a marcajului vizual de 'facut'. """ _seed_op_mapping(client) import_id = _upload_and_get_import_id(client) text = _get_preview_via_mapare(client, import_id) # Clasa "facut" trebuie sa apara pentru pasii 1 si 2 (index < pas curent) assert "facut" in text, \ "Clasa/marcajul 'facut' nu a fost gasit in preview (pasii 1 si 2 ar trebui marcati ca facuti)" # Numarul de aparitii: cel putin 2 pasi marcati ca facuti count_facut = text.count("facut") assert count_facut >= 2, \ f"Asteptat cel putin 2 pasi marcati ca 'facut' in preview, gasit {count_facut}" def test_import_hx_target_in_tab(client): """Fragmentele de import pastreaza hx-target='#import-section'. Fragmentul de upload (/_import/reset) trebuie sa contina hx-target='#import-section' pentru ca HTMX sa actualizeze corect containerul din panoul de tab, nu din alta parte. """ r = client.get("/_import/reset") assert r.status_code == 200 text = r.text assert 'hx-target="#import-section"' in text, \ "hx-target='#import-section' nu a fost gasit in fragmentul de upload" # Wrapper-ul extern trebuie sa aiba id="import-section" assert 'id="import-section"' in text, \ "id='import-section' nu a fost gasit in fragmentul de upload" def test_import_forms_pastreaza_csrf(client): """Formularele de import contin csrf_token (input hidden cu valoare). Testeaza atat fragmentul de upload cat si cel de mapare coloane. """ # Fragment upload r_upload = client.get("/_import/reset") assert r_upload.status_code == 200 text_upload = r_upload.text # Trebuie sa contina campul csrf_token (poate fi gol in modul dev fara sesiune, # dar campul trebuie sa existe) assert 'name="csrf_token"' in text_upload, \ "name='csrf_token' nu a fost gasit in formularul de upload" # Fragment mapare coloane csv_bytes = _make_csv_bytes(_SAMPLE_ROWS) r_map = client.post( "/_import/upload", files={"file": ("test.csv", csv_bytes, "text/csv")}, ) assert r_map.status_code == 200 text_map = r_map.text if "mapare-coloane" in text_map: # s-a primit fragmentul de mapare assert 'name="csrf_token"' in text_map, \ "name='csrf_token' nu a fost gasit in formularul mapare-coloane"