Files
rar-autopass/tests/test_web_scope.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

232 lines
8.7 KiB
Python

"""US-011 (PRD 5.15): account-scope pe GET-urile de listare web (securitate).
Verifica:
- /_fragments/submissions: un cont nu vede randurile altui cont
- /_fragments/trimitere/{id}: 404-before-leak pe id strain
- /_fragments/nomenclator: necesita autentificare (fragment dashboard, nu endpoint public)
- /_fragments/trimiteri-versiune: necesita autentificare
- Unauthenticated access -> redirect 303 pe ORICE fragment cu date
Legatura cu implementare: mecanismul de scope existent (require_login +
account_scope_clause) se reutilizeaza fara logica noua. Nomenclatorul primeste
require_login din US-011 (fragment dashboard, nu endpoint public).
"""
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, "csrf_token negasit in pagina de login"
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",
vin: str = "WVWZZZ1JZXW000777",
nr: str = "B777TST",
) -> int:
from app.db import get_connection
conn = get_connection()
try:
p = {
"vin": vin,
"nr_inmatriculare": nr,
"data_prestatie": "2026-06-18",
"odometru_final": "50000",
"prestatii": [{"cod_prestatie": "OE-1", "denumire": "Revizie"}],
}
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) "
"VALUES (?, ?, ?, ?)",
(f"scope-{acct}-{os.urandom(4).hex()}", acct, status, json.dumps(p)),
)
conn.commit()
rid = cur.lastrowid
assert rid is not None
return int(rid)
finally:
conn.close()
@pytest.fixture()
def client(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "scope.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()
# ---------------------------------------------------------------------------
# Test 1: cross-account isolation pe listare submissions
# ---------------------------------------------------------------------------
def test_get_listare_scoped_cont(client):
"""Un cont NU vede randurile (VIN/PII) ale altui cont in /_fragments/submissions.
Contul A are un submission cu nr. inmatriculare unic NR_A; contul B nu trebuie
sa vada NR_A in listarea sa. Verificam izolarea atat prin nr. cat si prin VIN
(ultimele 6 caractere afisate ca vin_scurt in template).
"""
NR_A = "BV01SCO"
NR_B = "BV02SCO"
VIN_A = "WVWZZZ1JZXW111AAA" # vin_scurt va fi '...111AAA'
VIN_B = "WVWZZZ1JZXW222BBB" # vin_scurt va fi '...222BBB'
acct_a = _create_account_user("scope-a@test.com", name="ContA")
acct_b = _create_account_user("scope-b@test.com", name="ContB")
_insert_submission(acct_a, vin=VIN_A, nr=NR_A)
_insert_submission(acct_b, vin=VIN_B, nr=NR_B)
# Login ca cont B
_login(client, "scope-b@test.com")
resp = client.get("/_fragments/submissions")
assert resp.status_code == 200
html = resp.text
# Contul B vede propriul nr inmatriculare
assert NR_B in html, "Contul B ar trebui sa vada propriul nr inmatriculare"
# Contul B NU vede nr inmatriculare si VIN (vin_scurt) ale contului A
assert NR_A not in html, "Scurgere cross-account: nr_inmatriculare contului A vizibil contului B"
assert "111AAA" not in html, "Scurgere cross-account: VIN (vin_scurt) contului A vizibil contului B"
# ---------------------------------------------------------------------------
# Test 2: unauthenticated -> redirect pe listare submissions
# ---------------------------------------------------------------------------
def test_get_listare_neautentificat_redirect_submissions(client):
"""Fara sesiune activa, /_fragments/submissions returneaza 303 (redirect /login)."""
resp = client.get("/_fragments/submissions")
assert resp.status_code == 303, (
f"Asteptat 303 redirect, primit {resp.status_code}. "
"/_fragments/submissions trebuie sa necesite autentificare."
)
# ---------------------------------------------------------------------------
# Test 3: 404-before-leak pe detaliu id strain
# ---------------------------------------------------------------------------
def test_get_detaliu_scoped_404(client):
"""Detaliul unui submission apartinand altui cont returneaza 404 (fara leak).
Acelasi 404 pentru id inexistent = nu confirmam existenta.
"""
acct_a = _create_account_user("detscope-a@test.com", name="DetA")
_create_account_user("detscope-b@test.com", name="DetB")
sid_a = _insert_submission(acct_a, vin="WVWZZZ1JZXWAAA111")
# Login ca cont B
_login(client, "detscope-b@test.com")
# Cerere detaliu pe submission-ul contului A
resp = client.get(f"/_fragments/trimitere/{sid_a}")
assert resp.status_code == 404, (
f"Asteptat 404, primit {resp.status_code}. "
"Nu trebuie confirmata existenta unui submission al altui cont."
)
# Id inexistent -> acelasi 404 (nu confirmam existenta)
resp2 = client.get("/_fragments/trimitere/999999")
assert resp2.status_code == 404
# ---------------------------------------------------------------------------
# Test 4: nomenclator necesita autentificare (RED inainte de fix)
# ---------------------------------------------------------------------------
def test_get_nomenclator_neautentificat_redirect(client):
"""/_fragments/nomenclator este un fragment al dashboard-ului autentificat.
Fara sesiune, trebuie sa returneze 303 redirect la /login.
RED inainte de fix: in prezent fragmentul nu apeleaza require_login
si returneaza 200 chiar fara autentificare.
"""
resp = client.get("/_fragments/nomenclator")
assert resp.status_code == 303, (
f"Asteptat 303 redirect pentru fragment dashboard neautentificat, "
f"primit {resp.status_code}. "
"/_fragments/nomenclator trebuie sa necesite autentificare."
)
def test_get_nomenclator_autentificat_ok(client):
"""/_fragments/nomenclator accesibil dupa autentificare."""
_create_account_user("nom-auth@test.com", name="NomAuth")
_login(client, "nom-auth@test.com")
resp = client.get("/_fragments/nomenclator")
assert resp.status_code == 200
# ---------------------------------------------------------------------------
# Test 5: trimiteri-versiune necesita autentificare
# ---------------------------------------------------------------------------
def test_get_trimiteri_versiune_neautentificat_redirect(client):
"""/_fragments/trimiteri-versiune necesita autentificare (redirect 303 fara sesiune)."""
resp = client.get("/_fragments/trimiteri-versiune")
assert resp.status_code == 303
# ---------------------------------------------------------------------------
# Test 6: izolare paginare - filtru nu poate scoate randuri strain
# ---------------------------------------------------------------------------
def test_get_listare_filtru_nu_sparge_scope(client):
"""Filtrele (status, vehicul) nu pot scoate randuri din alt cont.
Un cont B cu filtru vehicul=VIN_A nu trebuie sa vada niciodata VIN_A.
"""
VIN_A = "WVWZZZ1JZXW333CCC"
NR_A = "BV03FLT"
acct_a = _create_account_user("filtru-a@test.com", name="FiltrA")
_create_account_user("filtru-b@test.com", name="FiltrB")
_insert_submission(acct_a, vin=VIN_A, nr=NR_A)
_login(client, "filtru-b@test.com")
# Cont B incearca sa filtreze dupa nr inmatriculare al contului A
resp = client.get(f"/_fragments/submissions?vehicul={NR_A}")
assert resp.status_code == 200
assert NR_A not in resp.text, (
"Filtrul vehicul a scos date din alt cont (scurgere cross-account prin filtru)."
)