"""Teste TDD pentru US-007 (PRD 5.15): formular editare slim.
RED -> implementare -> GREEN.
AC-uri verificate:
- Un singur camp VIN (fara "Confirma VIN").
- Textarea obs (Observatii) prezent in formular.
- Chips multi-select prestatii cu hidden inputs name="cod_prestatie".
- Endpoint /form-chips re-randeaza sectiunea chips (add/remove).
- Acelasi _form_editare.html in ambele modale (trimitere detaliu + editare preview).
- Reveal dinamic odometru initial cand chips contin R-ODO/I-ODO.
"""
from __future__ import annotations
import json
import os
import re
import tempfile
from pathlib import Path
import pytest
from starlette.testclient import TestClient
TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "app" / "web" / "templates"
# --------------------------------------------------------------------------- #
# Fixtures #
# --------------------------------------------------------------------------- #
@pytest.fixture()
def client(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "slim_form.db"))
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true")
from app.config import get_settings
get_settings.cache_clear()
from app.web import ratelimit
ratelimit._hits.clear()
from app.main import app
with TestClient(app, follow_redirects=False) as c:
yield c
ratelimit._hits.clear()
get_settings.cache_clear()
# --------------------------------------------------------------------------- #
# Helpere #
# --------------------------------------------------------------------------- #
def _create_account_user(email: str, password: str = "parolasecreta10"):
from app.accounts import create_account
from app.users import create_user
from app.db import get_connection
conn = get_connection()
try:
acct_id = create_account(conn, "Service Test", active=True)
create_user(conn, acct_id, email, password)
return acct_id
finally:
conn.close()
def _login(client, email: str, password: str = "parolasecreta10") -> None:
resp = client.get("/login")
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) or \
re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
assert m, "csrf_token nu gasit in login"
resp = client.post(
"/login",
data={"email": email, "parola": password, "csrf_token": m.group(1)},
)
assert resp.status_code == 303
def _csrf(client) -> str:
resp = client.get("/?tab=coada")
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text)
assert m, "csrf_token nu gasit in dashboard"
return m.group(1)
def _insert(acct: int, *, status: str, payload: dict) -> int:
from app.db import get_connection
conn = get_connection()
try:
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) "
"VALUES (?, ?, ?, ?)",
(f"k-{os.urandom(6).hex()}", acct, status, json.dumps(payload)),
)
conn.commit()
return int(cur.lastrowid)
finally:
conn.close()
def _seed_cod(cod: str, denumire: str = "Prestatie test") -> None:
from app.db import get_connection
conn = get_connection()
try:
conn.execute(
"INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
(cod, denumire),
)
conn.commit()
finally:
conn.close()
def _payload_needs_data_cu_cod(vin: str = "WVWZZZ1JZXW0US007A") -> dict:
"""Payload needs_data: cod RAR setat, dar odometru_final gol."""
return {
"vin": vin,
"nr_inmatriculare": "B200AA",
"data_prestatie": "2026-06-20",
"odometru_final": "", # gol -> needs_data
"prestatii": [{"cod_prestatie": "OE-1", "cod_op_service": "Op-A", "denumire": "Schimb ulei"}],
}
def _payload_cu_ops(vin: str, ops: list[tuple]) -> dict:
"""Payload cu prestatii avand cod_op_service (needs_mapping)."""
return {
"vin": vin,
"nr_inmatriculare": "B100AAA",
"data_prestatie": "2026-06-10",
"odometru_final": "50000",
"prestatii": [
{"cod_op_service": op, "denumire": den}
for op, den in ops
],
}
def _payload_cu_r_odo(vin: str = "WVWZZZ1JZXW0RODO1") -> dict:
"""Payload needs_data cu R-ODO in chips — declanseaza reveal odometru initial."""
return {
"vin": vin,
"nr_inmatriculare": "B300RO",
"data_prestatie": "2026-06-20",
"odometru_final": "39000",
# odometru_initial ABSENT -> needs_data cand R-ODO
"prestatii": [{"cod_prestatie": "R-ODO", "cod_op_service": "", "denumire": "Revizie odometru"}],
}
# --------------------------------------------------------------------------- #
# Test 1: UN SINGUR camp VIN (fara "Confirma VIN") #
# --------------------------------------------------------------------------- #
def test_un_singur_vin(client):
"""US-007 AC1: formularul slim are UN SINGUR input name='vin'.
Fara camp 'Confirma VIN' — PRD si contractul RAR cer un singur VIN.
RED: daca ar exista doua campuri VIN sau un camp 'confirma_vin', testul pica.
"""
acct = _create_account_user("vin.unic@test.com")
_login(client, "vin.unic@test.com")
_seed_cod("OE-1")
sid = _insert(acct, status="needs_data", payload=_payload_needs_data_cu_cod())
resp = client.get(f"/_fragments/trimitere/{sid}")
assert resp.status_code == 200, resp.text[:300]
html = resp.text
# Exact un singur input cu name="vin"
vin_inputs = re.findall(r']+name="vin"[^>]*>', html)
assert len(vin_inputs) == 1, (
f"Trebuie exact UN input name='vin', gasit {len(vin_inputs)}: {vin_inputs}"
)
# Fara camp "Confirma VIN" sau "confirma_vin"
assert "confirma_vin" not in html.lower(), (
"Formular NU trebuie sa aiba camp 'confirma_vin' (VIN unic per contract RAR)"
)
assert "confirma vin" not in html.lower(), (
"Formular NU trebuie sa afiseze eticheta 'confirma vin'"
)
# --------------------------------------------------------------------------- #
# Test 2: Camp Observatii (textarea name="obs") #
# --------------------------------------------------------------------------- #
def test_camp_observatii_prezent(client):
"""US-007 AC2: formularul are textarea name='obs' pentru Observatii (US-005).
RED: obs nu e inca in _form_editare.html (US-005 adauga backend-ul, US-007 adauga UI-ul).
"""
acct = _create_account_user("obs.forma@test.com")
_login(client, "obs.forma@test.com")
_seed_cod("OE-1")
sid = _insert(acct, status="needs_data", payload=_payload_needs_data_cu_cod())
resp = client.get(f"/_fragments/trimitere/{sid}")
assert resp.status_code == 200
html = resp.text
# textarea cu name="obs" trebuie sa existe
has_textarea_obs = bool(
re.search(r'