Files
rar-autopass/tests/test_web_status_fragment.py
Claude Agent 5a964a1a8d feat(5.10): UX trimiteri (pill filtre, paginare, editare) + Mapari in meniu + branding ROMFAST
14 stories TDD prin echipa de workeri (lead orchestreaza, 3 teammates pe valuri cu fisiere disjuncte; routes.py + base.html serializate ca fisiere fierbinti).

- US-001 fix filtrare data (_iso_date_prefix pe garda+comparatie, prinde timestamp cu ora)
- US-002/007 operatie service distincta in payload_view + afisare in detaliu
- US-003 pill-uri categorii (button/aria-pressed; needs_mapping --warn, needs_data/error --err); fara lista ID-uri/dropdown
- US-004 paginare numerotata 25/pag (total ramificat SQL-COUNT vs fetch-all+slice, clamp page, poll pastreaza pagina)
- US-005 VIN block-level sub nr
- US-006/006b editare cod RAR + validare nomenclator + recalcul idempotency (needs_data/needs_mapping via /corecteaza, error via /repune)
- US-008 card eroare 3-niveluri doar pe read-only + rezumat top-of-form
- US-009 Mapari in meniu hamburger; scoatere tab-bar + role=tablist orfan
- US-010/011 pagina Mapari consolidata + butoane icon SVG + dirty-state (fara kebab/emoji)
- US-012/012b header centrat + logo ROMFAST (/static/romfast_logo.png) in header
- US-013 paleta azur ROMFAST (#2E74D6/#1F66C9) + IBM Plex Sans/Mono self-host (woff2 reale)
- US-014 selector tema ciclic Light/Dark/Petrol/Auto + anti-FOUC pe 4 stari

Backend trimitere (worker/masina stari/idempotenta/mapping) + schema NEATINSE (UI/UX pur + 1 fix de filtrare).
VERIFY context curat PASS; /code-review high: 1 finding material reparat (US-006b). Regresie 896 passed, 1 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 20:20:58 +00:00

255 lines
9.6 KiB
Python

"""Teste US-002 (PRD 3.4): bara de status persistenta cu etichete umane.
TDD: testele se scriu INAINTE de implementare; la inceput pica (RED),
dupa implementare trec (GREEN).
Rute testate:
- GET /_fragments/status -> bara de status cu etichete umane, scoped pe cont
"""
from __future__ import annotations
import os
import re
import tempfile
import pytest
from starlette.testclient import TestClient
def _create_account_user(email: str = "user@test.com", password: str = "parolasecreta10"):
"""Creeaza cont + user. Intoarce (acct_id, user_id)."""
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 Status", active=True)
user_id = create_user(conn, acct_id, email, password)
return acct_id, user_id
finally:
conn.close()
def _login(client, email: str, password: str) -> None:
"""Face login real prin HTTP si seteaza cookie-ul de sesiune pe client."""
resp = client.get("/login")
assert resp.status_code == 200
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text)
if not m:
m = re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
assert m, "csrf_token negasit pe /login"
csrf = m.group(1)
resp = client.post("/login", data={
"email": email,
"parola": password,
"csrf_token": csrf,
})
assert resp.status_code == 303, f"Login esuat: {resp.status_code} {resp.text[:200]}"
def _insert_submission(status: str, account_id: int) -> None:
"""Insereaza un submission cu status dat pentru un cont dat."""
from app.db import get_connection
import json
conn = get_connection()
try:
conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) "
"VALUES (?, ?, ?, ?)",
(
f"test-key-{status}-{account_id}-{os.urandom(4).hex()}",
account_id,
status,
json.dumps({"vin": "TEST", "status": status}),
),
)
conn.commit()
finally:
conn.close()
@pytest.fixture()
def client(monkeypatch):
"""Client cu BD izolata si autentificare web activata."""
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "status_test.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() # izolare: limiterul login e global in-proces
from app.main import app
with TestClient(app, follow_redirects=False) as c:
yield c
ratelimit._hits.clear()
get_settings.cache_clear()
# ============================================================
# test_status_fragment_text_uman
# ============================================================
def test_status_fragment_text_uman(client):
"""GET /_fragments/status (autentificat) -> contine 'Trimitere automata', NU 'worker viu'."""
_create_account_user("status@test.com", "parolasecreta10")
_login(client, "status@test.com", "parolasecreta10")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Trebuie sa contina textul uman din eticheta_worker (labels.py)
assert "Trimitere automata" in html, (
f"Fragmentul nu contine 'Trimitere automata'. HTML (primele 500 ch): {html[:500]}"
)
# NU trebuie sa contina textul brut tehnic
assert "worker viu" not in html.lower(), (
f"Fragmentul contine 'worker viu' (text tehnic brut). HTML (primele 500 ch): {html[:500]}"
)
# NU trebuie sa contina "mort" (stare tehnica bruta)
# (poate aparea in 'oprita' -> acceptam; 'mort' singur -> nu)
# Verificam ca nu apare 'mort' ca eticheta standalone
assert "viu</div>" not in html, (
"Fragmentul contine eticheta bruta 'viu'"
)
# ============================================================
# test_status_blocate_defalcare
# ============================================================
def test_status_blocate_defalcare(client):
"""Cu submissions blocate in DB, fragmentul arata defalcarea pe motiv (texte umane)."""
acct_id, _ = _create_account_user("blocate@test.com", "parolasecreta10")
_login(client, "blocate@test.com", "parolasecreta10")
# Insereaza submissions blocate din fiecare tip
_insert_submission("needs_mapping", acct_id)
_insert_submission("needs_mapping", acct_id)
_insert_submission("needs_data", acct_id)
_insert_submission("error", acct_id)
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# US-003 (PRD 5.10): Blocatele apar ca pill-uri (nu ca lista cu ID-uri)
assert "Necesita atentie" in html, (
f"Fragmentul nu contine sectiunea 'Necesita atentie'. HTML: {html[:800]}"
)
# Pill-urile au etichetele scurte per categorie (nu etichetele lungi din eticheta_stare)
assert "Lipsa cod" in html, (
"Fragmentul nu arata pill-ul pentru needs_mapping"
)
assert "Date incomplete" in html, (
"Fragmentul nu arata pill-ul pentru needs_data"
)
assert "Eroare" in html, (
"Fragmentul nu arata pill-ul pentru error"
)
# Pill-urile arata numarul total per categorie (2 needs_mapping, 1 needs_data, 1 error)
assert "2" in html, "Pill-ul needs_mapping trebuie sa arate numarul 2"
assert "<button" in html, "Pill-urile trebuie sa fie elemente <button>"
# ============================================================
# test_status_se_reincarca_htmx
# ============================================================
def test_status_se_reincarca_htmx(client):
"""Fragmentul contine atribut hx-trigger cu poll periodic (every 15s)."""
_create_account_user("htmx@test.com", "parolasecreta10")
_login(client, "htmx@test.com", "parolasecreta10")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Trebuie sa contina hx-trigger periodic
assert "hx-trigger" in html, (
f"Fragmentul nu contine atribut hx-trigger. HTML: {html[:500]}"
)
assert "every 15s" in html, (
f"Fragmentul nu contine poll 'every 15s'. HTML: {html[:500]}"
)
# Trebuie sa aiba endpoint corect pentru auto-refresh
assert "/_fragments/status" in html, (
"Fragmentul nu contine referinta la /_fragments/status pentru hx-get"
)
# Trebuie sa aiba un id stabil pe containerul radacina
assert 'id="status-bar"' in html, (
"Fragmentul nu are id='status-bar' pe containerul radacina"
)
# ============================================================
# US-014: banner "Necesita atentia ta" actionabil
# ============================================================
def _insert_submission_vehicul(status, account_id, vin, nr):
from app.db import get_connection
import json
conn = get_connection()
try:
conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) VALUES (?, ?, ?, ?)",
(f"k-{os.urandom(5).hex()}", account_id, status,
json.dumps({"vin": vin, "nr_inmatriculare": nr, "data_prestatie": "2026-06-15",
"odometru_final": "1", "prestatii": [{"cod_prestatie": "OE-1"}]})),
)
conn.commit()
finally:
conn.close()
def test_categorie_blocata_linkeaza_la_trimiteri_filtrate(client):
"""US-003 (PRD 5.10): pill-ul error are hx-get cu ?status=error.
Deep-link-ul tab=acasa&status=error a fost eliminat (pill inlocuieste link-ul vechi)."""
acct_id, _ = _create_account_user("link@test.com", "parolasecreta10")
_login(client, "link@test.com", "parolasecreta10")
_insert_submission("error", acct_id)
html = client.get("/_fragments/status").text
# Pill-ul are hx-get cu status=error (filtrare directa submissions)
assert "/_fragments/submissions?status=error" in html
# Deep-link-ul tab=acasa&status=error nu mai exista — pill-uri inlocuiesc link-urile
assert "tab=acasa&status=error" not in html
def test_status_nu_arata_identificator_rand_blocat(client):
"""US-003 (PRD 5.10): VIN/nr inmatriculare nu mai apar in bara de status.
Lista de ID-uri a fost inlocuita cu pill-uri cu numar total (fara PII nominal)."""
acct_id, _ = _create_account_user("ident@test.com", "parolasecreta10")
_login(client, "ident@test.com", "parolasecreta10")
_insert_submission_vehicul("error", acct_id, "WVWZZZ1KZAW000123", "B123ABC")
html = client.get("/_fragments/status").text
# Bara de status arata pill cu count, nu lista cu VIN/nr per rand
assert "B123ABC" not in html, "Nr inmatriculare nu trebuie sa mai apara in bara de status"
assert "WVWZZZ1KZAW000123" not in html, "VIN integral nu trebuie expus"
assert "0123" not in html, "VIN partial nu trebuie sa mai apara in bara de status"
# Pill-ul cu count 1 apare in locul listei
assert "status=error" in html, "Pill error trebuie sa aiba hx-get cu status=error"
def test_scoped_pe_cont(client):
from app.accounts import create_account
from app.db import get_connection
acct_id, _ = _create_account_user("own@test.com", "parolasecreta10")
conn = get_connection()
try:
other = create_account(conn, "Alt", active=True)
finally:
conn.close()
_login(client, "own@test.com", "parolasecreta10")
_insert_submission_vehicul("error", other, "OTHERVIN000009999", "X999ZZZ")
html = client.get("/_fragments/status").text
# randul altui cont NU apare in banner-ul meu
assert "X999ZZZ" not in html
assert "9999" not in html