Files
rar-autopass/tests/test_reresolve_text_rules.py
Claude Agent 51dc504f1d feat(5.8): reguli mapare pe text (substring/cont) + UX tabel trimiteri (detaliu inline, fara scroll, cod RAR)
Reguli text per cont (operation_text_rules), resolve_prestatii cu param aditiv
text_rules + precedenta stricta, threadat pe toate cele 6 callsite-uri + valid_codes
+ seam classify_prezentare. UI Mapari: sectiune reguli + preview pre-salvare + overlap
+ telemetrie text_rule_hit. UX tabel: cod_rar sub operatie, pill eticheta scurta, fara
scroll orizontal (scopat .tabel-trimiteri + carduri <768px), detaliu inline expandabil
(a11y + pauza poll). code-review: reparat regula auto_send=0 care trimitea automat la RAR
in loc sa tina randul pentru review. 814 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:47:37 +00:00

268 lines
9.7 KiB
Python

"""US-003: Reguli text active la ingestie (API + import + corectie web) si la
re-rezolvarea blocajelor (`reresolve_account`).
Verifica ca o regula text salvata in prealabil rezolva o operatie fara mapare
exacta pe TOATE caile de ingestie, in loc sa o lase `needs_mapping`, si ca la
re-rezolvare un rand `needs_mapping` care acum da match pe o regula se deblocheaza.
Codul rezolvat din regula respecta validarea fata de nomenclator (US-002): folosim
`OE-2`, cod valid din seed-ul nomenclatorului.
"""
from __future__ import annotations
import io
import json
import os
import re
import tempfile
import openpyxl
import pytest
from fastapi.testclient import TestClient
# --------------------------------------------------------------------------- #
# Fixtures #
# --------------------------------------------------------------------------- #
@pytest.fixture()
def api_env(monkeypatch):
"""Client API + get_connection, DB temporara izolata (fara web-auth)."""
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "rr.db"))
from app.config import get_settings
get_settings.cache_clear()
from app.db import get_connection
from app.main import app
with TestClient(app) as c:
yield c, get_connection
get_settings.cache_clear()
@pytest.fixture()
def web_env(monkeypatch):
"""Client web (auth pornit) + get_connection, DB temporara izolata."""
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "rrw.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.db import get_connection
from app.main import app
with TestClient(app, follow_redirects=False) as c:
yield c, get_connection
ratelimit._hits.clear()
get_settings.cache_clear()
# --------------------------------------------------------------------------- #
# Helpere #
# --------------------------------------------------------------------------- #
def _seed_text_rule(get_connection, account_id, pattern, cod, auto_send=True):
from app.mapping import save_text_rule
conn = get_connection()
try:
save_text_rule(conn, account_id, pattern, cod, auto_send=auto_send)
conn.commit()
finally:
conn.close()
def _make_xlsx(rows):
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Sheet1"
for row in rows:
ws.append(row)
buf = io.BytesIO()
wb.save(buf)
return buf.getvalue()
def _row_status_cod(get_connection, sub_id):
conn = get_connection()
try:
r = conn.execute(
"SELECT status, payload_json FROM submissions WHERE id=?", (sub_id,)
).fetchone()
payload = json.loads(r["payload_json"]) if r["payload_json"] else {}
cod = (payload.get("prestatii") or [{}])[0].get("cod_prestatie")
return r["status"], cod
finally:
conn.close()
# --------------------------------------------------------------------------- #
# 1. Ingestie API (router.py -> classify_prezentare) #
# --------------------------------------------------------------------------- #
def test_ingestie_api_aplica_regula_text(api_env):
"""POST /v1/prezentari cu operatie fara mapare exacta dar match pe regula text
-> queued (cod din regula), nu needs_mapping."""
client, get_connection = api_env
_seed_text_rule(get_connection, 1, "verificare", "OE-2")
body = {
"rar_credentials": {"email": "x@y.ro", "password": "s"},
"prezentari": [{
"vin": "WVWZZZ1KZAW000123",
"nr_inmatriculare": "B999TST",
"data_prestatie": "2026-06-15",
"odometru_final": "123456",
"prestatii": [{"cod_op_service": "Verificare frane", "denumire": "Verificare frane"}],
}],
}
r = client.post("/v1/prezentari", json=body)
assert r.status_code == 200, r.text
res = r.json()["results"][0]
assert res["status"] == "queued", res
assert not res.get("nemapate")
status, cod = _row_status_cod(get_connection, res["submission_id"])
assert status == "queued"
assert cod == "OE-2"
# --------------------------------------------------------------------------- #
# 2. Ingestie import (import_router.py preview + commit) #
# --------------------------------------------------------------------------- #
def test_ingestie_import_aplica_regula_text(api_env):
"""Import xlsx cu operatie fara mapare exacta dar match pe regula text:
preview o marcheaza 'ok' si commit o pune 'queued' (cod din regula)."""
client, get_connection = api_env
_seed_text_rule(get_connection, 1, "verificare", "OE-2")
header = ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"]
row = ["WVWZZZ1KZAW001111", "B100TST", "2026-06-15", "123456", "Verificare frane"]
data = _make_xlsx([header, row])
r = client.post(
"/v1/import",
files={"file": ("t.xlsx", io.BytesIO(data), "application/octet-stream")},
)
assert r.status_code == 200, r.text
import_id = r.json()["import_id"]
rc = client.post(f"/v1/import/{import_id}/column-mapping", json={"json_mapare": {
"VIN": "vin",
"Nr inmatriculare": "nr_inmatriculare",
"Data prestatie": "data_prestatie",
"Odometru final": "odometru_final",
"Operatie": "operatie",
}})
assert rc.status_code == 200, rc.text
rp = client.get(f"/v1/import/{import_id}/preview")
assert rp.status_code == 200, rp.text
assert rp.json()["summary"].get("ok", 0) == 1, rp.json()["summary"]
rcommit = client.post(f"/v1/import/{import_id}/commit", json={
"n_confirmat": 1, "reviewed_rows": [],
})
assert rcommit.status_code == 200, rcommit.text
assert rcommit.json()["enqueued"] == 1
sub_id = rcommit.json()["submissions"][0]["submission_id"]
status, cod = _row_status_cod(get_connection, sub_id)
assert status == "queued"
assert cod == "OE-2"
# --------------------------------------------------------------------------- #
# 3. Corectie web (routes.py -> post_corectie_trimitere) #
# --------------------------------------------------------------------------- #
def _create_account_user(get_connection, email, name="Service", password="parolasecreta10"):
from app.accounts import create_account
from app.users import create_user
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, password="parolasecreta10"):
resp = client.get("/login")
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text)
assert m
resp = client.post("/login", data={"email": email, "parola": password, "csrf_token": m.group(1)})
assert resp.status_code == 303
def _csrf(client):
resp = client.get("/?tab=acasa")
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text)
assert m
return m.group(1)
def _insert_needs_mapping(get_connection, acct, op, denumire=None, batch_id=None):
conn = get_connection()
try:
payload = {
"vin": "WVWZZZ1KZAW000123", "nr_inmatriculare": "B123ABC",
"data_prestatie": "2026-06-10", "odometru_final": "159004",
"prestatii": [{"cod_prestatie": None, "cod_op_service": op, "denumire": denumire or op}],
}
k = f"k-{os.urandom(6).hex()}"
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, rar_error, batch_id) "
"VALUES (?, ?, 'needs_mapping', ?, ?, ?)",
(k, acct, json.dumps(payload), json.dumps({"unmapped": [{"cod_op_service": op}]}), batch_id),
)
conn.commit()
return int(cur.lastrowid)
finally:
conn.close()
def test_corectie_web_aplica_regula_text(web_env):
"""POST /trimitere/{id}/corecteaza pe un needs_mapping a carui operatie acum da
match pe o regula text -> randul intra 'queued' (cod din regula)."""
client, get_connection = web_env
acct = _create_account_user(get_connection, "cor@test.com")
sid = _insert_needs_mapping(get_connection, acct, op="Verificare frane")
_seed_text_rule(get_connection, acct, "verificare", "OE-2")
_login(client, "cor@test.com")
csrf = _csrf(client)
resp = client.post(f"/trimitere/{sid}/corecteaza", data={"csrf_token": csrf})
assert resp.status_code == 200, resp.text
status, cod = _row_status_cod(get_connection, sid)
assert status == "queued"
assert cod == "OE-2"
# --------------------------------------------------------------------------- #
# 4. Re-rezolvare blocaje (mapping.reresolve_account) #
# --------------------------------------------------------------------------- #
def test_salvare_regula_rerezolva_blocate(api_env):
"""Dupa salvarea unei reguli noi, reresolve_account deblocheaza randurile
needs_mapping care acum dau match (acelasi mecanism ca la save_mapping)."""
client, get_connection = api_env
sid = _insert_needs_mapping(get_connection, 1, op="Verificare frane")
_seed_text_rule(get_connection, 1, "verificare", "OE-2")
from app.mapping import reresolve_account
conn = get_connection()
try:
stats = reresolve_account(conn, 1)
conn.commit()
finally:
conn.close()
assert stats["requeued"] == 1, stats
status, cod = _row_status_cod(get_connection, sid)
assert status == "queued"
assert cod == "OE-2"