feat(web): hub integrare /integrare — exemple cod + retetar VFP + ping + export (PRD 5.1)

Pagina /integrare (tab autentificat, scoped pe cont): exemple cod multi-limbaj
(curl/Python/PHP/C#/Node) + retetar Visual FoxPro (MSXML2 + WinHttp) pe ambele
canale (prezentari JSON + import fisier), export Postman/OpenAPI/Swagger si buton
"Testeaza conexiunea".

- US-001: GET /v1/ping (readiness: account_id/mediu/autentificat_cu_cheie/
  are_creds_rar/ts) + GET /v1/integrare/postman.json (v2.1.0, allowlist 3 rute)
- US-002: app/web/integrare_examples.py pur (7 limbaje x 2 canale, drift-test
  is_required(), JSON compact pentru C#/VFP)
- US-003: tab "Integrare" IA pe 2 niveluri (limbaj->canal, VFP cu dialecte),
  copy din <pre><code>, empty-state CTA, export .cardlink, script scoped
- US-004: POST /integrare/test-cheie (account_for_key direct, scoped sesiune,
  no-echo cheie)

Backend trimitere (worker/masina stari/idempotenta/mapping) si schema neatinse.
568 teste pass. VERIFY context curat + E2E browser (Playwright) + code-review high.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-22 12:16:41 +00:00
parent be36c2c53b
commit f0786051f5
13 changed files with 2263 additions and 62 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
# PRD 5.1 — Hub de integrare (pagina /integrare cu exemple de cod + retetar VFP + ping + export)
**Stare**: aprobat (2026-06-22 — intrebari deschise rezolvate; 3 review-uri de plan rulate si incorporate (CEO/eng/design, toate APROB CU MODIFICARI); aprobat la poarta umana. Urmeaza EXECUTE)
**Stare**: inchis (2026-06-22 — VERIFY PASS + E2E browser PASS + `/code-review` high incorporat; 568 teste pass; writeback ROADMAP facut; asteapta poarta umana de commit)
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
@@ -66,16 +66,16 @@ de importat **pentru ca** sa-mi validez conexiunea inainte sa trimit prezentari
`test_postman_contine_exact_trei_requesturi` (prezentari, import, ping; header `X-API-Key: {{api_key}}`; `{{base_url}}`),
`test_postman_nu_deriva_din_app_routes` (allowlist hardcodat — NU expune `/v1/conturi/rar-creds` etc.).
- **Acceptance criteria**:
- [ ] `GET /v1/ping` (dependinta `resolve_account_id`) = **readiness check**, nu doar auth-echo: cheie valida → 200
- [x] `GET /v1/ping` (dependinta `resolve_account_id`) = **readiness check**, nu doar auth-echo: cheie valida → 200
`{account_id, mediu, autentificat_cu_cheie, are_creds_rar, ts}`; cheie invalida → 401; fara cheie cu
`require_api_key=true` → 401; fara cheie in dev → cont 1 cu `autentificat_cu_cheie=false`. (`are_creds_rar` = contul are
`accounts.rar_creds_enc` setate; citire-only, raspunde la "esti gata sa trimiti?", nu doar "cheia merge". CEO-review P1/P2.)
- [ ] Suporta si `Authorization: Bearer rfak_...` (al doilea header documentat in pagina).
- [ ] `GET /v1/integrare/postman.json`: colectie Postman v2.1.0 cu variabile `base_url` + `api_key`, **exact 3 requesturi**
- [x] Suporta si `Authorization: Bearer rfak_...` (al doilea header documentat in pagina).
- [x] `GET /v1/integrare/postman.json`: colectie Postman v2.1.0 cu variabile `base_url` + `api_key`, **exact 3 requesturi**
(allowlist hardcodat, NU derivat din `app.routes`): `POST /v1/prezentari`, `POST /v1/import`, `GET /v1/ping`, fiecare cu
header `X-API-Key: {{api_key}}` si body exemplu (pt prezentari: payload din schema reala `PrezentareRequest`, inclusiv `prestatii`).
- [ ] `/openapi.json` ramane accesibil (FastAPI implicit) — niciun regres.
- [ ] NU atinge `submissions`, worker, schema. `are_creds_rar` = SELECT read-only pe `accounts`.
- [x] `/openapi.json` ramane accesibil (FastAPI implicit) — niciun regres.
- [x] NU atinge `submissions`, worker, schema. `are_creds_rar` = SELECT read-only pe `accounts`.
- **Verificare E2E**: `curl -H "X-API-Key: rfak_..." http://localhost:8010/v1/ping` pe instanta locala → 200 cu contul corect +
`are_creds_rar`; import `postman.json` in Postman/Swagger.
@@ -98,13 +98,13 @@ de importat **pentru ca** sa-mi validez conexiunea inainte sa trimit prezentari
`test_prestatii_in_snippet_are_cod` (item-ul `prestatii` are `cod_prestatie` SAU `cod_op_service` — vezi `PrestatieItem._require_one`),
`test_placeholder_cheie_nu_e_valoare_reala` (mereu `rfak_...`, niciodata o cheie injectata).
- **Acceptance criteria**:
- [ ] Functie pura `exemple(base_url: str, account_id: int) -> dict` care intoarce, per limbaj, snippet pt ambele canale.
- [ ] Placeholder cheie = `rfak_...` (constant), endpoint-ul derivat din `base_url`.
- [ ] Payload-ul JSON reflecta schema reala (`PrezentareRequest`): `rar_credentials{email,password}` + `prezentari[]` cu
- [x] Functie pura `exemple(base_url: str, account_id: int) -> dict` care intoarce, per limbaj, snippet pt ambele canale.
- [x] Placeholder cheie = `rfak_...` (constant), endpoint-ul derivat din `base_url`.
- [x] Payload-ul JSON reflecta schema reala (`PrezentareRequest`): `rar_credentials{email,password}` + `prezentari[]` cu
**toate** campurile obligatorii prin `is_required()`, **inclusiv `prestatii` cu cel putin un item valid** (eng-review P1:
`prestatii` e fara default → obligatoriu; un payload fara el ar fi nevalid, exact ce pagina trebuie sa previna).
- [ ] Drift-test foloseste `field.is_required()` (nu doar prezenta cheii), ca sa nu raporteze fals campurile cu default.
- [ ] Fara I/O, fara DB, fara stare globala — pur (usor de testat).
- [x] Drift-test foloseste `field.is_required()` (nu doar prezenta cheii), ca sa nu raporteze fals campurile cu default.
- [x] Fara I/O, fara DB, fara stare globala — pur (usor de testat).
- **Verificare E2E**: acoperita de UI (US-003) + unit teste.
### US-003: UI — tab "Integrare" cu exemple, retetar VFP si export
@@ -128,24 +128,24 @@ sa integrez gateway-ul fara sa caut prin README/Swagger.
`test_empty_state_cta_cont_cand_fara_cheie_sau_creds`,
`test_fara_culori_hardcodate_doar_tokens` (snippet-ul template-ului nu contine `#`-hex; doar `var(--...)`).
- **Acceptance criteria**:
- [ ] Tab nou "Integrare" in nav (`dashboard.html`), `integrare` in `_TABS_VALIDE`, **branch nou in `_render_panel_for_tab`**,
- [x] Tab nou "Integrare" in nav (`dashboard.html`), `integrare` in `_TABS_VALIDE`, **branch nou in `_render_panel_for_tab`**,
deep-link `/?tab=integrare` randat server-side, fragment lazy pe click (pattern identic cu Mapari/Cont). NU atinge `_render_panel_import`/`coada`.
- [ ] **IA pe doua niveluri (design-review P1):** tab primar = LIMBAJ (curl/Python/PHP/C#/Node/VFP) — VFP e UN tab, nu sectiune
- [x] **IA pe doua niveluri (design-review P1):** tab primar = LIMBAJ (curl/Python/PHP/C#/Node/VFP) — VFP e UN tab, nu sectiune
separata; in panelul VFP, al doilea tablist = dialect (MSXML2 / WinHttp). Tab secundar (in fiecare panel de limbaj) = CANAL
(Prezentari JSON / Import fisier). Se vede UN singur snippet o data (fara produs cartezian 14-snippet pe ecran).
- [ ] **Refolosire ARIA (design-review P1):** generalizeaza scriptul de keyboard-nav din `dashboard.html` (sageti/Home/End,
- [x] **Refolosire ARIA (design-review P1):** generalizeaza scriptul de keyboard-nav din `dashboard.html` (sageti/Home/End,
roving `tabindex`, sync `aria-selected`+`aria-controls`) ca sa prinda ORICE `[role=tablist]`, scoped pe containerul propriu
(nu primul `querySelector` global) — altfel tab-urile imbricate (limbaj/canal/VFP-dialect) intra in conflict pe `ArrowRight`.
- [ ] **Copy-to-clipboard (design-review P2):** buton per snippet care citeste textul din `<pre><code>` asociat (NU din `data-*`);
- [x] **Copy-to-clipboard (design-review P2):** buton per snippet care citeste textul din `<pre><code>` asociat (NU din `data-*`);
feedback in `aria-live="polite"` + label "Copiat" cu revenire la "Copiaza" dupa ~2s; `navigator.clipboard` cu `.catch()` +
fallback pt context ne-securizat (selecteaza textul + "Ctrl+C") — butonul nu ramane blocat fals pe "Copiat".
- [ ] **Empty-state onboarding (design-review P2 + CEO P1):** cand sesiunea NU are cheie rotita sau NU are creds RAR, afiseaza
- [x] **Empty-state onboarding (design-review P2 + CEO P1):** cand sesiunea NU are cheie rotita sau NU are creds RAR, afiseaza
deasupra snippet-urilor un empty-state cu CTA direct `href="/?tab=cont"` ("Genereaza cheia din Cont; o vezi o singura data").
Snippet-urile raman vizibile (placeholder-based). Flux de onboarding continuu, nu doar o nota text.
- [ ] Card separat "Export & referinta" (componenta `.cardlink`): Swagger `/docs`, `GET /openapi.json`, `GET /v1/integrare/postman.json`
- [x] Card separat "Export & referinta" (componenta `.cardlink`): Swagger `/docs`, `GET /openapi.json`, `GET /v1/integrare/postman.json`
(Postman = `download`; restul `target="_blank" rel="noopener"`).
- [ ] Integreaza formularul "Testeaza conexiunea" din US-004 (primul card al panoului; markup-ul tinteste ruta US-004).
- [ ] **Tokens de tema (design-review P3, pregatire 5.3):** template-ul foloseste DOAR variabilele din `:root`
- [x] Integreaza formularul "Testeaza conexiunea" din US-004 (primul card al panoului; markup-ul tinteste ruta US-004).
- [x] **Tokens de tema (design-review P3, pregatire 5.3):** template-ul foloseste DOAR variabilele din `:root`
(`--bg`/`--card`/`--ink`/`--muted`/`--line`/`--accent`), zero culori hex hardcodate; fara highlight de sintaxa colorat
(ar introduce paleta noua). Cod copiabil pe `--ink`, nu `--muted`.
- **Verificare E2E**: browser pe `/integrare` — comuta limbaj (inclusiv VFP cu dialect), comuta canal, copiaza un snippet (vezi
@@ -169,16 +169,16 @@ integrarea va autentifica corect inainte sa scriu cod.
`test_csrf_lipsa_respinsa`,
`test_cheia_nu_apare_in_raspuns_sau_log` (no echo).
- **Acceptance criteria**:
- [ ] `POST /integrare/test-cheie` (`require_login`, CSRF): valideaza cheia lipita prin **`account_for_key` direct** (NU
- [x] `POST /integrare/test-cheie` (`require_login`, CSRF): valideaza cheia lipita prin **`account_for_key` direct** (NU
`resolve_account_id` — altfel in dev o cheie goala/gunoi ar cadea pe cont 1 si ar raporta fals "valida"); confirma DOAR
daca mapeaza pe **contul sesiunii**; altfel mesaj neutru ("nu apartine contului tau", fara sa dezvaluie care cont).
- [ ] Cheie revocata (dupa rotire) → tratata ca invalida (`account_for_key` filtreaza `active=1`).
- [ ] Niciun echo al cheii in raspuns/log (regula 422-no-echo din `main.py`).
- [ ] Raspuns HTML fragment dedicat (`_integrare_test_rezultat.html`), htmx swap intr-un container `aria-live="polite"`:
- [x] Cheie revocata (dupa rotire) → tratata ca invalida (`account_for_key` filtreaza `active=1`).
- [x] Niciun echo al cheii in raspuns/log (regula 422-no-echo din `main.py`).
- [x] Raspuns HTML fragment dedicat (`_integrare_test_rezultat.html`), htmx swap intr-un container `aria-live="polite"`:
succes pe `.flash` (border `--ok`, "Cheie valida — cont X"), eroare pe `.banner` (border `--err`). Label uman, fara emoji.
- [ ] Input `type=password` + `autocomplete="off"`; microcopy anti-confuzie langa camp: "Verificam doar daca cheia e valida.
- [x] Input `type=password` + `autocomplete="off"`; microcopy anti-confuzie langa camp: "Verificam doar daca cheia e valida.
Nu o salvam si nu o memoram — cheia se gestioneaza in Cont." Buton "Testeaza conexiunea" (NU "Salveaza").
- [ ] NU creeaza/roteste chei (doar verifica) — fara efecte secundare.
- [x] NU creeaza/roteste chei (doar verifica) — fara efecte secundare.
- **Verificare E2E**: browser — lipeste cheia reala (din rotire `/cont`) → "valida, cont X"; lipeste gunoi → eroare clara;
lipeste cheia veche dupa rotire → respinsa.
@@ -253,3 +253,29 @@ trimite), fara suport uman si fara sa atinga README/Swagger separat.
tema (pregatire 5.3); microcopy anti-confuzie + `type=password` la test-cheie.
Toate cele trei: **APROB CU MODIFICARI**, modificarile incorporate mai sus. Asteapta poarta umana de aprobare (§5.8) → `aprobat`.
---
## Raport VERIFY (2026-06-22 — subagent context curat + E2E browser lead)
**Rezultat: PASS** (dupa o runda de fix pe 4 discrepante minore).
- **Suita**: `python3 -m pytest -q`**564 passed**, 0 failed (523 baseline + 41 noi: 11 API ping/postman, 8 examples, 10 web integrare initial + 4 lock-uri fix, 8 test-cheie).
- **US-001..US-004 (criterii de acceptare)**: toate PASS (verificator independent, dovezi in cod + teste + `curl` live pe :8010 — ping cont 1 `autentificat_cu_cheie=false`, cheie valida `=true`, Bearer ok, cheie invalida 401, postman.json 3 items allowlist fara `/v1/conturi/rar-creds`, openapi 200).
- **Fix dupa VERIFY r1** (4 discrepante PRD-vs-implementare, toate in `_integrare.html`, lock-uite cu teste noi): cardul Export foloseste `.cardlink`; linkul Postman are `download` (fara `target=_blank`); microcopy anti-confuzie la test-cheie ("Nu o salvam si nu o memoram…"); butonul Copiaza isi schimba label-ul in "Copiat" + revine la 2s.
- **E2E browser (Playwright, lead, dev `web_auth_required=false`→cont 1)**: PASS pe `/?tab=integrare` — randare server-side, IA pe 2 niveluri (limbaj→canal), VFP cu al 3-lea nivel (dialect MSXML2↔WinHttp comuta corect, snippet WinHttp.WinHttpRequest.5.1), endpoint+account_id reale, empty-state CTA `/?tab=cont`, card Export, microcopy prezent, **htmx test-cheie**: cheie invalida → fragment eroare in container `aria-live` ("Cheie invalida sau revocata"). **0 erori in consola** (clasa de bug-uri htmx care a muscat 3.6 — absenta aici).
- **Regresia de aur (enqueue)**: `POST /v1/prezentari``status: queued` (live :8010). **Backend trimitere NEATINS** (doar rute noi de citire + UI; zero modificari worker/masina-stari/idempotenta/schema) — confirmat la diff. **Live RAR test (`postPrezentare`→FINALIZATA) NEPROBAT** in sesiune: `AUTOPASS_CREDS_KEY`/creds RAR test indisponibile in mediu. Risc minim (cod de trimitere neatins).
Toate PASS → CLOSE.
### `/code-review` high (2026-06-22 — CLOSE)
8 unghiuri (3 correctness + cleanup/altitude/conventii), verificare 1-vot. **4 bug-uri reale reparate** (toate in suprafata noua, backend trimitere neatins), lock-uite cu teste:
1. **C# snippet** — payload JSON multi-linie (`json.dumps indent=2`) intr-un string literal C# obisnuit → CS1010 (nu compileaza). Fix: `_payload_json_compact` (separators `(",",":")`, fara newline) pentru C#.
2. **VFP snippet**`json.dumps(indent=0)` PRODUCE TOTUSI newline-uri → `cPayload = "..."` rupt (VFP nu suporta string literal multi-linie) in ambele dialecte. Fix: acelasi helper compact.
3. **Node import snippet**`await import("node:buffer")` nu exporta `FormData``new FormData()` arunca TypeError pe orice Node; plus import mort `readFileSync`/`import("fs")` duplicat. Fix: `FormData`/`Blob` globale (Node 18+) + `readFileSync` static folosit direct.
4. **Script `_integrare.html` ne-scoped**`document.querySelectorAll('[role=tablist]')` ataseaza handlere si pe tab-bar-ul PRINCIPAL din `dashboard.html`, acumuland listeneri la fiecare swap htmx. Fix: scoping pe `#integrare-section` (`root.querySelectorAll(...)`), nu mai atinge tab-bar-ul principal.
**Notat ca cleanup viitor (NEREPARAT, disciplina backend/altitudine — low value, fara risc):** `_render_integrare` dubleaza SQL-ul `are_creds`/`are_cheie` din `_get_acasa_context`/`_render_panel_cont` (candidat de helper partajat); `ping` deschide 2 conexiuni DB + apeleaza `account_for_key` de 2 ori (derivabil din `account_id != DEFAULT_ACCOUNT_ID`); `_campuri_obligatorii()` necache-uit (apelat ~6×/render); cele 6 panouri de limbaj din `_integrare.html` copy-paste (candidat macro Jinja2); `{{ mesaj }}` fara `| e` explicit (salvat acum de autoescape, no-echo confirmat). **Pre-existente (in afara 5.1):** `GET /v1/nomenclator` + `/_fragments/nomenclator` neprotejate (deja notate in ROADMAP "de remediat").
568 teste pass.