5.12 (livrat): editare in modal a randurilor de preview, cont obligatoriu inainte de import, formular editare extras (_form_editare, _editare_preview_modal), plus suita de teste aferenta (preview edit/compact, mapare op, form editare, signup, admin panel). Design + planificare: - docs/design.md: sistem de design (tokeni, breakpoints, scara control, componente, a11y). - docs/prd/prd-5.12-* si prd-5.13-* (5.13 cu raport /autoplan: CEO+Design+Eng, audit trail). Curatare: sterse PNG-urile de test/mockup temporare din radacina. Nota: implementarea CSS 5.13 (responsive compact + sistem butoane) NU e inca facuta — planul revizuit cere refactorul testelor fragile din test_web_responsive.py INAINTE de CSS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
175 lines
6.7 KiB
Python
175 lines
6.7 KiB
Python
"""Teste US-005 (PRD 5.12): formular de editare partajat DRY + input date.
|
|
|
|
_form_editare.html — partial NOU cu campurile vehicul/data/odo.
|
|
_macros.html — macro `camp` extins cu tip='date'.
|
|
_trimitere_detaliu.html — consuma partial-ul in ramura editabil.
|
|
|
|
TDD: scriem testele RED inainte de implementare.
|
|
"""
|
|
|
|
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"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _create_account_user(email: str, name: str = "Service", 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, name, 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
|
|
resp = client.post("/login", data={"email": email, "parola": password, "csrf_token": m.group(1)})
|
|
assert resp.status_code == 303
|
|
|
|
|
|
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 _payload_needs_data(vin: str = "WVWZZZ1JZXW0FE001") -> dict:
|
|
return {
|
|
"vin": vin,
|
|
"nr_inmatriculare": "B200FE",
|
|
"data_prestatie": "2026-06-15",
|
|
"odometru_final": "", # gol -> needs_data
|
|
"prestatii": [{"cod_prestatie": "R-FRANE"}],
|
|
}
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "form_editare.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()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test 1: fragmentul de trimitere randeaza <input type="date"> pentru data_prestatie
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_form_editare_are_input_date_pe_data_prestatie(client):
|
|
"""Fragmentul de detaliu pentru un rand needs_data trebuie sa randereze
|
|
<input type="date"> pentru campul data_prestatie (calendar nativ, D#10/R3).
|
|
Inainte de US-005, campul e type="text" -> test RED.
|
|
"""
|
|
acct = _create_account_user("fe1@test.com")
|
|
sid = _insert(acct, status="needs_data", payload=_payload_needs_data())
|
|
_login(client, "fe1@test.com")
|
|
|
|
resp = client.get(f"/_fragments/trimitere/{sid}")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
|
|
# Campul data_prestatie trebuie sa fie type="date" (nu type="text").
|
|
assert 'name="data_prestatie"' in html, "campul data_prestatie lipseste din fragment"
|
|
# Cautam input cu name=data_prestatie si type=date.
|
|
assert re.search(r'<input[^>]+name="data_prestatie"[^>]+type="date"', html) or \
|
|
re.search(r'<input[^>]+type="date"[^>]+name="data_prestatie"', html), \
|
|
"data_prestatie trebuie sa fie <input type='date'>, nu type='text'"
|
|
|
|
# Asiguram ca NU este type="text" pentru data_prestatie.
|
|
# type="text" pe data_prestatie inseamna ca partial-ul nu e activ.
|
|
match_text = re.search(r'<input[^>]+name="data_prestatie"[^>]+type="text"', html) or \
|
|
re.search(r'<input[^>]+type="text"[^>]+name="data_prestatie"', html)
|
|
assert not match_text, \
|
|
"data_prestatie NU trebuie sa fie type='text' dupa US-005 (trebuie type='date')"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test 2: _trimitere_detaliu.html foloseste partial-ul _form_editare.html
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_trimitere_detaliu_foloseste_form_partajat():
|
|
"""Sursa _trimitere_detaliu.html trebuie sa includa _form_editare.html.
|
|
Inainte de US-005, include lipseste -> test RED.
|
|
"""
|
|
sursa = (TEMPLATES_DIR / "_trimitere_detaliu.html").read_text(encoding="utf-8")
|
|
|
|
# Trebuie sa contina include sau import din _form_editare.html.
|
|
assert "_form_editare.html" in sursa, (
|
|
"_trimitere_detaliu.html nu referencieaza _form_editare.html. "
|
|
"US-005 cere ca partial-ul sa fie consumat in ramura editabil."
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test 3: macro `camp` din _macros.html suporta tip='date'
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_camp_macro_randeaza_type_date():
|
|
"""Macro `camp` din _macros.html trebuie sa suporte tip='date' si sa
|
|
randeze <input type='date'> fara a strica tip='text' (default).
|
|
Inainte de US-005, macros.html nu are macro `camp` -> test RED.
|
|
"""
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
env = Environment(loader=FileSystemLoader(str(TEMPLATES_DIR)))
|
|
# Randare directa a macro-ului camp din _macros.html.
|
|
tmpl = env.from_string(
|
|
"{% from '_macros.html' import camp %}"
|
|
"{{ camp('data_prestatie', 'Data prestatie', '2026-06-15', tip='date') }}"
|
|
)
|
|
html = tmpl.render()
|
|
|
|
# Trebuie sa contina type="date".
|
|
assert 'type="date"' in html, \
|
|
"macro camp cu tip='date' trebuie sa randeze <input type='date'>"
|
|
assert 'name="data_prestatie"' in html, \
|
|
"macro camp trebuie sa randeze input cu name corect"
|
|
|
|
# Verifica ca tip='text' (default) inca functioneaza.
|
|
tmpl_text = env.from_string(
|
|
"{% from '_macros.html' import camp %}"
|
|
"{{ camp('nr_inmatriculare', 'Nr inmatriculare', 'B100AA') }}"
|
|
)
|
|
html_text = tmpl_text.render()
|
|
assert 'type="text"' in html_text, \
|
|
"macro camp fara tip explicit trebuie sa randeze type='text' (default neschimbat)"
|
|
assert 'type="date"' not in html_text, \
|
|
"macro camp fara tip='date' NU trebuie sa randeze type='date'"
|