Files
rar-autopass/tests/test_import_web_scope.py
Claude Agent 958b182e8e feat(config): web_auth_required ON implicit (login obligatoriu pe rutele web)
Inverseaza default-ul C12: rutele web cer sesiune + CSRF implicit (sigur pentru
prod). Dev rapid pe contul 1 = opt-out explicit AUTOPASS_WEB_AUTH_REQUIRED=false.
Testele de comportament import/dashboard marcate explicit dev-mode; test nou
blocheaza default-ul. 394 teste pass.

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

202 lines
8.3 KiB
Python

"""Teste US-006a/b (PRD 3.3): scoping import web pe sesiune.
US-006a: citiri (upload, preview, mapare-coloane) pe contul sesiunii.
US-006b: scrieri (confirma) pe contul sesiunii; alt cont -> inaccesibil.
C8/OV-2: aceeasi cheie idempotenta prin API si web pe acelasi cont.
"""
from __future__ import annotations
import io
import re
import os
import tempfile
import openpyxl
import pytest
from fastapi.testclient import TestClient
def _make_xlsx(rows: list[dict]) -> bytes:
wb = openpyxl.Workbook()
ws = wb.active
if rows:
ws.append(list(rows[0].keys()))
for r in rows:
ws.append(list(r.values()))
buf = io.BytesIO()
wb.save(buf)
return buf.getvalue()
_ROWS = [
{"vin": "WVWZZZ1KZAW111111", "nr_inmatriculare": "B111TST",
"data_prestatie": "2026-06-01", "odometru_final": "10000",
"cod_prestatie": "OE-1"},
]
def _csrf_from(html: str) -> str:
"""Extrage tokenul CSRF din HTML (hidden input)."""
m = re.search(r'name="csrf_token" value="([^"]*)"', html)
return m.group(1) if m else ""
@pytest.fixture()
def env(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "scope.db"))
# Scoping testat prin monkeypatch require_login pe acct_a/acct_b; rulam in mod
# dev (CSRF skip fara sesiune) — auth web e default ON in prod, testat in test_web_*.
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
from app.config import get_settings
get_settings.cache_clear()
from app.main import app
with TestClient(app, follow_redirects=False) as c:
from app.db import get_connection
conn = get_connection()
from app.accounts import create_account
acct_a = create_account(conn, "Cont A Scope", active=True)
acct_b = create_account(conn, "Cont B Scope", active=True)
yield c, conn, acct_a, acct_b
conn.close()
get_settings.cache_clear()
def _setup_op_mapping(conn, account_id):
"""Configureaza maparea operatie cod_op=OE-1 -> cod_prestatie=OE-1 pt. cont."""
conn.execute(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
("OE-1", "Operatii electrice"),
)
conn.execute(
"INSERT OR REPLACE INTO operations_mapping "
"(account_id, cod_op_service, cod_prestatie, auto_send) VALUES (?, ?, ?, ?)",
(account_id, "OE-1", "OE-1", 1),
)
def _mapare_coloane(c, import_id, csrf_token: str = ""):
"""Salveaza maparea de coloane; mapeaza cod_prestatie -> operatie (canonical).
cod_prestatie din xlsx trebuie mapat la 'operatie' (nu la 'cod_prestatie' care nu
e camp canonic). resolve_prestatii il rezolva din operations_mapping.
"""
return c.post(
f"/_import/{import_id}/mapare-coloane",
data={
"csrf_token": csrf_token,
"colname": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "cod_prestatie"],
"canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"],
},
)
def test_upload_pe_contul_sesiunii(env, monkeypatch):
"""Upload creeaza batch pe contul din sesiune (nu DEFAULT_ACCOUNT_ID)."""
client, conn, acct_a, acct_b = env
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_a)
r = client.post("/_import/upload", files={"file": ("a.xlsx", _make_xlsx(_ROWS), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
assert r.status_code == 200
batch = conn.execute("SELECT id, account_id FROM import_batches").fetchone()
assert batch is not None
assert batch["account_id"] == acct_a
def test_batch_alt_cont_inaccesibil(env, monkeypatch):
"""Batch-ul contului A nu e accesibil din sesiunea contului B (preview -> eroare)."""
client, conn, acct_a, acct_b = env
# Upload ca A (sesiune curata, fara csrf_token anterior)
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_a)
client.post("/_import/upload", files={"file": ("a.xlsx", _make_xlsx(_ROWS), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
batch_id = conn.execute("SELECT id FROM import_batches WHERE account_id=?", (acct_a,)).fetchone()["id"]
# Preview ca B (GET, fara CSRF) -> trebuie eroare/inaccesibil
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_b)
r = client.get(f"/_import/{batch_id}/preview")
assert r.status_code == 200
assert "inexistent" in r.text.lower() or "inaccesibil" in r.text.lower()
def test_commit_creeaza_submissions_pe_cont(env, monkeypatch):
"""Confirma creeaza submissions cu account_id-ul sesiunii."""
client, conn, acct_a, acct_b = env
_setup_op_mapping(conn, acct_a)
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_a)
# Upload — raspunsul contine csrf_token in form (sesiunea l-a creat)
r_upload = client.post("/_import/upload", files={"file": ("a.xlsx", _make_xlsx(_ROWS), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
csrf = _csrf_from(r_upload.text)
batch_id = conn.execute("SELECT id FROM import_batches WHERE account_id=?", (acct_a,)).fetchone()["id"]
# Mapare coloane cu tokenul din upload
r_map = _mapare_coloane(client, batch_id, csrf)
csrf = _csrf_from(r_map.text) or csrf # tokenul din preview (stabil per sesiune)
# Confirma cu tokenul sesiunii
r = client.post(f"/_import/{batch_id}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
assert r.status_code == 200
sub = conn.execute("SELECT account_id FROM submissions").fetchone()
assert sub is not None
assert sub["account_id"] == acct_a
def test_cheie_identica_api_vs_web_acelasi_cont(env, monkeypatch):
"""C8/OV-2: import web si API pe acelasi cont produc aceeasi cheie idempotenta."""
from app.idempotency import build_key, canonicalize_row
client, conn, acct_a, acct_b = env
_setup_op_mapping(conn, acct_a)
row = {
"vin": "WVWZZZ1KZAW999999",
"nr_inmatriculare": "B999TST",
"data_prestatie": "2026-06-15",
"odometru_final": "99999",
"prestatii": [{"cod_prestatie": "OE-1"}],
}
canon = canonicalize_row(row)
key_api = build_key(acct_a, canon)
# Upload web pe acelasi cont
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_a)
web_row = {
"vin": "WVWZZZ1KZAW999999",
"nr_inmatriculare": "B999TST",
"data_prestatie": "2026-06-15",
"odometru_final": "99999",
"cod_prestatie": "OE-1",
}
r_up = client.post("/_import/upload", files={"file": ("w.xlsx", _make_xlsx([web_row]), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
csrf = _csrf_from(r_up.text)
batch_id = conn.execute("SELECT id FROM import_batches WHERE account_id=?", (acct_a,)).fetchone()["id"]
r_map = _mapare_coloane(client, batch_id, csrf)
csrf = _csrf_from(r_map.text) or csrf
client.post(f"/_import/{batch_id}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
sub = conn.execute("SELECT idempotency_key FROM submissions WHERE account_id=?", (acct_a,)).fetchone()
assert sub is not None
assert sub["idempotency_key"] == key_api
def test_confirma_alt_cont_inaccesibil(env, monkeypatch):
"""Confirma batch-ul contului A din sesiunea B -> eroare batch inexistent."""
client, conn, acct_a, acct_b = env
_setup_op_mapping(conn, acct_a)
# Upload + mapare ca A (cu CSRF tokens corecti)
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_a)
r_up = client.post("/_import/upload", files={"file": ("a.xlsx", _make_xlsx(_ROWS), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")})
csrf = _csrf_from(r_up.text)
batch_id = conn.execute("SELECT id FROM import_batches WHERE account_id=?", (acct_a,)).fetchone()["id"]
r_map = _mapare_coloane(client, batch_id, csrf)
csrf = _csrf_from(r_map.text) or csrf
# Confirma ca B cu tokenul din sesiune (acelasi cookie jar, token valid CSRF)
# dar batch apartine lui A -> "inexistent sau expirat"
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct_b)
r = client.post(f"/_import/{batch_id}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
assert r.status_code == 200
assert "inexistent" in r.text.lower() or "inaccesibil" in r.text.lower() or "expirat" in r.text.lower()