Files
rar-autopass/tests/test_web_status_fragment.py
Claude Agent f05fe5b221 fix(5.11): tabel trimiteri stabil — bug status=None, pills in bara de filtre, nudge "Date noi" in loc de poll 15s, logo ROMFAST marit
- Fix bug: campul hidden de filtru randa literal "None" (status_filtru None +
  Jinja default('')) -> poll-ul trimitea status=None -> tabel gol. status or "".
- Pills de stare mutate din bara de status in bara de filtre (filtreazaStare scrie
  campul hidden + re-trimite form-ul; filtrul persista la reincarcari). Re-randate
  OOB cu contoare proaspete la fiecare reincarcare a tabelului.
- Polling redesign: tabelul nu se mai reincarca singur (fara every 15s). Poller usor
  JSON (/_fragments/trimiteri-versiune) detecteaza schimbari -> nudge "Date noi —
  Reincarca". Reincarcarea (nudge / actiune) pastreaza filtrul+pagina. Scroll/selectia
  nu se mai pierd. Poll-guard eliminat (nu mai exista poll periodic de pauzat).
- Logo ROMFAST 32px -> 60px (ca pe romfast.ro), header min-height 92px, 44px pe mobil.

Regresie: 896 passed, 1 deselected.

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

242 lines
9.2 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)
# Pill-urile s-au mutat in bara de filtre din sectiunea Trimiteri (nu in bara de status).
resp = client.get("/?tab=acasa")
assert resp.status_code == 200
html = resp.text
# Pill-urile au etichetele scurte per categorie (nu etichetele lungi din eticheta_stare)
assert "Lipsa cod" in html, "Acasa nu arata pill-ul pentru needs_mapping"
assert "Date incomplete" in html, "Acasa nu arata pill-ul pentru needs_data"
assert "Eroare" in html, "Acasa nu arata pill-ul pentru error"
# Pill-urile arata numarul total per categorie (2 needs_mapping, 1 needs_data, 1 error)
assert 'class="pill-cat"' in html, "Pill-urile trebuie sa fie elemente cu clasa pill-cat"
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):
"""Pill-ul error filtreaza tabelul prin filtreazaStare(this, 'error') in bara de filtre."""
acct_id, _ = _create_account_user("link@test.com", "parolasecreta10")
_login(client, "link@test.com", "parolasecreta10")
_insert_submission("error", acct_id)
html = client.get("/?tab=acasa").text
# Pill-ul scrie campul de filtru si re-trimite form-ul (nu mai navigheaza prin deep-link)
assert "filtreazaStare(this, 'error')" in html
assert 'data-status="error"' in html
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 doar contoare, nu lista cu VIN/nr per rand (fara PII nominal)
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"
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