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

252 lines
8.9 KiB
Python

"""US-010: Telemetrie hit regula text in `app_events`.
Cand o operatie nemapata primeste cod RAR dintr-o regula text (substring), apelantii
cu `conn` (ingestie API, import, re-rezolvare) emit un eveniment `text_rule_hit` in
`app_events` cu {submission_id, account_id, pattern, cod_prestatie}. Maparea exacta
(cod_op_service -> cod_prestatie) NU emite acest eveniment. Evenimentul e redactat
(fara PII) si scoped pe cont.
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()
# --------------------------------------------------------------------------- #
# 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 _seed_mapping(get_connection, account_id, op, cod, auto_send=True):
from app.mapping import save_mapping
conn = get_connection()
try:
save_mapping(conn, account_id, op, cod, auto_send=auto_send)
conn.commit()
finally:
conn.close()
def _text_rule_hits(get_connection):
conn = get_connection()
try:
rows = conn.execute(
"SELECT account_id, context_json FROM app_events WHERE tip='text_rule_hit' ORDER BY id"
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
_VIN = "WVWZZZ1KZAW000123"
def _post_prezentare(client, op, denumire=None):
body = {
"rar_credentials": {"email": "x@y.ro", "password": "parola-secreta"},
"prezentari": [{
"vin": _VIN,
"nr_inmatriculare": "B999TST",
"data_prestatie": "2026-06-15",
"odometru_final": "123456",
"prestatii": [{"cod_op_service": op, "denumire": denumire or op}],
}],
}
return client.post("/v1/prezentari", json=body)
# --------------------------------------------------------------------------- #
# 1. Hit regula -> eveniment #
# --------------------------------------------------------------------------- #
def test_hit_regula_emite_app_event(api_env):
"""O operatie rezolvata prin regula text emite `text_rule_hit` cu pattern + cod."""
client, get_connection = api_env
_seed_text_rule(get_connection, 1, "verificare", "OE-2")
r = _post_prezentare(client, "Verificare frane")
assert r.status_code == 200, r.text
res = r.json()["results"][0]
assert res["status"] == "queued", res
sub_id = res["submission_id"]
hits = _text_rule_hits(get_connection)
assert len(hits) == 1, hits
ctx = json.loads(hits[0]["context_json"])
assert ctx["submission_id"] == sub_id
assert ctx["account_id"] == 1
assert ctx["pattern"] == "verificare"
assert ctx["cod_prestatie"] == "OE-2"
# --------------------------------------------------------------------------- #
# 2. Maparea exacta NU emite #
# --------------------------------------------------------------------------- #
def test_mapare_exacta_nu_emite_text_rule_hit(api_env):
"""O operatie rezolvata prin mapare exacta (cod_op_service) nu emite text_rule_hit."""
client, get_connection = api_env
_seed_mapping(get_connection, 1, "Verificare frane", "OE-2")
r = _post_prezentare(client, "Verificare frane")
assert r.status_code == 200, r.text
res = r.json()["results"][0]
assert res["status"] == "queued", res
hits = _text_rule_hits(get_connection)
assert hits == [], hits
# --------------------------------------------------------------------------- #
# 3. Eveniment redactat + scoped pe cont #
# --------------------------------------------------------------------------- #
def test_event_redactat_si_scoped_pe_cont(api_env):
"""Evenimentul e scoped pe cont (coloana account_id) si nu contine PII (VIN integral)."""
client, get_connection = api_env
_seed_text_rule(get_connection, 1, "verificare", "OE-2")
r = _post_prezentare(client, "Verificare frane")
assert r.status_code == 200, r.text
hits = _text_rule_hits(get_connection)
assert len(hits) == 1, hits
assert hits[0]["account_id"] == 1
# Fara PII: VIN-ul integral nu apare in context.
assert _VIN not in (hits[0]["context_json"] or "")
# --------------------------------------------------------------------------- #
# 4. Calea web import commit (routes.py -> web_confirma_import) #
# --------------------------------------------------------------------------- #
@pytest.fixture()
def web_env(monkeypatch):
"""Client web (mod dev) + get_connection + cont creat; require_login fixat pe cont."""
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "rrw.db"))
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
from app.config import get_settings
get_settings.cache_clear()
from app.web import ratelimit
ratelimit._hits.clear()
from app.accounts import create_account
from app.db import get_connection
from app.main import app
with TestClient(app, follow_redirects=False) as c:
conn = get_connection()
acct = create_account(conn, "Cont Web Telemetrie", active=True)
conn.commit()
conn.close()
monkeypatch.setattr("app.web.routes.require_login", lambda r: acct)
yield c, get_connection, acct
ratelimit._hits.clear()
get_settings.cache_clear()
def _make_xlsx(rows):
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()
def _csrf_from(html):
m = re.search(r'name="csrf_token" value="([^"]*)"', html)
return m.group(1) if m else ""
def test_web_import_commit_emite_text_rule_hit(web_env):
"""Import web commit pe un rand a carui operatie da match pe o regula text
emite `text_rule_hit` in app_events cu submission_id-ul randului creat."""
client, get_connection, acct = web_env
_seed_text_rule(get_connection, acct, "verificare", "OE-2")
rows = [{
"vin": "WVWZZZ1KZAW001111", "nr_inmatriculare": "B100TST",
"data_prestatie": "2026-06-15", "odometru_final": "123456",
"operatie": "Verificare frane",
}]
r_up = client.post(
"/_import/upload",
files={"file": ("t.xlsx", _make_xlsx(rows),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")},
)
assert r_up.status_code == 200, r_up.text
csrf = _csrf_from(r_up.text)
batch_id = int(re.search(r"/_import/(\d+)/mapare-coloane", r_up.text).group(1))
r_map = client.post(
f"/_import/{batch_id}/mapare-coloane",
data={
"csrf_token": csrf,
"colname": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"],
"canon": ["vin", "nr_inmatriculare", "data_prestatie", "odometru_final", "operatie"],
},
)
assert r_map.status_code == 200, r_map.text
csrf = _csrf_from(r_map.text) or csrf
r_commit = client.post(f"/_import/{batch_id}/confirma", data={"n_confirmat": "1", "csrf_token": csrf})
assert r_commit.status_code == 200, r_commit.text
conn = get_connection()
try:
sub = conn.execute(
"SELECT id, status FROM submissions WHERE account_id=? AND batch_id=?", (acct, batch_id)
).fetchone()
finally:
conn.close()
assert sub is not None, "submission-ul nu a fost creat"
assert sub["status"] == "queued"
hits = _text_rule_hits(get_connection)
assert len(hits) == 1, hits
ctx = json.loads(hits[0]["context_json"])
assert ctx["submission_id"] == sub["id"]
assert ctx["account_id"] == acct
assert ctx["pattern"] == "verificare"
assert ctx["cod_prestatie"] == "OE-2"