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>
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
"""Teste US-001 (PRD 3.5): bara de status compacta cu bife accesibile + data formatata.
|
||||
"""Teste US-003 (PRD 5.15): strip sanatate mereu-vizibil + carduri-contor pe dashboard.
|
||||
|
||||
Bifa = glifa distincta (✓ / ✗) + text, NU doar culoare (daltonism, design review).
|
||||
Verde/✓ cand worker viu + RAR ok; rosu/✗ cand oprit/indisponibil.
|
||||
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, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
@@ -70,8 +76,12 @@ def client(monkeypatch):
|
||||
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 -> bifa verde ✓ pentru ambele stari binare."""
|
||||
"""Worker viu + RAR login recent -> glifa verde ✓ + text 'declaratiile curg normal'."""
|
||||
_create_account_user("bifeok@test.com")
|
||||
_login(client, "bifeok@test.com", "parolasecreta10")
|
||||
|
||||
@@ -81,15 +91,16 @@ def test_status_are_bife_verzi_cand_totul_ok(client):
|
||||
resp = client.get("/_fragments/status")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
# Glifa de OK prezenta (accesibilitate: nu doar culoare)
|
||||
# Glifa accesibila ✓ (nu doar culoare)
|
||||
assert "✓" in html, f"Lipseste glifa ✓ cand totul e ok. HTML: {html[:600]}"
|
||||
# Texte umane de OK
|
||||
assert "activa" in html.lower()
|
||||
assert "functionala" in html.lower()
|
||||
# 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 -> bifa rosie ✗ + text 'oprita'."""
|
||||
"""Fara heartbeat -> worker oprit -> glifa rosie ✗ + text explicit 'blocat' / 'nu pleaca'."""
|
||||
_create_account_user("biferosu@test.com")
|
||||
_login(client, "biferosu@test.com", "parolasecreta10")
|
||||
|
||||
@@ -99,7 +110,9 @@ def test_status_are_bife_rosii_cand_worker_oprit(client):
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
assert "✗" in html, f"Lipseste glifa ✗ cand worker oprit. HTML: {html[:600]}"
|
||||
assert "oprita" in html.lower()
|
||||
# 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):
|
||||
@@ -118,12 +131,231 @@ def test_status_data_formatata_romaneste(client):
|
||||
|
||||
|
||||
def test_status_fara_fonturi_minuscule(client):
|
||||
"""Niciun text din bara nu mai foloseste font-size sub 13px (US-001 AC)."""
|
||||
"""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)."
|
||||
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 "✗" 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()
|
||||
|
||||
Reference in New Issue
Block a user