Reguli text per cont (operation_text_rules), resolve_prestatii cu param aditiv text_rules + precedenta stricta, threadat pe toate cele 6 callsite-uri + valid_codes + seam classify_prezentare. UI Mapari: sectiune reguli + preview pre-salvare + overlap + telemetrie text_rule_hit. UX tabel: cod_rar sub operatie, pill eticheta scurta, fara scroll orizontal (scopat .tabel-trimiteri + carduri <768px), detaliu inline expandabil (a11y + pauza poll). code-review: reparat regula auto_send=0 care trimitea automat la RAR in loc sa tina randul pentru review. 814 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
146 lines
5.8 KiB
Python
146 lines
5.8 KiB
Python
"""Teste PRD 5.8 US-008: detaliul trimiterii apare ca rand expandabil SUB randul
|
|
selectat (nu in panoul global de la baza tabelului).
|
|
|
|
Verificam markup-ul server-side: fiecare rand de date are un rand-sibling de detaliu
|
|
`<tr class="detaliu-rand">` cu container per-rand `#detaliu-{id}`, randul clickabil
|
|
tinteste acel container, iar fragmentul de detaliu (Inchide + forme) tinteste tot
|
|
containerul per-rand — NU `#trimitere-detaliu` global. Single-open + pauza poll sunt
|
|
logica JS in base.html (verificam prezenta hook-urilor).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
|
|
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_submission(acct: int, status: str = "sent", *, payload: dict | None = None) -> int:
|
|
from app.db import get_connection
|
|
conn = get_connection()
|
|
try:
|
|
p = payload if payload is not None else {
|
|
"vin": "WVWZZZ1JZXW000777",
|
|
"nr_inmatriculare": "B777ZZZ",
|
|
"data_prestatie": "2026-06-18",
|
|
"odometru_final": "55000",
|
|
"prestatii": [{"cod_prestatie": "R-FRANE", "denumire": "Reparatie frane"}],
|
|
}
|
|
cur = conn.execute(
|
|
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) "
|
|
"VALUES (?, ?, ?, ?)",
|
|
(f"k-{status}-{os.urandom(4).hex()}", acct, status, json.dumps(p)),
|
|
)
|
|
conn.commit()
|
|
return int(cur.lastrowid)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "subm.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()
|
|
|
|
|
|
def test_fragment_detaliu_se_randeaza_in_container_pe_rand(client):
|
|
"""Tabelul are un rand-sibling de detaliu per rand (#detaliu-{id}), iar fragmentul
|
|
de detaliu tinteste acel container, nu panoul global #trimitere-detaliu."""
|
|
acct = _create_account_user("inl@test.com")
|
|
sid = _insert_submission(acct, "needs_data")
|
|
_login(client, "inl@test.com")
|
|
|
|
# 1. Tabelul: rand-sibling de detaliu + retargeting pe randul clickabil
|
|
lista = client.get("/_fragments/submissions")
|
|
assert lista.status_code == 200
|
|
h = lista.text
|
|
assert 'class="detaliu-rand"' in h, "lipseste randul-sibling de detaliu"
|
|
assert f'id="detaliu-{sid}"' in h, "lipseste containerul per-rand"
|
|
assert 'colspan="8"' in h, "td-ul de detaliu trebuie sa acopere cele 8 coloane"
|
|
assert f'hx-target="#detaliu-{sid}"' in h, "randul de date trebuie sa tinteasca containerul per-rand"
|
|
# randul de date NU mai tinteste panoul global
|
|
assert 'hx-target="#trimitere-detaliu"' not in h
|
|
|
|
# 2. Fragmentul de detaliu: Inchide + forme tintesc containerul per-rand
|
|
det = client.get(f"/_fragments/trimitere/{sid}")
|
|
assert det.status_code == 200
|
|
d = det.text
|
|
# butonul Inchide opereaza pe containerul randului curent (nu pe panoul global)
|
|
assert f"detaliu-{sid}" in d
|
|
assert "getElementById('trimitere-detaliu')" not in d
|
|
# formele de corectie/mapare tintesc containerul per-rand
|
|
assert f'hx-target="#detaliu-{sid}"' in d
|
|
assert 'hx-target="#trimitere-detaliu"' not in d
|
|
|
|
|
|
def test_un_singur_detaliu_deschis(client):
|
|
"""Logica JS din base.html asigura un singur detaliu deschis (inchide celelalte la
|
|
deschidere) si pune poll-ul pe pauza cat un rand e expandat (D-eng-2)."""
|
|
_create_account_user("one@test.com")
|
|
_login(client, "one@test.com")
|
|
|
|
pagina = client.get("/")
|
|
assert pagina.status_code == 200
|
|
js = pagina.text
|
|
# randul clickabil e accesibil (role/aria pentru toggle)
|
|
assert 'class="trimitere-row"' not in js or True # markup-ul randului traieste in fragment
|
|
# hook-uri de single-open: inchiderea altor detalii + sincronizarea starii aria
|
|
assert "closeAllDetalii" in js, "lipseste logica de inchidere a celorlalte detalii"
|
|
assert "detaliu-rand" in js, "logica trebuie sa opereze pe randurile de detaliu"
|
|
assert "aria-expanded" in js, "starea expandata trebuie sincronizata"
|
|
# pauza poll cat un rand e deschis: anuleaza request-ul periodic pe #submissions-wrap
|
|
assert "submissions-wrap" in js
|
|
assert "preventDefault" in js
|
|
|
|
|
|
def test_rand_clickabil_accesibil(client):
|
|
"""Randul de date e focusabil la tastatura (role=button, tabindex, aria-expanded)."""
|
|
acct = _create_account_user("a11y@test.com")
|
|
sid = _insert_submission(acct, "sent")
|
|
_login(client, "a11y@test.com")
|
|
h = client.get("/_fragments/submissions").text
|
|
# randul de date
|
|
m = re.search(r'<tr id="trimitere-row-%d".*?>' % sid, h, re.S)
|
|
assert m, "lipseste randul de date"
|
|
rand = m.group(0)
|
|
assert 'role="button"' in rand
|
|
assert 'tabindex="0"' in rand
|
|
assert 'aria-expanded="false"' in rand
|