feat(errors): erori pe 3 niveluri (problema+cauza+fix) pe API si UI (PRD 5.4)
Catalog central pur app/errors.py ca sursa unica cod->{problema,fix},
consumat de API+UI+worker. Aditiv (field/message pastrate la octet) +
rar_error stocat superset. Scope: fluxul de declarare; login/signup/CSRF
neatinse. labels.parse_erori degradeaza gratios; UI progresiv AA light+dark.
631 teste.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
180
tests/test_web_import_erori.py
Normal file
180
tests/test_web_import_erori.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""Teste US-007 (PRD 5.4): erori de import pe 3 niveluri in interfata web.
|
||||
|
||||
Verifica ca fragmentele HTML intoarse de rutele web /_import/* contin
|
||||
textele structurate (problema + fix) din catalog pentru erorile de upload
|
||||
si de mapare coloane.
|
||||
|
||||
Rutele testate sunt WEB (HTML), nu API JSON — raspunsul este HTML fragment.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import openpyxl
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Fixture client cu WEB_AUTH_REQUIRED=false (dev mode, cont 1 implicit) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@pytest.fixture()
|
||||
def client(monkeypatch):
|
||||
"""Client FastAPI cu DB temporara izolata, auth web dezactivat (dev mode)."""
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "erori3n.db"))
|
||||
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
|
||||
from app.config import get_settings
|
||||
get_settings.cache_clear()
|
||||
from app.crypto import reset_cache
|
||||
reset_cache()
|
||||
from app.main import app
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
get_settings.cache_clear()
|
||||
reset_cache()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Helpere #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def _make_xlsx_prea_mare(n_randuri: int = 5001) -> bytes:
|
||||
"""Xlsx cu mai mult de 5000 de randuri de date (declanseaza FileTooLarge)."""
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append(["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"])
|
||||
for i in range(n_randuri):
|
||||
ws.append([
|
||||
f"WVWZZZ1KZA{i:07d}",
|
||||
f"B{i:04d}TST",
|
||||
"2026-06-15",
|
||||
str(100000 + i),
|
||||
"Revizie",
|
||||
])
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _upload_web(client: TestClient, data: bytes, filename: str = "test.xlsx") -> object:
|
||||
"""POST pe ruta web de upload (intoarce HTML fragment)."""
|
||||
return client.post(
|
||||
"/_import/upload",
|
||||
files={"file": (filename, io.BytesIO(data), "application/octet-stream")},
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 1. Upload fisier prea mare → fragment cu fix din IMPORT_FISIER_PREA_MARE #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_upload_fisier_prea_mare_3niveluri(client):
|
||||
"""Upload xlsx >5000 randuri → fragment HTML contine textul fix din catalog.
|
||||
|
||||
Textul asteptat din CATALOG['IMPORT_FISIER_PREA_MARE']['fix']:
|
||||
'Imparte fisierul in bucati de maxim 5000 de randuri si incarca-le pe rand.'
|
||||
"""
|
||||
from app.errors import CATALOG
|
||||
fix_asteptat = CATALOG["IMPORT_FISIER_PREA_MARE"]["fix"]
|
||||
|
||||
data = _make_xlsx_prea_mare(5001)
|
||||
r = _upload_web(client, data, "mare.xlsx")
|
||||
|
||||
assert r.status_code == 200, f"Asteptat 200, primit {r.status_code}: {r.text[:300]}"
|
||||
html = r.text
|
||||
assert fix_asteptat in html, (
|
||||
f"Textul fix din IMPORT_FISIER_PREA_MARE nu apare in fragmentul HTML.\n"
|
||||
f"Asteptat: {fix_asteptat!r}\n"
|
||||
f"Fragment (primii 800 chars): {html[:800]}"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 2. Upload fisier nerecunoscut → fragment cu fix din IMPORT_FISIER_NERECUNOSCUT
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_upload_fisier_nerecunoscut_3niveluri(client):
|
||||
"""Upload bytes invalizi (nu e xlsx/csv valid) → fragment HTML cu fix din catalog.
|
||||
|
||||
Textul asteptat din CATALOG['IMPORT_FISIER_NERECUNOSCUT']['fix']:
|
||||
'Incarca un fisier .xlsx sau .csv valid.'
|
||||
"""
|
||||
from app.errors import CATALOG
|
||||
fix_asteptat = CATALOG["IMPORT_FISIER_NERECUNOSCUT"]["fix"]
|
||||
problema_asteptata = CATALOG["IMPORT_FISIER_NERECUNOSCUT"]["problema"]
|
||||
|
||||
# Bytes invalizi: nu este un zip/xlsx, nu este text CSV
|
||||
date_invalide = b"\x00\x01\x02\x03\x04\x05binar_junk_non_xlsx"
|
||||
r = _upload_web(client, date_invalide, "test.xlsx")
|
||||
|
||||
assert r.status_code == 200, f"Asteptat 200, primit {r.status_code}: {r.text[:300]}"
|
||||
html = r.text
|
||||
assert fix_asteptat in html, (
|
||||
f"Textul fix din IMPORT_FISIER_NERECUNOSCUT nu apare in fragmentul HTML.\n"
|
||||
f"Asteptat: {fix_asteptat!r}\n"
|
||||
f"Fragment (primii 800 chars): {html[:800]}"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 3. Mapare coloane cu JSON invalid → fragment cu fix din COLOANE_FORMAT_JSON #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_mapcoloane_format_json_3niveluri(client):
|
||||
"""POST direct pe ruta de mapare coloane cu corp JSON invalid → fragment cu fix din catalog.
|
||||
|
||||
Textul asteptat din CATALOG['COLOANE_FORMAT_JSON']['fix']:
|
||||
'Verifica sintaxa JSON a maparii de coloane (ghilimele duble, acolade inchise corect).'
|
||||
|
||||
Ruta /_import/{id}/mapare-coloane accepta form data. Codul de test simuleaza
|
||||
un batch_id invalid (0) pentru a forta ramura de eroare, sau injecteaza direct
|
||||
un batch_id valid cu JSON invalid in campul de mapare. Deoarece ruta nu primeste
|
||||
JSON direct, testam ramura din routes.py unde se face json.dumps si se valideaza
|
||||
manual, sau modificam testul sa plaseze un batch valid si sa trimita date malformate.
|
||||
|
||||
Strategia: upload un fisier valid, obtine import_id, trimite un form cu valoare
|
||||
json invalida in campul coloane (via Content-Type: application/json intentionat gresit
|
||||
sau via parametru form malformat). In implementarea din routes.py, eroarea
|
||||
COLOANE_FORMAT_JSON este randata cand json.loads esueaza pe un camp special.
|
||||
"""
|
||||
from app.errors import CATALOG
|
||||
fix_asteptat = CATALOG["COLOANE_FORMAT_JSON"]["fix"]
|
||||
|
||||
# Primul pas: upload un fisier valid pentru a obtine un import_id real
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append(["VIN", "Nr", "Data", "KM", "Op"])
|
||||
ws.append(["WVWZZZ1KZAW000123", "B001TST", "2026-06-15", "100000", "Revizie"])
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
xlsx_data = buf.getvalue()
|
||||
|
||||
r_upload = _upload_web(client, xlsx_data, "test.xlsx")
|
||||
assert r_upload.status_code == 200, r_upload.text[:300]
|
||||
|
||||
# Extrage import_id din raspuns
|
||||
import re
|
||||
m = re.search(r"/_import/(\d+)/mapare-coloane", r_upload.text)
|
||||
assert m, f"Nu s-a gasit import_id in raspuns: {r_upload.text[:500]}"
|
||||
import_id = int(m.group(1))
|
||||
|
||||
# Trimite un request pe ruta de mapare coloane cu Content-Type: application/json
|
||||
# si corp JSON invalid → ruta ar trebui sa intoarca eroarea COLOANE_FORMAT_JSON
|
||||
r = client.post(
|
||||
f"/_import/{import_id}/mapare-coloane",
|
||||
content=b"{invalid json}",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
assert r.status_code == 200, f"Asteptat 200, primit {r.status_code}: {r.text[:300]}"
|
||||
html = r.text
|
||||
assert fix_asteptat in html, (
|
||||
f"Textul fix din COLOANE_FORMAT_JSON nu apare in fragmentul HTML.\n"
|
||||
f"Asteptat: {fix_asteptat!r}\n"
|
||||
f"Fragment (primii 800 chars): {html[:800]}"
|
||||
)
|
||||
Reference in New Issue
Block a user