diff --git a/CLAUDE.md b/CLAUDE.md index 531dd53..57f2626 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,10 +29,16 @@ python3 -m app.worker # worker (necesar doar pentru a proce ./start.sh stop # opreste procesele pornite cu "both" ./start-test.sh / ./start-prod.sh # fixeaza mediul, forwardeaza rolul -# Teste (pytest, fara config special; folosesc FastAPI TestClient + SQLite temporar) +# Teste (pytest; folosesc FastAPI TestClient + SQLite temporar). Testele live RAR sunt +# skip implicit (marker `live`) — `pytest -q` nu atinge endpointul real. python3 -m pytest -q python3 -m pytest tests/test_worker_reconcile.py -q # un fisier python3 -m pytest tests/test_worker_reconcile.py::test_x -q # un singur test +python3 -m pytest -q -m "not live" # exclude explicit testele live + +# Test LIVE pe RAR test (opt-in, skip implicit; atinge endpointul real -> creeaza FINALIZATA): +# reproduce lantul mapare inline -> queued -> worker -> sent -> verificare in finalizate. +AUTOPASS_LIVE_RAR=1 python3 -m pytest tests/test_live_rar.py -q # necesita settings.xml cu creds # Lifecycle chei API (admin, doar din CLI — nu exista suprafata HTTP) python3 -m tools.apikey create --account 2 # cheie afisata O SINGURA DATA (rfak_...) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 93cc21e..e327201 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -48,7 +48,7 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi > PRD-uri (`docs/prd/prd-X.Y-*.md`), linkate in coloana Detalii. La fiecare livrabila terminata: > schimba statusul + data + linkul PRD si actualizeaza "Ultima actualizare". -**Ultima actualizare**: 2026-06-23 — 5.7 LIVRAT (raspuns API onest la blocaje + mapare inline din detaliu). Raportat din client VFP: `POST /v1/prezentari` intorcea `submission_id`+`status` FARA motiv pe randuri blocate (`erori` se popula doar pe ramura `on_unmapped_error=True`) → un `needs_data`/`needs_mapping` parea succes ("raspuns fara erori"). Reparat ADITIV: `SubmissionResult` += `nemapate` + `motiv`; `create_prezentari` populeaza `erori` (validare continut, 3 niveluri) / `nemapate` (coduri fara mapare, COD_NEMAPAT) / `motiv` (rezumat uman) pe TOATE caile non-`queued` — enqueue, respins (`on_unmapped_error=True`) si reactivare dedup peste `error`. UI: mapare inline in panoul de detaliu trimitere (`POST /trimitere/{id}/mapeaza`, reuse EXACT `save_mapping`+`reresolve_account`, scoped sesiune + CSRF, re-rezolva pe `batch_id`-ul randului → deblocheaza si randurile-frate; apare doar pe operatii nemapate reale, nu pe auto_send=0) — fara drum prin tab-ul Mapari. `/code-review high`: 2 buguri reale reparate (reactivarea omitea `erori`/`nemapate`/`motiv`; dublu `load_nomenclator` in `_detaliu_ctx`), restul candidatilor infirmati la verify (parse `auto_send` corect via `or ""`; lipsa `conn/account_id` pe ramuri de corectie nereachable cu needs_mapping+unmapped). `pytest -q` **765 passed, 0 failed**. PRD: [prd-5.7](prd/prd-5.7-raspuns-onest-mapare-inline.md). | ISTORIC: FIX out-of-process (raportat din client VFP): `cod_prestatie` necunoscut in nomenclator era trimis raw la RAR → **HTTP 500** (`ORA-12899`, coloana `COD_PRESTATIE` max 5 car.) + record PARTIAL `FINALIZATA` (RAR ne-tranzactional) pe care reconcilierea il marca fals `sent`. Reparat: validare `cod_prestatie` fata de nomenclator la ingestie (cod necunoscut → tratat ca operatie de mapat, nu se mai trimite raw) + optiune boolean `on_unmapped_error` (`false` default → needs_mapping | `true` → respinge) per-cerere cu default per-cont `accounts.on_unmapped_error_default` (migrare aditiva). Confirmat live raspunsul RAR (500 pe cod intern vs 200 pe `OE-1`). Inclus si in `c842e33`: fix lease orfan worker (nepotrivire format data sending_since vs cutoff → orice rand `sending` parea expirat) + guard anti-dublu-POST + fix UI `hx-confirm` mostenit pe randuri (alerta de stergere la click pe rand). Teste: **748 passed** (cele 2 esecuri pre-existente fara legatura). Contract + CLAUDE.md actualizate. | 5.6 IMPLEMENTAT + VERIFY PASS (asteapta commit). Cele 14 stories din PRD 5.6 livrate TDD (RED->GREEN), `pytest -q` **741 passed, 0 failed**. Lifecycle trimiteri blocate (Val A primul, decizie #18): `app/submissions_admin.py` (sterge/repune scoped, 404-before-409); reactivare dedup peste `error` cu CAS + invalidare sesiune worker la creds noi (T1) + propagare `accounts.rar_creds_enc` (#17) + camp aditiv `reactivated:true` (#19); retentie randuri blocate 30z + `purge_after` curatat la reactivare/requeue (T2); API `DELETE`/`/repune` (200+JSON, #20); UI butoane + bulk + banner "Necesita atentia ta" actionabil cu deep-link. Observabilitate: `app/observ.py log_event` (dublu canal `app_events` DB + `RotatingFileHandler` per-proces, redactare creds/PII la scriere via `app/security.redact_pii`/`vin_partial`), `request_id` middleware + `X-Request-ID` pe toate raspunsurile (T8), handler global excepții -> 500 envelope 6-chei + request_id (T7), audit cerere API (`api_prezentari`/`api_auth_esuat`) + audit worker (`rar_login`/tranzitii), tab "Jurnal" filtrabil scoped (non-admin doar contul sau), retentie jurnal 90z. Live RAR `--send` NEPROBAT in sesiune (recomandat la deploy: confirma `rar_login` ok + `submission_sent` in jurnal). PRD actualizat cu raport VERIFY; contract actualizat cu endpointurile noi (T10). | ISTORIC: HOTFIX livrat + 5.6 APROBAT. Hotfix 500 pe `POST /v1/prezentari` (raportat din client Visual FoxPro): `AUTOPASS_CREDS_KEY` din `.env` nu respecta formatul Fernet (32 bytes url-safe base64) → `ValueError` la primul `encrypt_creds` → 500 brut. Reparat: cheie Fernet valida in `.env` + `crypto.validate_creds_key()` apelata in `main.lifespan` (fail-fast la startup, mesaj clar in loc de 500 la primul POST). Confirmat live: POST VFP → 200 `queued`; trimitere reala pe RAR test → `sent idPrezentare=68818` (verificat independent in finalizate). Corectat si mesajul fals din dashboard pentru starea `error` in `labels.py` ("se reincearca automat" → starea e terminala, NU se reincearca). Investigatia a expus 3 goluri structurale (500 brut fara traducere 3 niveluri; lipsa jurnal de aplicatie la nivel de eveniment; lacune de lifecycle — randuri blocate permanente, dedup blocat de un rand `error`, banner "Necesita atentia ta" neactionabil) → **PRD 5.6 APROBAT** (14 stories; decizii §5 rezolvate cu user). PRD: [prd-5.6](prd/prd-5.6-observabilitate-jurnal.md). | ISTORIC: 5.5 LIVRAT (uniformizare/standardizare UI/UX: tabele la grila Trimiteri, meniu hamburger + tab-bar redus Acasa/Mapari, sterge Ajutor de pe Acasa, panou admin cu selectie+bulk pe model nou `accounts.status`. 9 stories in 3 valuri, UI pur cu o singura exceptie backend = stare cont; stergere soft cu purjare PII imediata GDPR. VERIFY 671 teste + E2E browser (2 bug-uri prinse) + `/code-review high` (2 bug-uri reale reparate). Commit `1fbd894`, vezi randul 5.5). | ISTORIC: 5.4 LIVRAT (Erori pe 3 niveluri problema+cauza+fix pe API si UI: catalog central pur `app/errors.py` ca SINGURA sursa de adevar cod→{problema,fix}, consumat de API+UI+worker — face imposibila divergenta intre canale, acelasi invariant ca 5.2. 8 stories in 5 valuri. Tot ADITIV: `field`/`message`/`error` pastrate la octet, adaugam `cod/problema/cauza/fix`; `rar_error` stocat = SUPERSET (chei vechi intacte → `labels.py` nu se rupe intre valuri, zero migrare). Scope = fluxul de declarare; login/signup/CSRF neatinse. UI progresiv: lista compacta, 3 niveluri complete in detaliu/preview, AA light+dark. VERIFY context curat PASS 628 teste (byte-compat+superset verificate direct, E2E API+web; live RAR neprobat — lipsa creds key). `/code-review high`: 2 bug-uri reale reparate in `labels.py` (`motiv_uman` fara ramura 3-niveluri → 401 creds garbled in coloana Motiv; `parse_erori` element gol pe `{}`). 631 teste. Backend trimitere + schema NEATINSE. PRD: [prd-5.4](prd/prd-5.4-erori-3-niveluri.md)). | ISTORIC: 5.3 LIVRAT (Light/Dark mode: tema light ca bloc `[data-theme="light"]` peste variabilele `:root` — dark NESCHIMBAT la octet; comutator soare/luna in header pe toate paginile, default OS-aware cu fallback dark, persistenta `localStorage` doar la comutare explicita, script anti-FOUC in `` pre-paint; suprafetele de stare hardcodate convertite la `color-mix` in `base.html` + 7 fragmente. Zero backend — pur frontend. VERIFY 2 runde: r1 FAIL a prins literalii dark ramasi in 7 fragmente HTMX (text invizibil in light, test vacuu pe doar base.html) → fix US-003 + test care scaneaza fragmentele; r2 PASS E2E browser (banner light ~13:1 contrast, toggle instant+persista+anti-FOUC, dark identic). `/code-review` high: 1 finding reparat (light `--ok` green sub AA ca text → green-700, ~5.0:1). 584 teste. PRD: [prd-5.3](prd/prd-5.3-light-dark-mode.md)). | ISTORIC: 5.2 LIVRAT (Endpoint dry-run `POST /v1/prezentari/valideaza`: valideaza payload + mapare si intoarce verdictul real — `status_estimat` queued/needs_data/needs_mapping + erori `[{field,message}]` + coduri nemapate + prestatii rezolvate — FARA enqueue, FARA creds, zero scriere DB). 1 story TDD. Cheia de design: helper pur partajat `classify_prezentare` folosit de AMBELE rute, ca dry-run-ul sa nu poata diverge de trimiterea reala (invariant de corectitudine); `create_prezentari` refactorizat pe el cu comportament identic. Scope minim per decizie user: doar validare+mapare (fara idempotency/duplicat, `idempotency.py` neatins), hub `/integrare` amanat ca follow-up (descoperibilitate). VERIFY context curat PASS (577 teste; E2E API cu cele 3 verdicte + COUNT(*)=0 dupa dry-run + fara leak creds in raspuns; regresia de aur verde; live RAR `FINALIZATA` neprobat — lipsa creds key, endpoint read-only nu atinge worker/coada/schema). `/code-review` high: 0 findings (refactor faithful, mutable-default Pydantic-safe, import local necesar anti-circular). PRD: [prd-5.2](prd/prd-5.2-dryrun-valideaza.md). | ISTORIC: 5.1 LIVRAT (Hub de integrare `/integrare`: exemple cod multi-limbaj + retetar VFP cu 2 dialecte + `GET /v1/ping` readiness + export Postman/OpenAPI + "Testeaza conexiunea"). 4 stories in 2 valuri (Val 1 = US-001/US-002/US-004 paralel pe fisiere disjuncte via Agent team; Val 2 = US-003 UI). Atentie operationala: US-003 a rulat intr-un worktree branched din ultimul commit (FARA modificarile necomise ale US-004 din working-tree) si la "copiere manuala" a SUPRASCRIS `routes.py`, stergand ruta `POST /integrare/test-cheie` (8 teste 404) — reparat prin re-aplicarea rutei de catre autorul US-004 pe `routes.py` curent. Lectie: stories care ating acelasi fisier in valuri diferite + worktree = clobber daca worktree-ul nu vede working-tree-ul; foloseste fisiere disjuncte SAU merge atent de catre lead. VERIFY context curat PASS (568 teste) + E2E browser Playwright (deep-link server-side, IA pe 2 niveluri, VFP cu 3 niveluri de tab comuta corect, copy, htmx test-cheie → fragment eroare, 0 erori consola) + enqueue live (`POST /v1/prezentari` → queued); live RAR `FINALIZATA` NEPROBAT in sesiune (lipsa `AUTOPASS_CREDS_KEY`/creds RAR test) — risc minim, backend trimitere NEATINS. `/code-review` high a prins 4 bug-uri reale (toate in suprafata noua, reparate + lock-uite cu teste): snippet C# JSON multi-linie nevalid (CS1010), snippet VFP `json.dumps(indent=0)` inca cu newline-uri → string literal rupt in ambele dialecte, snippet Node `node:buffer` nu exporta FormData → TypeError, script `_integrare.html` ne-scoped acumuland event-listeneri pe tab-bar-ul principal la fiecare swap htmx (scoped pe `#integrare-section`). Notat ca cleanup viitor (nereparat): `_render_integrare` dubleaza SQL `are_creds`/`are_cheie`, `ping` cu 2 conexiuni DB + `account_for_key` de 2 ori, `_campuri_obligatorii` necache-uit, panouri limbaj copy-paste (candidat macro Jinja2). Backend trimitere (worker/masina stari/idempotenta/mapping) si schema NEATINSE. PRD: [prd-5.1](prd/prd-5.1-hub-integrare.md). | ISTORIC: 3.6 INCHIS (editare celule in preview + Acasa unificata). CLOSE: `/code-review` high a prins 1 bug real (decriptare `override_json` neprotejata de try/except in ambele cai de preview — 500 pe tot batch-ul la rotatie cheie Fernet vs. `raw_json` care degrada gratios), reparat in `import_router.preview_import` + `routes._web_compute_preview`; duplicarea `_override_of`/canonicalize notata ca cleanup viitor. 523 teste pass. 7 stories in 3 valuri, executate de 2 echipe in paralel (TeamCreate) pe fisiere disjuncte (core: routes/import/templates; mapari: `_mapari.html`) + US-007 secvential. Livrate: tab "Trimiteri" eliminat→sectiune "Trimiterile tale" sub upload pe Acasa (US-003); upload bara slim accentuata cu hero la first-run (US-004); editare de celule in preview prin `import_rows.override_json` (Approach B, Fernet, patch canonic aplicat ULTIMUL in `_resolve_row_for_preview`+`commit_import` — completeaza inclusiv coloane ABSENTE din fisier), mutatie pura cu status rederivat (US-001); buton Editeaza pe rand cu swap pe ``+OOB contoare (nu pe sectiune), form propriu, mutual-exclusion, reuse grila `_trimitere_detaliu.html` (US-002); Mapari + formate de coloane ca tabele `.tablewrap`, H4 auto_send stocat (US-005/006); bifa "auto-send"→comutator etichetat pe COADA ("Pune automat in coada"/"Tine pentru verificare"), scoped pe operatie, `name=auto_send` pastrat (zero backend) (US-007). 523 teste pass. **VERIFY**: E2E browser pe `/` (Acasa unificata, upload slim, editare rand needs_data→ok cu swap pe rand + contoare OOB, Mapari tabelar + comutator) + LIVE pe RAR test — import fara coloana data → editarea completeaza data (override) → commit → worker login RAR test → `postPrezentare` → `sent` cu `idPrezentare=68696`, confirmat independent in lista finalizate RAR. 3 bug-uri JS (htmx 1.9.12) prinse DOAR la E2E in browser (invizibile la TestClient) si reparate: `useTemplateFragments=true` (raspunsul ``+OOB era parsat in context de tabel → `swapError` + contoare pierdute), re-activare `confirm-btn` deferita pe tick (race `editing=true` tranzitoriu), `n-hint` ok-count actualizat de `updateN`. Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare) NEATINS — singura atingere de schema: 1 coloana nullable `override_json` cu migrare defensiva. PRD: [prd-3.6](prd/prd-3.6-editare-preview-acasa-unificata.md). | ISTORIC: 3.5 LIVRAT (dashboard compact). 11 stories in 4 valuri, TDD. US-001 bara status compacta pe 2 randuri cu bife accesibile (glife ✓/✗ + text, nu doar culoare) + `format_data_rar` (dd.mm.yyyy hh24:mi:ss, helper pur). US-002 Acasa = ecranul de import (upload dominant inline, tab Import scos, `?tab=import`→Acasa fara 404). US-003 helper pur partajat `app/payload_view.py` (payload→campuri afisabile, defensiv, coercion Excel) refolosit si de `GET /v1/prezentari` (DRY). US-004 "Coada"→"Trimiteri": coloane RO + stare umana + detaliu complet la click in panou dedicat `#trimitere-detaliu` (nu inline — poll 10s), scoped 404 cross-account. US-005/006 CRUD mapari operatii + formate coloane salvate (scoped, re-rezolvare auto la edit cod). US-007 "Mapari" 3 sectiuni (de rezolvat / op salvate / formate coloane), "Cont" doar cheie+creds. US-008 motiv (mesaj validare) pe randuri needs_data in preview. US-009 filtre Trimiteri (stare SQL / vehicul+data Python) scoped + "sterge filtrele". US-010 corectie inline needs_data→queued cu payload+idempotency recalculate, sent read-only (403), coliziune idempotency prinsa pre-UPDATE. US-011 badge contoare pe tab-uri (Mapari/Trimiteri), scoped, aria-label. VERIFY context curat PASS (483 teste; E2E browser/RAR LIVE neprobat — recomandata probare manuala `--send`). `/code-review` high a prins 4 findings reale, toate reparate: corectie needs_mapping re-rezolva prestatiile (nu mai poate trimite cod nul la RAR), filtru fara LIMIT silentios, coliziune idempotency atomica (try/except IntegrityError), comparatie data doar ISO. Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare, schema) NEATINS. PRD: [prd-3.5](prd/prd-3.5-dashboard-compact-trimiteri-mapari.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP). +**Ultima actualizare**: 2026-06-23 — 5.7 LIVRAT (raspuns API onest la blocaje + mapare inline din detaliu). Raportat din client VFP: `POST /v1/prezentari` intorcea `submission_id`+`status` FARA motiv pe randuri blocate (`erori` se popula doar pe ramura `on_unmapped_error=True`) → un `needs_data`/`needs_mapping` parea succes ("raspuns fara erori"). Reparat ADITIV: `SubmissionResult` += `nemapate` + `motiv`; `create_prezentari` populeaza `erori` (validare continut, 3 niveluri) / `nemapate` (coduri fara mapare, COD_NEMAPAT) / `motiv` (rezumat uman) pe TOATE caile non-`queued` — enqueue, respins (`on_unmapped_error=True`) si reactivare dedup peste `error`. UI: mapare inline in panoul de detaliu trimitere (`POST /trimitere/{id}/mapeaza`, reuse EXACT `save_mapping`+`reresolve_account`, scoped sesiune + CSRF, re-rezolva pe `batch_id`-ul randului → deblocheaza si randurile-frate; apare doar pe operatii nemapate reale, nu pe auto_send=0) — fara drum prin tab-ul Mapari. `/code-review high`: 2 buguri reale reparate (reactivarea omitea `erori`/`nemapate`/`motiv`; dublu `load_nomenclator` in `_detaliu_ctx`), restul candidatilor infirmati la verify (parse `auto_send` corect via `or ""`; lipsa `conn/account_id` pe ramuri de corectie nereachable cu needs_mapping+unmapped). `pytest -q` **765 passed, 0 failed** (+1 skipped live). **PROBA LIVE `--send` (2026-06-23): mapare inline E2E pe RAR test** — `POST /v1/prezentari` cu operatie nemapata → `needs_mapping` (raspuns onest cu `nemapate`+`motiv`) → mapare inline din panoul de detaliu in browser (Playwright) → `queued` → worker login RAR + `postPrezentare` → `sent idPrezentare=68827`, confirmat independent in finalizate RAR + jurnal `app_events` (`rar_login ok` → `submission_sent`). Automatizat ca test live opt-in `tests/test_live_rar.py` (skip implicit; `AUTOPASS_LIVE_RAR=1` + creds test reproduc tot lantul → `idPrezentare=68828`). PRD: [prd-5.7](prd/prd-5.7-raspuns-onest-mapare-inline.md). | ISTORIC: FIX out-of-process (raportat din client VFP): `cod_prestatie` necunoscut in nomenclator era trimis raw la RAR → **HTTP 500** (`ORA-12899`, coloana `COD_PRESTATIE` max 5 car.) + record PARTIAL `FINALIZATA` (RAR ne-tranzactional) pe care reconcilierea il marca fals `sent`. Reparat: validare `cod_prestatie` fata de nomenclator la ingestie (cod necunoscut → tratat ca operatie de mapat, nu se mai trimite raw) + optiune boolean `on_unmapped_error` (`false` default → needs_mapping | `true` → respinge) per-cerere cu default per-cont `accounts.on_unmapped_error_default` (migrare aditiva). Confirmat live raspunsul RAR (500 pe cod intern vs 200 pe `OE-1`). Inclus si in `c842e33`: fix lease orfan worker (nepotrivire format data sending_since vs cutoff → orice rand `sending` parea expirat) + guard anti-dublu-POST + fix UI `hx-confirm` mostenit pe randuri (alerta de stergere la click pe rand). Teste: **748 passed** (cele 2 esecuri pre-existente fara legatura). Contract + CLAUDE.md actualizate. | 5.6 IMPLEMENTAT + VERIFY PASS (asteapta commit). Cele 14 stories din PRD 5.6 livrate TDD (RED->GREEN), `pytest -q` **741 passed, 0 failed**. Lifecycle trimiteri blocate (Val A primul, decizie #18): `app/submissions_admin.py` (sterge/repune scoped, 404-before-409); reactivare dedup peste `error` cu CAS + invalidare sesiune worker la creds noi (T1) + propagare `accounts.rar_creds_enc` (#17) + camp aditiv `reactivated:true` (#19); retentie randuri blocate 30z + `purge_after` curatat la reactivare/requeue (T2); API `DELETE`/`/repune` (200+JSON, #20); UI butoane + bulk + banner "Necesita atentia ta" actionabil cu deep-link. Observabilitate: `app/observ.py log_event` (dublu canal `app_events` DB + `RotatingFileHandler` per-proces, redactare creds/PII la scriere via `app/security.redact_pii`/`vin_partial`), `request_id` middleware + `X-Request-ID` pe toate raspunsurile (T8), handler global excepții -> 500 envelope 6-chei + request_id (T7), audit cerere API (`api_prezentari`/`api_auth_esuat`) + audit worker (`rar_login`/tranzitii), tab "Jurnal" filtrabil scoped (non-admin doar contul sau), retentie jurnal 90z. Live RAR `--send` NEPROBAT in sesiune (recomandat la deploy: confirma `rar_login` ok + `submission_sent` in jurnal). PRD actualizat cu raport VERIFY; contract actualizat cu endpointurile noi (T10). | ISTORIC: HOTFIX livrat + 5.6 APROBAT. Hotfix 500 pe `POST /v1/prezentari` (raportat din client Visual FoxPro): `AUTOPASS_CREDS_KEY` din `.env` nu respecta formatul Fernet (32 bytes url-safe base64) → `ValueError` la primul `encrypt_creds` → 500 brut. Reparat: cheie Fernet valida in `.env` + `crypto.validate_creds_key()` apelata in `main.lifespan` (fail-fast la startup, mesaj clar in loc de 500 la primul POST). Confirmat live: POST VFP → 200 `queued`; trimitere reala pe RAR test → `sent idPrezentare=68818` (verificat independent in finalizate). Corectat si mesajul fals din dashboard pentru starea `error` in `labels.py` ("se reincearca automat" → starea e terminala, NU se reincearca). Investigatia a expus 3 goluri structurale (500 brut fara traducere 3 niveluri; lipsa jurnal de aplicatie la nivel de eveniment; lacune de lifecycle — randuri blocate permanente, dedup blocat de un rand `error`, banner "Necesita atentia ta" neactionabil) → **PRD 5.6 APROBAT** (14 stories; decizii §5 rezolvate cu user). PRD: [prd-5.6](prd/prd-5.6-observabilitate-jurnal.md). | ISTORIC: 5.5 LIVRAT (uniformizare/standardizare UI/UX: tabele la grila Trimiteri, meniu hamburger + tab-bar redus Acasa/Mapari, sterge Ajutor de pe Acasa, panou admin cu selectie+bulk pe model nou `accounts.status`. 9 stories in 3 valuri, UI pur cu o singura exceptie backend = stare cont; stergere soft cu purjare PII imediata GDPR. VERIFY 671 teste + E2E browser (2 bug-uri prinse) + `/code-review high` (2 bug-uri reale reparate). Commit `1fbd894`, vezi randul 5.5). | ISTORIC: 5.4 LIVRAT (Erori pe 3 niveluri problema+cauza+fix pe API si UI: catalog central pur `app/errors.py` ca SINGURA sursa de adevar cod→{problema,fix}, consumat de API+UI+worker — face imposibila divergenta intre canale, acelasi invariant ca 5.2. 8 stories in 5 valuri. Tot ADITIV: `field`/`message`/`error` pastrate la octet, adaugam `cod/problema/cauza/fix`; `rar_error` stocat = SUPERSET (chei vechi intacte → `labels.py` nu se rupe intre valuri, zero migrare). Scope = fluxul de declarare; login/signup/CSRF neatinse. UI progresiv: lista compacta, 3 niveluri complete in detaliu/preview, AA light+dark. VERIFY context curat PASS 628 teste (byte-compat+superset verificate direct, E2E API+web; live RAR neprobat — lipsa creds key). `/code-review high`: 2 bug-uri reale reparate in `labels.py` (`motiv_uman` fara ramura 3-niveluri → 401 creds garbled in coloana Motiv; `parse_erori` element gol pe `{}`). 631 teste. Backend trimitere + schema NEATINSE. PRD: [prd-5.4](prd/prd-5.4-erori-3-niveluri.md)). | ISTORIC: 5.3 LIVRAT (Light/Dark mode: tema light ca bloc `[data-theme="light"]` peste variabilele `:root` — dark NESCHIMBAT la octet; comutator soare/luna in header pe toate paginile, default OS-aware cu fallback dark, persistenta `localStorage` doar la comutare explicita, script anti-FOUC in `` pre-paint; suprafetele de stare hardcodate convertite la `color-mix` in `base.html` + 7 fragmente. Zero backend — pur frontend. VERIFY 2 runde: r1 FAIL a prins literalii dark ramasi in 7 fragmente HTMX (text invizibil in light, test vacuu pe doar base.html) → fix US-003 + test care scaneaza fragmentele; r2 PASS E2E browser (banner light ~13:1 contrast, toggle instant+persista+anti-FOUC, dark identic). `/code-review` high: 1 finding reparat (light `--ok` green sub AA ca text → green-700, ~5.0:1). 584 teste. PRD: [prd-5.3](prd/prd-5.3-light-dark-mode.md)). | ISTORIC: 5.2 LIVRAT (Endpoint dry-run `POST /v1/prezentari/valideaza`: valideaza payload + mapare si intoarce verdictul real — `status_estimat` queued/needs_data/needs_mapping + erori `[{field,message}]` + coduri nemapate + prestatii rezolvate — FARA enqueue, FARA creds, zero scriere DB). 1 story TDD. Cheia de design: helper pur partajat `classify_prezentare` folosit de AMBELE rute, ca dry-run-ul sa nu poata diverge de trimiterea reala (invariant de corectitudine); `create_prezentari` refactorizat pe el cu comportament identic. Scope minim per decizie user: doar validare+mapare (fara idempotency/duplicat, `idempotency.py` neatins), hub `/integrare` amanat ca follow-up (descoperibilitate). VERIFY context curat PASS (577 teste; E2E API cu cele 3 verdicte + COUNT(*)=0 dupa dry-run + fara leak creds in raspuns; regresia de aur verde; live RAR `FINALIZATA` neprobat — lipsa creds key, endpoint read-only nu atinge worker/coada/schema). `/code-review` high: 0 findings (refactor faithful, mutable-default Pydantic-safe, import local necesar anti-circular). PRD: [prd-5.2](prd/prd-5.2-dryrun-valideaza.md). | ISTORIC: 5.1 LIVRAT (Hub de integrare `/integrare`: exemple cod multi-limbaj + retetar VFP cu 2 dialecte + `GET /v1/ping` readiness + export Postman/OpenAPI + "Testeaza conexiunea"). 4 stories in 2 valuri (Val 1 = US-001/US-002/US-004 paralel pe fisiere disjuncte via Agent team; Val 2 = US-003 UI). Atentie operationala: US-003 a rulat intr-un worktree branched din ultimul commit (FARA modificarile necomise ale US-004 din working-tree) si la "copiere manuala" a SUPRASCRIS `routes.py`, stergand ruta `POST /integrare/test-cheie` (8 teste 404) — reparat prin re-aplicarea rutei de catre autorul US-004 pe `routes.py` curent. Lectie: stories care ating acelasi fisier in valuri diferite + worktree = clobber daca worktree-ul nu vede working-tree-ul; foloseste fisiere disjuncte SAU merge atent de catre lead. VERIFY context curat PASS (568 teste) + E2E browser Playwright (deep-link server-side, IA pe 2 niveluri, VFP cu 3 niveluri de tab comuta corect, copy, htmx test-cheie → fragment eroare, 0 erori consola) + enqueue live (`POST /v1/prezentari` → queued); live RAR `FINALIZATA` NEPROBAT in sesiune (lipsa `AUTOPASS_CREDS_KEY`/creds RAR test) — risc minim, backend trimitere NEATINS. `/code-review` high a prins 4 bug-uri reale (toate in suprafata noua, reparate + lock-uite cu teste): snippet C# JSON multi-linie nevalid (CS1010), snippet VFP `json.dumps(indent=0)` inca cu newline-uri → string literal rupt in ambele dialecte, snippet Node `node:buffer` nu exporta FormData → TypeError, script `_integrare.html` ne-scoped acumuland event-listeneri pe tab-bar-ul principal la fiecare swap htmx (scoped pe `#integrare-section`). Notat ca cleanup viitor (nereparat): `_render_integrare` dubleaza SQL `are_creds`/`are_cheie`, `ping` cu 2 conexiuni DB + `account_for_key` de 2 ori, `_campuri_obligatorii` necache-uit, panouri limbaj copy-paste (candidat macro Jinja2). Backend trimitere (worker/masina stari/idempotenta/mapping) si schema NEATINSE. PRD: [prd-5.1](prd/prd-5.1-hub-integrare.md). | ISTORIC: 3.6 INCHIS (editare celule in preview + Acasa unificata). CLOSE: `/code-review` high a prins 1 bug real (decriptare `override_json` neprotejata de try/except in ambele cai de preview — 500 pe tot batch-ul la rotatie cheie Fernet vs. `raw_json` care degrada gratios), reparat in `import_router.preview_import` + `routes._web_compute_preview`; duplicarea `_override_of`/canonicalize notata ca cleanup viitor. 523 teste pass. 7 stories in 3 valuri, executate de 2 echipe in paralel (TeamCreate) pe fisiere disjuncte (core: routes/import/templates; mapari: `_mapari.html`) + US-007 secvential. Livrate: tab "Trimiteri" eliminat→sectiune "Trimiterile tale" sub upload pe Acasa (US-003); upload bara slim accentuata cu hero la first-run (US-004); editare de celule in preview prin `import_rows.override_json` (Approach B, Fernet, patch canonic aplicat ULTIMUL in `_resolve_row_for_preview`+`commit_import` — completeaza inclusiv coloane ABSENTE din fisier), mutatie pura cu status rederivat (US-001); buton Editeaza pe rand cu swap pe ``+OOB contoare (nu pe sectiune), form propriu, mutual-exclusion, reuse grila `_trimitere_detaliu.html` (US-002); Mapari + formate de coloane ca tabele `.tablewrap`, H4 auto_send stocat (US-005/006); bifa "auto-send"→comutator etichetat pe COADA ("Pune automat in coada"/"Tine pentru verificare"), scoped pe operatie, `name=auto_send` pastrat (zero backend) (US-007). 523 teste pass. **VERIFY**: E2E browser pe `/` (Acasa unificata, upload slim, editare rand needs_data→ok cu swap pe rand + contoare OOB, Mapari tabelar + comutator) + LIVE pe RAR test — import fara coloana data → editarea completeaza data (override) → commit → worker login RAR test → `postPrezentare` → `sent` cu `idPrezentare=68696`, confirmat independent in lista finalizate RAR. 3 bug-uri JS (htmx 1.9.12) prinse DOAR la E2E in browser (invizibile la TestClient) si reparate: `useTemplateFragments=true` (raspunsul ``+OOB era parsat in context de tabel → `swapError` + contoare pierdute), re-activare `confirm-btn` deferita pe tick (race `editing=true` tranzitoriu), `n-hint` ok-count actualizat de `updateN`. Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare) NEATINS — singura atingere de schema: 1 coloana nullable `override_json` cu migrare defensiva. PRD: [prd-3.6](prd/prd-3.6-editare-preview-acasa-unificata.md). | ISTORIC: 3.5 LIVRAT (dashboard compact). 11 stories in 4 valuri, TDD. US-001 bara status compacta pe 2 randuri cu bife accesibile (glife ✓/✗ + text, nu doar culoare) + `format_data_rar` (dd.mm.yyyy hh24:mi:ss, helper pur). US-002 Acasa = ecranul de import (upload dominant inline, tab Import scos, `?tab=import`→Acasa fara 404). US-003 helper pur partajat `app/payload_view.py` (payload→campuri afisabile, defensiv, coercion Excel) refolosit si de `GET /v1/prezentari` (DRY). US-004 "Coada"→"Trimiteri": coloane RO + stare umana + detaliu complet la click in panou dedicat `#trimitere-detaliu` (nu inline — poll 10s), scoped 404 cross-account. US-005/006 CRUD mapari operatii + formate coloane salvate (scoped, re-rezolvare auto la edit cod). US-007 "Mapari" 3 sectiuni (de rezolvat / op salvate / formate coloane), "Cont" doar cheie+creds. US-008 motiv (mesaj validare) pe randuri needs_data in preview. US-009 filtre Trimiteri (stare SQL / vehicul+data Python) scoped + "sterge filtrele". US-010 corectie inline needs_data→queued cu payload+idempotency recalculate, sent read-only (403), coliziune idempotency prinsa pre-UPDATE. US-011 badge contoare pe tab-uri (Mapari/Trimiteri), scoped, aria-label. VERIFY context curat PASS (483 teste; E2E browser/RAR LIVE neprobat — recomandata probare manuala `--send`). `/code-review` high a prins 4 findings reale, toate reparate: corectie needs_mapping re-rezolva prestatiile (nu mai poate trimite cod nul la RAR), filtru fara LIMIT silentios, coliziune idempotency atomica (try/except IntegrityError), comparatie data doar ISO. Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare, schema) NEATINS. PRD: [prd-3.5](prd/prd-3.5-dashboard-compact-trimiteri-mapari.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP). > 2026-06-18 — 3.4 LIVRAT (interfata web ergonomica: tab-uri + wizard + microcopy). US-001 modul pur `app/web/labels.py` (stari tehnice→text uman + clasa CSS; test parametrizat din CHECK-ul `schema.sql` iese rosu la stare nemapata). US-002 bara status `/_fragments/status` + `_status.html` (etichete umane, defalcare blocate pe motiv, poll 15s, scoped pe cont). US-003 shell 6 tab-uri (Acasa·Import·Coada·Mapari·Cont·Nomenclator) cu deep-link `?tab=`, panou activ randat server-side, fragmente inactive lazy pe click, ARIA real (tablist/tab/tabpanel + aria-selected + navigare cu sageti). US-004 stepper import 4 pasi (PUR vizual, `hx-target="#import-section"` + csrf pastrate). US-005 Acasa onboarding checklist auto-bifat (are_creds/are_trimiteri) + colaps cand totul gata + empty states prietenoase Coada/Mapari. VERIFY lead-driven (TestClient ACs + 434 pytest pass; E2E browser/RAR LIVE neprobat in sesiune — recomandata probare manuala `--send`). Fix izolare teste (reset `ratelimit._hits` in fixturi, 429 la rulare subset). `/code-review` high: regasit avertisment "cont in asteptare de activare" (regresie din scoaterea `/_fragments/banner`) re-introdus in bara status + culori hardcodate→variabile paleta. 434 teste pass. Backend trimitere neatins. PRD: [prd-3.4](prd/prd-3.4-ux-dashboard-web.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP). @@ -102,7 +102,7 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi | 5.5 | Uniformizare/standardizare UI/UX: tabele la grila Trimiteri (Mapari compact + toggle Auto/Manual + Ajutor; Nomenclator), meniu hamburger (Cont/Integrare/Nomenclator/Admin/logout) cu tab-bar redus la Acasa+Mapari, sterge Ajutor de pe Acasa, panou admin cu selectie+bulk (Activeaza/Blocheaza/Arhiveaza/Sterge) pe model nou de stare cont | DONE | 2026-06-23 | 9 stories, 3 valuri. UI pur (reskin+reasezare) cu O SINGURA exceptie backend: `accounts.status` (pending/active/blocked/archived/deleted, migrare defensiva, gate worker `claim_one` pastrand `active=1 ⇔ status='active'`). Macro autosend rescris compact pastrand semantica de prezenta `auto_send` (zero backend). Stergere = SOFT: tombstone, dar PII (creds RAR + chei API + CUI) purjate IMEDIAT la stergere (GDPR/L.142), nu prin retentie. VERIFY 671 teste pass (+40); E2E browser Playwright a prins 2 bug-uri invizibile la TestClient (bara bulk `display:flex` inline invingea `[hidden]`; arhivate cadeau sub "in asteptare" → grupare pe status). `/code-review high`: 2 bug-uri reale reparate (soft delete pastra creds+CUI fara purjare → purjare PII la stergere; apostrof in nume rupea `confirm()` inline kebab) + cleanup `_lifecycle_route`. Debt acceptat: `/admin/deactivate`+`set_active` pastrate legacy (test CLI). Backend trimitere neatins (exceptie: gate cont). Commit `1fbd894`. Design: [5.5-uniformizare-ui](design/5.5-uniformizare-ui.md). PRD: [prd-5.5](prd/prd-5.5-uniformizare-ui.md) | | 5.4 | Erori pe 3 niveluri (problema + cauza + fix) pe API si UI | DONE | 2026-06-22 | Catalog central pur `app/errors.py` (24 coduri, `eroare()` → `{field,cod,problema,cauza,fix,message}`, ridica pe cod necunoscut) ca SINGURA sursa de adevar, consumat de API+UI+worker (acelasi invariant anti-divergenta ca 5.2). 8 stories, 5 valuri. Tot ADITIV (decizie user): `field`/`message`/`error` pastrate la octet, adaugam 3 niveluri + cod stabil; `rar_error` stocat = SUPERSET (chei vechi intacte → `labels.py` nu se rupe intre valuri, zero migrare). Scope = fluxul de declarare (validare continut, RAR 400/401, import, mapare op→cod); login/signup/CSRF NEATINSE. US-001 catalog; US-002 `validation.py` (cod+3n, byte-compat); US-003 `mapping.py`+`/valideaza` (needs_mapping/auto_send superset, `nemapate` 3n); US-004 worker RAR 400→`RAR_VALIDARE` (field_errors passthrough) + 401→`RAR_CREDS_INVALIDE` (clasificare transient neschimbata, fara echo creds); US-005 `import_router` detalii superset; US-006 `labels.parse_erori` (degradeaza gratios, lectia 3.6) + macro `_eroare.html` (progresiv: lista compacta, 3n complete in detaliu/preview; AA light+dark, accent≠rosu); US-007 upload/mapcoloane web prin macro; US-008 contract documentat. VERIFY context curat PASS (628 teste; byte-compat+superset verificate direct; E2E API `/valideaza` 3 niveluri + regresia de aur queued; E2E web fragmente upload+detaliu; live RAR `FINALIZATA` neprobat — lipsa creds key, endpoint-urile/UI noi nu ating trimiterea). `/code-review high`: 2 bug-uri reale reparate in `labels.py` — `motiv_uman` nu avea ramura pentru dict-ul 3-niveluri (401 creds → text garbled "field: None; cod:..." in coloana Motiv) + `parse_erori` intorcea element gol pe `{}`/`[{}]` (cutie de eroare goala); cleanup notat ca viitor (dublare `parse_erori`/`motiv_uman`, enrichment COD_NEMAPAT pe 2 straturi, upload handlers copy-paste cross-channel `routes.py`/`import_router`, `json.loads` mort in ramura COLOANE_FORMAT_JSON). 631 teste. Backend trimitere (worker masina stari/idempotenta/mapping-rezolvare) si schema NEATINSE. PRD: [prd-5.4](prd/prd-5.4-erori-3-niveluri.md) | | 5.6 | Observabilitate & jurnal aplicatie + lifecycle trimiteri blocate: jurnal structurat de evenimente (tabela `app_events` + log text) cu vizualizator in dashboard, handler global 500→eroare 3 niveluri + `request_id`, audit cereri API + login RAR + ciclu trimiteri (worker), redactare PII/creds, retentie/purjare; plus stergere/re-pune in coada randuri blocate (UI+API), dedup care nu mai e blocat de un rand `error`, purjare randuri blocate, banner "Necesita atentia ta" actionabil (link + identificare rand) | APROBAT | 2026-06-23 | Nascut din incidentul 500 (client VFP). HOTFIX deja livrat in afara procesului (US-000): cheie Fernet valida in `.env` + `crypto.validate_creds_key()` fail-fast in `main.lifespan` + mesaj `error` corectat in `labels.py`; confirmat live POST→200, send RAR test→`idPrezentare=68818`. 14 stories ramase: observabilitate US-001..008, lifecycle US-009..013, banner US-014. Decizii §5 rezolvate cu user: retentie jurnal 90z (`AUTOPASS_LOG_RETENTION_DAYS`) + `RotatingFileHandler`; tipuri evenimente extensibile; jurnal non-admin scoped pe cont (admin vede tot); la resubmit doar `error` se re-activeaza (needs_* raman deduped); retentie randuri blocate 30z (`AUTOPASS_BLOCKED_RETENTION_DAYS`); stergere UI cu confirmare simpla + bulk pe lista. NEIMPLEMENTAT inca (doar hotfix-ul e in cod). PRD: [prd-5.6](prd/prd-5.6-observabilitate-jurnal.md) | -| 5.7 | Raspuns API onest la blocaje (`erori`/`nemapate`/`motiv` pe orice status != `queued`) + mapare inline din panoul de detaliu trimitere | DONE | 2026-06-23 | Raportat din client VFP: `POST /v1/prezentari` raspundea `submission_id`+`status` fara motiv pe randuri blocate (`erori` doar pe `on_unmapped_error=True`) → `needs_data`/`needs_mapping` parea succes. 3 stories TDD. US-001 (backend API): `SubmissionResult` += `nemapate`+`motiv` (ADITIV), `create_prezentari` populeaza `erori`/`nemapate`/`motiv` pe enqueue + respins + reactivare via helperele `_rezultat_enqueue`/`_rezultat_respins`/`_motiv_clasificare`; `on_unmapped_error=True` pastreaza `erori`=COD_NEMAPAT (compat). US-002 (web): ruta `POST /trimitere/{id}/mapeaza` (reuse EXACT `save_mapping`+`reresolve_account`, scoped sesiune 404 + CSRF, re-rezolva pe `batch_id`-ul randului) + `_nemapate_pentru_submission` + context in `_detaliu_ctx`. US-003 (UI): sectiune "Mapeaza codul operatiei" in `_trimitere_detaliu.html` (selector cod RAR cu sugestie fuzzy preselectata >=60, `ui.autosend_toggle`), doar pe operatii nemapate reale. `/code-review high`: 2 buguri reale reparate (reactivarea omitea `erori`/`nemapate`/`motiv`; dublu `load_nomenclator`), restul infirmate. `pytest -q` **765 passed, 0 failed**. Live RAR `--send` NEPROBAT (recomandat la deploy: mapare inline → `queued` → worker trimite). Backend trimitere (worker/masina stari/idempotenta) si schema NEATINSE. PRD: [prd-5.7](prd/prd-5.7-raspuns-onest-mapare-inline.md) | +| 5.7 | Raspuns API onest la blocaje (`erori`/`nemapate`/`motiv` pe orice status != `queued`) + mapare inline din panoul de detaliu trimitere | DONE | 2026-06-23 | Raportat din client VFP: `POST /v1/prezentari` raspundea `submission_id`+`status` fara motiv pe randuri blocate (`erori` doar pe `on_unmapped_error=True`) → `needs_data`/`needs_mapping` parea succes. 3 stories TDD. US-001 (backend API): `SubmissionResult` += `nemapate`+`motiv` (ADITIV), `create_prezentari` populeaza `erori`/`nemapate`/`motiv` pe enqueue + respins + reactivare via helperele `_rezultat_enqueue`/`_rezultat_respins`/`_motiv_clasificare`; `on_unmapped_error=True` pastreaza `erori`=COD_NEMAPAT (compat). US-002 (web): ruta `POST /trimitere/{id}/mapeaza` (reuse EXACT `save_mapping`+`reresolve_account`, scoped sesiune 404 + CSRF, re-rezolva pe `batch_id`-ul randului) + `_nemapate_pentru_submission` + context in `_detaliu_ctx`. US-003 (UI): sectiune "Mapeaza codul operatiei" in `_trimitere_detaliu.html` (selector cod RAR cu sugestie fuzzy preselectata >=60, `ui.autosend_toggle`), doar pe operatii nemapate reale. `/code-review high`: 2 buguri reale reparate (reactivarea omitea `erori`/`nemapate`/`motiv`; dublu `load_nomenclator`), restul infirmate. `pytest -q` **765 passed, 0 failed** (+1 skipped live). **Live RAR `--send` PROBAT (2026-06-23)**: mapare inline in browser → `queued` → worker → `sent idPrezentare=68827` (confirmat independent in finalizate RAR + jurnal `app_events`); automatizat ca test live opt-in `tests/test_live_rar.py` (skip implicit; `AUTOPASS_LIVE_RAR=1` + creds test → reproduce tot lantul, `idPrezentare=68828`). Backend trimitere (worker/masina stari/idempotenta) si schema NEATINSE. PRD: [prd-5.7](prd/prd-5.7-raspuns-onest-mapare-inline.md) | ### Etapa 4 — Deprioritizat (post Etapa 5, daca apare nevoia din uz real) diff --git a/tests/conftest.py b/tests/conftest.py index b148d82..7f4812a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,3 +15,11 @@ import os os.environ.setdefault("AUTOPASS_REQUIRE_API_KEY", "false") os.environ.setdefault("AUTOPASS_WORKER_USE_TEST_CREDS", "false") + + +def pytest_configure(config): + """Markeri custom. `live` = teste care ating endpoint-ul real RAR (opt-in, + skip implicit; vezi tests/test_live_rar.py). Excludere: `-m 'not live'`.""" + config.addinivalue_line( + "markers", "live: test live pe RAR test (necesita AUTOPASS_LIVE_RAR=1 + creds reale)" + ) diff --git a/tests/test_live_rar.py b/tests/test_live_rar.py new file mode 100644 index 0000000..1a3bede --- /dev/null +++ b/tests/test_live_rar.py @@ -0,0 +1,187 @@ +"""Test LIVE end-to-end pe RAR test (opt-in) — mapare inline -> worker -> FINALIZATA. + +Reproduce automat proba live manuala din 5.7: un submission `needs_mapping` (cod +operatie nemapat) -> mapare inline din panoul de detaliu (ruta web reala, sesiune + +CSRF) -> `queued` -> worker-ul REAL (login RAR test, `postPrezentare`) -> `sent` cu +`idPrezentare` -> verificare INDEPENDENTA in lista de finalizate RAR (regresia de aur). + +SKIP implicit (nu ruleaza in suita normala / CI). Se activeaza doar cu: + + AUTOPASS_LIVE_RAR=1 + settings.xml cu creds reale + python3 -m pytest tests/test_live_rar.py -q + +ATENTIE: atinge endpoint-ul real RAR de test -> creeaza o prezentare `FINALIZATA` +(terminala la RAR, fara anulare prin API). Folosim VIN unic per rulare ca verificarea +in finalizate sa fie fara ambiguitate (nu se ciocneste de rulari anterioare). +""" + +from __future__ import annotations + +import json +import os +import re +import tempfile + +import pytest +from starlette.testclient import TestClient + +LIVE = os.environ.get("AUTOPASS_LIVE_RAR") == "1" + +pytestmark = [ + pytest.mark.live, + pytest.mark.skipif(not LIVE, reason="test live RAR dezactivat (seteaza AUTOPASS_LIVE_RAR=1)"), +] + +DATA_PRESTATIE = "2025-01-15" # in [2024-12-01, azi]; fix, ca sa nu depinda de ceas +ODOMETRU = "154000" + + +def _vin_unic() -> str: + """VIN valid (17 car., A-HJ-NPR-Z0-9), unic per rulare. hex -> 0-9A-F (fara I/O/Q).""" + return ("WAUZ" + os.urandom(7).hex().upper())[:17] + + +def _create_account_user(email: str, name: str = "Live Probe", password: str = "parolasecreta10") -> int: + 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 _insert_needs_mapping(acct: int, *, op: str, vin: str) -> int: + """Submission `needs_mapping` cu o prestatie nemapata (cod_prestatie null) + continut valid.""" + from app.db import get_connection + + payload = { + "vin": vin, + "nr_inmatriculare": "B123XYZ", + "data_prestatie": DATA_PRESTATIE, + "odometru_final": ODOMETRU, + "prestatii": [{"cod_prestatie": None, "cod_op_service": op, "denumire": op}], + } + conn = get_connection() + try: + cur = conn.execute( + "INSERT INTO submissions (idempotency_key, account_id, status, payload_json, rar_error) " + "VALUES (?, ?, 'needs_mapping', ?, ?)", + (f"live-{os.urandom(6).hex()}", acct, json.dumps(payload), + json.dumps({"unmapped": [{"cod_op_service": op}]})), + ) + conn.commit() + return int(cur.lastrowid or 0) + finally: + conn.close() + + +def _login(client: TestClient, email: str, password: str = "parolasecreta10") -> None: + resp = client.get("/login") + m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) + assert m, "token CSRF negasit pe /login" + resp = client.post("/login", data={"email": email, "parola": password, "csrf_token": m.group(1)}) + assert resp.status_code == 303 + + +def _csrf(client: TestClient) -> str: + resp = client.get("/?tab=acasa") + m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) + assert m, "token CSRF negasit pe dashboard" + return m.group(1) + + +@pytest.fixture() +def env(monkeypatch): + """Mediu test: DB temporar, RAR test, worker cu creds si send activat.""" + tmp = tempfile.mkdtemp() + monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "live.db")) + monkeypatch.setenv("AUTOPASS_RAR_ENV", "test") + monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "true") + monkeypatch.setenv("AUTOPASS_WORKER_USE_TEST_CREDS", "true") + monkeypatch.setenv("AUTOPASS_WORKER_SEND_ENABLED", "true") + from app.config import get_settings + get_settings.cache_clear() + from app.db import init_db + init_db() # schema + seed nomenclator (inainte de orice insert; lifespan ar veni prea tarziu) + from app.web import ratelimit + ratelimit._hits.clear() + yield + ratelimit._hits.clear() + get_settings.cache_clear() + + +def test_live_mapare_inline_pana_la_finalizata(env): + """Lant complet pe RAR test: needs_mapping -> mapare inline web -> queued -> worker -> sent -> finalizate.""" + from app.config import get_settings, load_test_credentials + + creds = load_test_credentials() + if not creds: + pytest.skip("settings.xml fara creds reale") + + op = "OP-LIVE-TEST" + vin = _vin_unic() + + # --- 1. ingestie: un rand blocat needs_mapping (cod operatie nemapat) --- + acct = _create_account_user("live@test.local") + sid = _insert_needs_mapping(acct, op=op, vin=vin) + + # --- 2. mapare inline din panoul de detaliu (ruta web reala, sesiune + CSRF) -> queued --- + from app.main import app + with TestClient(app, follow_redirects=False) as client: + _login(client, "live@test.local") + # panoul de detaliu expune selectorul de cod RAR pe operatia nemapata + det = client.get(f"/_fragments/trimitere/{sid}") + assert det.status_code == 200 + assert "Mapeaza codul operatiei" in det.text + csrf = _csrf(client) + resp = client.post(f"/trimitere/{sid}/mapeaza", data={ + "cod_op_service": op, "cod_prestatie": "OE-1", + "auto_send": "true", "csrf_token": csrf, + }) + assert resp.status_code == 200, resp.text + assert resp.headers.get("HX-Trigger") == "trimiteriChanged" + + from app.db import get_connection + conn = get_connection() + try: + row = conn.execute("SELECT status, payload_json FROM submissions WHERE id=?", (sid,)).fetchone() + assert row["status"] == "queued", f"asteptat queued dupa mapare, e {row['status']}" + assert json.loads(row["payload_json"])["prestatii"][0]["cod_prestatie"] == "OE-1" + + # --- 3. worker REAL: login RAR test + claim + postPrezentare -> sent --- + from app.rar_client import RarClient + from app.worker.__main__ import claim_one, process_one + + settings = get_settings() + rar = RarClient(settings) + token = rar.login(creds["email"], creds["password"]) + assert token, "login RAR test esuat" + + claimed = claim_one(conn) + assert claimed is not None and claimed["id"] == sid, "claim_one nu a preluat randul queued" + stare = process_one(conn, settings, rar, token, claimed) + assert stare == "sent", f"asteptat sent, worker a intors {stare}" + + sent = conn.execute( + "SELECT status, id_prezentare FROM submissions WHERE id=?", (sid,) + ).fetchone() + assert sent["status"] == "sent" + id_prezentare = sent["id_prezentare"] + assert id_prezentare, "RAR nu a intors idPrezentare" + + # --- 4. verificare INDEPENDENTA: prezentarea apare in finalizate RAR (match pe VIN) --- + from app.reconcile import match_finalizata + finalizate = rar.get_finalizate(token) + gasit = match_finalizata( + finalizate, vin=vin, data_prestatie=DATA_PRESTATIE, odometru_final=ODOMETRU, + ) + assert gasit == id_prezentare, ( + f"prezentarea {id_prezentare} (VIN {vin}) nu se regaseste in finalizate RAR" + ) + finally: + conn.close()