Files
rar-autopass/tests/test_web_status.py
Claude Agent 3fc53534e2 feat(5.15+5.14): CLOSE — fix-uri code-review + embeddings functional
5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata)
inchise dupa /code-review high. 8 buguri reparate TDD:

- HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim)
- HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare
  peste existing, codes pozitional
- HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus()
- HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile
- MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs=''
- MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard
- MED typo nome_prestatie -> nume_prestatie in select /repune
- MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest

Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus
construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default
off). Marime model corectata ~50MB->~230MB (estimare PRD gresita).

Cleanup: hoist load_* din bucla bulk-fix; import re la top.
Regresie: 1256 passed, 1 deselected (live), 0 failed.

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

362 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Teste US-003 (PRD 5.15): strip sanatate mereu-vizibil + carduri-contor pe dashboard.
D6 (strip sanatate): linie colorata DEASUPRA contoarelor — verde "declaratiile curg" /
rosu "Blocat: worker oprit / RAR inaccesibil", cu glifa accesibila (✓/✗).
D4 (contoare): In coada / Trimise (all-time + luna/azi) / De corectat.
E7 (timezone): azi/luna bucketate in timp local RO (UTC+3), nu UTC.
Actualizat in US-003 (PRD 5.15): bara veche cu bife individuale worker/RAR
inlocuita de strip unificat de sanatate + carduri-contor.
"""
from __future__ import annotations
import json
import os
import re
import tempfile
from datetime import datetime, timedelta, timezone
import pytest
from starlette.testclient import TestClient
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 Bife", 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:
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"
resp = client.post("/login", data={"email": email, "parola": password, "csrf_token": m.group(1)})
assert resp.status_code == 303, f"Login esuat: {resp.status_code} {resp.text[:200]}"
def _set_heartbeat(last_beat: str | None, last_rar_login_ok: str | None) -> None:
from app.db import get_connection
conn = get_connection()
try:
conn.execute(
"UPDATE worker_heartbeat SET last_beat=?, last_rar_login_ok=? WHERE id=1",
(last_beat, last_rar_login_ok),
)
conn.commit()
finally:
conn.close()
@pytest.fixture()
def client(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "bife_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()
from app.main import app
with TestClient(app, follow_redirects=False) as c:
yield c
ratelimit._hits.clear()
get_settings.cache_clear()
# ---------------------------------------------------------------------------
# Teste existente — actualizate pentru US-003 D6 (strip unificat in loc de bife individuale)
# ---------------------------------------------------------------------------
def test_status_are_bife_verzi_cand_totul_ok(client):
"""Worker viu + RAR login recent -> glifa verde ✓ + text 'declaratiile curg normal'."""
_create_account_user("bifeok@test.com")
_login(client, "bifeok@test.com", "parolasecreta10")
now = datetime.now(timezone.utc).isoformat()
_set_heartbeat(last_beat=now, last_rar_login_ok=now)
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Glifa accesibila ✓ (nu doar culoare)
assert "&#10003;" in html, f"Lipseste glifa ✓ cand totul e ok. HTML: {html[:600]}"
# US-003 D6: strip unificat (nu bife individuale worker/RAR)
assert "curg normal" in html.lower(), (
f"Textul 'curg normal' din strip sanatate lipseste. HTML: {html[:600]}"
)
def test_status_are_bife_rosii_cand_worker_oprit(client):
"""Fara heartbeat -> worker oprit -> glifa rosie ✗ + text explicit 'blocat' / 'nu pleaca'."""
_create_account_user("biferosu@test.com")
_login(client, "biferosu@test.com", "parolasecreta10")
_set_heartbeat(last_beat=None, last_rar_login_ok=None)
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
assert "&#10007;" in html, f"Lipseste glifa ✗ cand worker oprit. HTML: {html[:600]}"
# US-003 D6: mesaj explicit (nu text vag "oprita")
assert "blocat" in html.lower(), f"Cuvantul 'blocat' lipseste la worker oprit. HTML: {html[:600]}"
assert "nu pleaca" in html.lower(), f"Avertismentul 'nu pleaca' lipseste. HTML: {html[:600]}"
def test_status_data_formatata_romaneste(client):
"""Ultima autentificare RAR apare ca dd.mm.yyyy hh24:mi:ss."""
_create_account_user("bifedata@test.com")
_login(client, "bifedata@test.com", "parolasecreta10")
now = datetime.now(timezone.utc).isoformat()
_set_heartbeat(last_beat=now, last_rar_login_ok="2026-06-18T14:30:22")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
assert "18.06.2026 14:30:22" in resp.text, (
f"Data nu e formatata romaneste. HTML: {resp.text[:800]}"
)
def test_status_fara_fonturi_minuscule(client):
"""Niciun text din bara nu mai foloseste font-size literal sub 13px (US-001 AC)."""
_create_account_user("bifefont@test.com")
_login(client, "bifefont@test.com", "parolasecreta10")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Culorile prin clase CSS (nu inline font-size); shorthand font:N Xpx nu e acoperit de aceste litere
for bad in ("font-size:11px", "font-size:12px", "font-size: 11px", "font-size: 12px"):
assert bad not in html, f"Bara de status foloseste {bad} (sub 13px) inline."
# ---------------------------------------------------------------------------
# Teste NOI pentru US-003 (RED inainte de implementare)
# ---------------------------------------------------------------------------
def test_strip_sanatate_mereu_vizibil(client):
"""D6: strip de sanatate e prezent in fragment, indiferent de starea worker/RAR."""
_create_account_user("stripviz@test.com")
_login(client, "stripviz@test.com", "parolasecreta10")
# Stare worker viu
now = datetime.now(timezone.utc).isoformat()
_set_heartbeat(last_beat=now, last_rar_login_ok=now)
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
assert 'id="strip-sanatate"' in html, (
f"Strip sanatate (id='strip-sanatate') lipseste din fragment. HTML: {html[:600]}"
)
def test_strip_rosu_worker_oprit(client):
"""D6: worker oprit → strip rosu cu glifа ✗ + text 'Blocat: worker oprit — declaratiile NU pleaca'."""
_create_account_user("stroprosu@test.com")
_login(client, "stroprosu@test.com", "parolasecreta10")
_set_heartbeat(last_beat=None, last_rar_login_ok=None)
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
assert 'id="strip-sanatate"' in html, "Strip sanatate lipseste."
assert "&#10007;" in html, "Glifa ✗ lipseste in strip rosu."
assert "blocat" in html.lower(), "Cuvantul 'Blocat' trebuie sa apara cand worker e oprit."
assert "worker oprit" in html.lower(), "Textul 'worker oprit' trebuie sa fie explicit in strip."
assert "nu pleaca" in html.lower(), "Avertismentul 'NU pleaca' trebuie sa fie in strip."
def test_trei_contoare_card(client):
"""US-003: fragment status contine exact 3 carduri .contor-card (In coada / Trimise / De corectat)."""
_create_account_user("treicont@test.com")
_login(client, "treicont@test.com", "parolasecreta10")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
count = html.count("contor-card")
assert count >= 3, (
f"Trebuie minim 3 elemente contor-card in fragment, gasit: {count}. HTML: {html[:800]}"
)
# Etichete asteptate
assert "In coada" in html, "Eticheta 'In coada' lipseste din contoare."
assert "Trimise" in html, "Eticheta 'Trimise' lipseste din contoare."
assert "De corectat" in html, "Eticheta 'De corectat' lipseste din contoare."
def test_trimise_all_time_luna_azi(client):
"""D4: cardul Trimise afiseaza all-time ca cifra principala + sub-linie 'luna N · azi N'."""
acct_id, _ = _create_account_user("trimisetime@test.com")
_login(client, "trimisetime@test.com", "parolasecreta10")
# Insereaza o trimitere sent cu updated_at = acum
from app.db import get_connection
conn = get_connection()
try:
conn.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key, updated_at) "
"VALUES (?, 'sent', ?, 'key-luna-azi', datetime('now'))",
(acct_id, json.dumps({"vin": "VINTEST00000000001"})),
)
conn.commit()
finally:
conn.close()
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Sub-linia trebuie sa contina "luna" si "azi" (format: "luna N · azi N")
assert "luna" in html.lower(), (
f"Sub-linia 'luna N · azi N' lipseste din cardul Trimise. HTML: {html[:800]}"
)
assert "azi" in html.lower(), (
f"Sub-linia 'luna N · azi N' nu contine 'azi'. HTML: {html[:800]}"
)
def test_fara_bara_veche(client):
"""US-003: contoarele vechi inline ('In asteptare:' / 'Declarate la RAR:') nu mai apar."""
_create_account_user("faraveche@test.com")
_login(client, "faraveche@test.com", "parolasecreta10")
resp = client.get("/_fragments/status")
assert resp.status_code == 200
html = resp.text
# Stilul vechi: etichete inline cu colon (bara de la PRD 3.5)
assert "In asteptare:" not in html, (
f"Contorul vechi 'In asteptare:' inca prezent. HTML: {html[:600]}"
)
assert "Declarate la RAR:" not in html, (
f"Contorul vechi 'Declarate la RAR:' inca prezent. HTML: {html[:600]}"
)
def _set_tz_bucuresti(monkeypatch, request):
"""Forteaza TZ=Europe/Bucharest pentru ca modificatorul SQLite 'localtime' sa
rezolve la fusul RO indiferent de TZ-ul runner-ului (CI ruleaza de regula in UTC).
Restaureaza tzset-ul la teardown (monkeypatch reface env-ul TZ; tzset reciteste)."""
import time
monkeypatch.setenv("TZ", "Europe/Bucharest")
if hasattr(time, "tzset"):
time.tzset()
request.addfinalizer(time.tzset) # dupa ce monkeypatch reface TZ -> reciteste
def test_granita_miez_noapte_local_ro(monkeypatch, request):
"""E7: trimitere cu updated_at = ieri UTC 22:00 = azi Romania (UTC+2/+3) se numara 'azi'.
Cu date(updated_at) simplu (UTC) ar aparea pe ziua precedenta — GRESIT.
Cu date(updated_at, 'localtime') + TZ=Europe/Bucharest apare pe ziua de azi RO — CORECT
(DST-aware: +2h iarna, +3h vara).
"""
_set_tz_bucuresti(monkeypatch, request)
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "granita.db"))
from app.config import get_settings
get_settings.cache_clear()
from app.db import get_connection, init_db
from app.accounts import create_account
from app.web.routes import _status_counts
# Initializeaza schema (init_db o face idempotent)
init_db()
# Ieri la 22:00 UTC = azi 00:00 (iarna) / 01:00 (vara) Romania -> 'azi' in ambele.
today_utc = datetime.now(timezone.utc).date()
yesterday_utc = today_utc - timedelta(days=1)
boundary_updated_at = f"{yesterday_utc} 22:00:00"
conn = get_connection()
try:
acct_id = create_account(conn, "Service Granita", active=True)
conn.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key, updated_at) "
"VALUES (?, 'sent', ?, 'key-granita-1', ?)",
(acct_id, json.dumps({"vin": "VIN00000000000001"}), boundary_updated_at),
)
conn.commit()
counts = _status_counts(conn, acct_id)
# 22:00 UTC -> azi in RO (localtime) => sent_today=1
assert counts["sent_today"] == 1, (
f"E7 FAIL: trimitere la {boundary_updated_at} UTC = azi in Romania "
f"trebuie sa fie 'azi', dar sent_today={counts.get('sent_today')}. "
"SQL trebuie sa foloseasca date(updated_at, 'localtime') = date('now', 'localtime')."
)
assert counts["sent_month"] >= 1, (
f"E7: sent_month trebuie sa fie >= 1, got {counts.get('sent_month')}"
)
finally:
conn.close()
get_settings.cache_clear()
def test_iarna_nu_bleed_in_ziua_urmatoare(monkeypatch, request):
"""Bug fix (code-review 5.15): iarna (UTC+2), o trimitere la 21:30 UTC = 23:30 RO AZI
NU trebuie sa cada pe ziua de MAINE.
Vechiul offset fix '+3 hours' o impingea la 00:30 maine -> sent_today gresit.
'localtime' (DST-aware) o pastreaza corect pe azi. Testul fixeaza o data de iarna
explicita (15 ianuarie) ca sa fie determinist indiferent de cand ruleaza.
"""
_set_tz_bucuresti(monkeypatch, request)
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "iarna.db"))
from app.config import get_settings
get_settings.cache_clear()
from app.db import get_connection, init_db
from app.accounts import create_account
init_db()
# Data de iarna fixa: 2026-01-15. 21:30 UTC = 23:30 RO (EET, UTC+2) -> ziua 15, nu 16.
conn = get_connection()
try:
acct_id = create_account(conn, "Service Iarna", active=True)
conn.execute(
"INSERT INTO submissions (account_id, status, payload_json, idempotency_key, updated_at) "
"VALUES (?, 'sent', ?, 'key-iarna-1', ?)",
(acct_id, json.dumps({"vin": "VIN00000000000002"}), "2026-01-15 21:30:00"),
)
conn.commit()
# Verifica direct ce zi RO atribuie SQLite (localtime vs vechiul +3h).
row = conn.execute(
"SELECT date(updated_at, 'localtime') AS zi_local, "
" date(updated_at, '+3 hours') AS zi_plus3 "
"FROM submissions WHERE idempotency_key='key-iarna-1'"
).fetchone()
assert row["zi_local"] == "2026-01-15", (
f"localtime (EET, UTC+2) trebuie sa pastreze 21:30 UTC pe 15 ian RO, "
f"got {row['zi_local']}"
)
# Demonstreaza bug-ul vechi: +3h impingea pe 16 ian (ziua gresita iarna).
assert row["zi_plus3"] == "2026-01-16", (
f"Confirmare bug vechi: '+3 hours' iarna pune 21:30 UTC pe 16 ian, got {row['zi_plus3']}"
)
finally:
conn.close()
get_settings.cache_clear()