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>
282 lines
24 KiB
Markdown
282 lines
24 KiB
Markdown
# PRD 5.1 — Hub de integrare (pagina /integrare cu exemple de cod + retetar VFP + ping + export)
|
||
|
||
**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).
|
||
> **Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge.**
|
||
> Aceasta livrabila adauga DOAR suprafata de onboarding/integrare (citire) peste API-ul existent + stratul web.
|
||
> Schema neatinsa (zero coloane noi).
|
||
|
||
## 1. Obiectiv
|
||
|
||
Produsul e functional pe ambele canale (API + import web), dar un service nou care vine din **Visual FoxPro**
|
||
sau soft propriu nu are de unde sa inceapa integrarea: cheia API se creeaza/roteste din web (`/cont`), insa nu
|
||
exista nicio pagina cu **cum chem API-ul**, ce payload, ce header, in ce limbaj. Singurele referinte sunt
|
||
`README.md` (curl) si Swagger `/docs` — invizibile din aplicatie si fara exemple in limbajul integratorului.
|
||
|
||
Livram o pagina **`/integrare`** (tab nou, autentificat, scoped pe contul din sesiune) care:
|
||
- afiseaza exemple de cod **pre-completate cu endpoint-ul real + account_id-ul utilizatorului** pentru ambele
|
||
canale (`POST /v1/prezentari` JSON si `POST /v1/import` upload fisier), in **curl, Python, PHP, C#, Node**;
|
||
- include un **retetar Visual FoxPro** (POST via `MSXML2.ServerXMLHTTP.6.0` SI `WinHttp.WinHttpRequest.5.1`,
|
||
plus upload CSV);
|
||
- ofera **export OpenAPI** (`/openapi.json` din FastAPI) + **colectie Postman** + link Swagger `/docs`;
|
||
- are un buton **"Testeaza conexiunea"**: utilizatorul lipeste cheia salvata, iar serverul confirma ca e valida
|
||
si mapeaza pe contul lui (acelasi drum de auth ca API-ul real).
|
||
|
||
Constrangere de securitate (din `app/auth.py`): stocam DOAR SHA-256 al cheii API, deci **nu putem reafisa cheia**.
|
||
Exemplele folosesc placeholder `rfak_...`; cheia reala se obtine o singura data la creare/rotire din `/cont`.
|
||
|
||
## 2. Non-Goals (anti scope-creep)
|
||
|
||
- **Nu reafisam cheia API** — exemplele au placeholder, niciodata cheia in clar (SHA-256-only).
|
||
- **Fara markdown versionat in `docs/`** (decis cu utilizatorul: doar pagina in-app dinamica). Daca apare nevoia
|
||
de referinta indexabila, e o livrabila viitoare separata.
|
||
- **Fara generare automata de SDK-uri** — OpenAPI + Postman sunt punctul de plecare; integratorul genereaza singur.
|
||
- **Fara dry-run** — validarea de payload fara enqueue e livrabila 5.2 (separata). `GET /v1/ping` verifica DOAR
|
||
autentificarea (cheie valida + cont activ), nu valideaza continut.
|
||
- **Fara editarea exemplelor de catre user** — continut read-only generat din schema.
|
||
- **Nu atinge backend-ul de trimitere** (worker/masina stari/idempotenta-logica/mapping-rezolvare) si **nici schema**.
|
||
- **Fara i18n** — pagina e in romana, ca restul UI-ului.
|
||
|
||
## 3. Stories atomice
|
||
|
||
> Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sub `require_login` +
|
||
> **scoped pe contul din sesiune**. CSRF pe toate POST-urile web. Endpoint-urile `/v1/*` folosesc `resolve_account_id`.
|
||
|
||
### US-001: Backend — endpoint-uri de integrare (`GET /v1/ping` + export Postman)
|
||
**Ca** integrator extern **vreau** un endpoint care confirma ca cheia mea functioneaza si o colectie Postman gata
|
||
de importat **pentru ca** sa-mi validez conexiunea inainte sa trimit prezentari reale.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/api/v1/integrare_router.py` (NOU), montare in `app/main.py` (`app.include_router`),
|
||
`tests/test_integrare_api.py` (~3 fisiere)
|
||
- **Montare (decizie eng-review P1)**: router nou cu `prefix="/v1"`, inclus in `app/main.py` cu `app.include_router(...)`
|
||
(consistent cu `import_v1_router`). NU sub-include in `app/api/v1/router.py` (acela are deja `prefix="/v1"` → ar dubla prefixul).
|
||
- **Test intai (RED)**: `tests/test_integrare_api.py` —
|
||
`test_ping_cu_cheie_valida_200` (raspuns: `account_id`, `mediu` test/prod, `autentificat_cu_cheie=true`, `are_creds_rar`, `ts`),
|
||
`test_ping_cu_bearer_valid_200` (`Authorization: Bearer rfak_...`),
|
||
`test_ping_fara_cheie_dev_cont_implicit` (flag off → cont 1, `autentificat_cu_cheie=false`),
|
||
`test_ping_x_api_key_gol_in_dev_cont_implicit` (whitespace tratat ca lipsa),
|
||
`test_ping_are_creds_rar_reflecta_contul` (cont cu `accounts.rar_creds_enc` → `true`; fara → `false`),
|
||
`test_ping_cheie_invalida_401`,
|
||
`test_ping_prod_fara_cheie_401` (flag on),
|
||
`test_ruta_ping_inregistrata_o_singura_data` (assert pe `app.routes` — fara dubla inregistrare),
|
||
`test_postman_export_json_valid` (Content-Type JSON, parseaza, schema Postman v2.1.0),
|
||
`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**:
|
||
- [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.)
|
||
- [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`).
|
||
- [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.
|
||
|
||
### US-002: Helper pur — generator de exemple de cod multi-limbaj
|
||
**Ca** dezvoltator al gateway-ului **vreau** un modul pur care produce snippet-urile de integrare dintr-un
|
||
`(base_url, account_id)` **pentru ca** pagina sa fie testabila si snippet-urile sa ramana langa schema reala.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/web/integrare_examples.py` (NOU), `tests/test_integrare_examples.py` (~2 fisiere)
|
||
- **Test intai (RED)**: `tests/test_integrare_examples.py` —
|
||
`test_snippet_curl_prezentari_contine_endpoint_si_header` (`{base_url}/v1/prezentari`, `X-API-Key: rfak_...`),
|
||
`test_snippet_python_import_upload_multipart` (foloseste `files=` / multipart pe `/v1/import`),
|
||
`test_toate_limbajele_prezente` (curl, python, php, csharp, node, vfp_msxml, vfp_winhttp),
|
||
`test_ambele_canale_per_limbaj` (fiecare limbaj are si prezentari JSON, si upload fisier),
|
||
`test_vfp_msxml_si_winhttp_distincte` (MSXML2.ServerXMLHTTP.6.0 vs WinHttp.WinHttpRequest.5.1),
|
||
`test_payload_acopera_campurile_obligatorii_din_model` (snippet-ul prezentari contine TOATE campurile obligatorii
|
||
derivate prin `field.is_required()` din `PrezentareIn` — INCLUSIV `prestatii` cu ≥1 item — si `RarCredentials{email,password}`;
|
||
drift-test corect: `{c for c,f in PrezentareIn.model_fields.items() if f.is_required()}` ⊆ campurile din snippet; campurile cu
|
||
default — `odometru_initial`, `obs`, `b64_image`, `sistem_reparat` — NU dau drift fals),
|
||
`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**:
|
||
- [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).
|
||
- [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
|
||
**Ca** utilizator **vreau** o pagina cu exemple gata de copiat in limbajul meu + linkuri de export **pentru ca**
|
||
sa integrez gateway-ul fara sa caut prin README/Swagger.
|
||
|
||
- **Depinde de**: US-001, US-002, US-004
|
||
- **Fisiere**: `app/web/routes.py` (`_render_integrare` + `/_fragments/integrare` + `integrare` in `_TABS_VALIDE` +
|
||
**branch nou in `_render_panel_for_tab`** — necesar pt deep-link server-side, altfel `/?tab=integrare` cade pe Acasa),
|
||
`app/web/templates/dashboard.html` (tuplu nav nou + generalizarea scriptului ARIA tablist), `app/web/templates/_integrare.html` (NOU),
|
||
`tests/test_web_integrare.py` (~4 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_integrare.py` —
|
||
`test_tab_integrare_in_nav`,
|
||
`test_deeplink_tab_integrare_randeaza_panou_server_side` (`/?tab=integrare` → panou randat, NU Acasa — prinde branch-ul lipsa din `_render_panel_for_tab`),
|
||
`test_fragment_integrare_necesita_login` (`require_login`),
|
||
`test_pagina_contine_account_id_si_endpoint_real` (din `request.base_url`),
|
||
`test_pagina_are_tab_limbaje_si_vfp_cu_dialecte` (limbaj = tablist primar; VFP = un tab cu sub-tablist MSXML2/WinHttp),
|
||
`test_canal_secundar_prezentari_si_import`,
|
||
`test_export_card_openapi_postman_swagger`,
|
||
`test_buton_copiaza_citeste_din_pre_code` (markup copy citeste din `<pre><code>`, nu din atribut),
|
||
`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**:
|
||
- [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`.
|
||
- [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).
|
||
- [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`.
|
||
- [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".
|
||
- [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.
|
||
- [x] Card separat "Export & referinta" (componenta `.cardlink`): Swagger `/docs`, `GET /openapi.json`, `GET /v1/integrare/postman.json`
|
||
(Postman = `download`; restul `target="_blank" rel="noopener"`).
|
||
- [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
|
||
"Copiat" + revenire), vede account_id-ul + endpoint-ul reale, deschide `/openapi.json` si importul Postman.
|
||
|
||
### US-004: Backend web — "Testeaza conexiunea" (verifica cheia lipita, scoped pe sesiune)
|
||
**Ca** utilizator **vreau** sa lipesc cheia mea si sa primesc confirmare ca e valida **pentru ca** sa stiu ca
|
||
integrarea va autentifica corect inainte sa scriu cod.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/web/routes.py` (`POST /integrare/test-cheie`, `require_login` + CSRF),
|
||
`app/web/templates/_integrare_test_rezultat.html` (NOU — fragment de raspuns dedicat, NU inline in `_integrare.html` care
|
||
apartine US-003; eng-review P2: evita fisier partajat intre valuri), `tests/test_integrare_test_cheie.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `tests/test_integrare_test_cheie.py` —
|
||
`test_cheie_valida_a_contului_curent_ok` (mesaj succes + account_id),
|
||
`test_cheie_a_altui_cont_respinsa` (cheia mapeaza pe alt cont → mesaj "nu apartine contului tau", NU dezvaluie care),
|
||
`test_cheie_invalida_mesaj_clar`,
|
||
`test_cheie_revocata_dupa_rotire_respinsa` (eng-review P2: cel mai probabil caz de suport — lipesti cheia veche dupa rotire),
|
||
`test_cheie_goala_nu_da_fals_pozitiv_in_dev` (eng-review P2: foloseste `account_for_key` direct, NU `resolve_account_id` cu fallback cont 1),
|
||
`test_fara_login_redirect_sau_401`,
|
||
`test_csrf_lipsa_respinsa`,
|
||
`test_cheia_nu_apare_in_raspuns_sau_log` (no echo).
|
||
- **Acceptance criteria**:
|
||
- [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).
|
||
- [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.
|
||
- [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").
|
||
- [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.
|
||
|
||
## 4. Graful de valuri
|
||
|
||
- **Val 1 (paralel, fisiere disjuncte):** US-001 (`integrare_router.py` NOU) ‖ US-002 (`integrare_examples.py` NOU)
|
||
‖ US-004 (`routes.py` + test). US-001 si US-002 sunt fisiere noi; US-004 atinge `routes.py` (ruta noua, fara
|
||
suprapunere cu US-003 care vine in Val 2). Atentie: US-003 si US-004 ating ambele `routes.py` → NU in acelasi val.
|
||
- **Val 2:** US-003 (UI: `routes.py` + `dashboard.html` + `_integrare.html`) — depinde de US-001/US-002/US-004.
|
||
Nota (eng-review P2): US-004 isi pune raspunsul in `_integrare_test_rezultat.html` (fisier propriu), NU in `_integrare.html`
|
||
(al US-003) — asa cele doua valuri nu impart un template.
|
||
|
||
**Prioritate de descope (CEO-review P2/P3)** — daca timpul se scurteaza, taie in aceasta ordine inversa de valoare:
|
||
1. (pastreaza ultimul) **US-002 + US-003 exemple + retetar VFP in pagina** = wedge-ul de adoptie, ne-negociabil.
|
||
2. US-004 (test cheie) — potentator.
|
||
3. US-001 ping — potentator.
|
||
4. (primul taiat) **export Postman/OpenAPI** — audienta moderna (Python/Node), public secundar fata de VFP. Retetarul VFP
|
||
ramane prim-clas indiferent de descope.
|
||
|
||
## 5. Considerații tehnice
|
||
|
||
- **Auth**: header `X-API-Key: rfak_...` (sau `Authorization: Bearer rfak_...`), dependinta `resolve_account_id`
|
||
(`app/auth.py`). In dev (`require_api_key=false`) lipsa cheii → cont 1; `ping` semnaleaza asta cu `autentificat_cu_cheie`.
|
||
- **Schema request** (din `app/models.py`): `PrezentareRequest{rar_credentials{email,password}, prezentari[PrezentareIn]}`;
|
||
`PrezentareIn{vin, nr_inmatriculare, data_prestatie(YYYY-MM-DD), odometru_final(str), odometru_initial?, sistem_reparat="null",
|
||
obs?, b64_image?, prestatii?[{cod_prestatie|cod_op_service, denumire}]}`.
|
||
- **Import**: `POST /v1/import`, multipart, camp `file` (`app/api/v1/import_router.py`).
|
||
- **base_url** derivat din `request.base_url`; documenteaza override in spatele proxy (X-Forwarded). Risc minor de
|
||
base_url gresit in spatele reverse-proxy — mitigat prin nota in pagina.
|
||
- **Pattern UI**: tab nou = tuplu in `dashboard.html` + `integrare` in `_TABS_VALIDE` + `_render_integrare` +
|
||
`/_fragments/integrare` (identic cu Mapari/Cont/Nomenclator).
|
||
- **OpenAPI/Postman**: `/openapi.json` e gratuit din FastAPI; colectia Postman e construita o data din endpoint-urile cunoscute.
|
||
|
||
## 6. Riscuri
|
||
|
||
- **Drift de schema** intre snippet-uri si `PrezentareIn` real → test care leaga snippet-ul de `model_fields` (US-002).
|
||
- **base_url** gresit in spatele proxy → nota + derivare din request.
|
||
- **Confuzie dev "merge fara cheie"** (cont 1) → `ping` raporteaza `autentificat_cu_cheie=false`.
|
||
- **Scurgere cheie in test-cheie** → no-echo enforce + test dedicat (US-004).
|
||
|
||
## 7. Intrebari deschise
|
||
|
||
Rezolvate cu utilizatorul (2026-06-22): test conexiune = `GET /v1/ping` nou (independent de 5.2); exemple pe ambele
|
||
canale (prezentari + import); continut doar in-app dinamic (fara markdown versionat); VFP = ambele dialecte (MSXML2 + WinHttp).
|
||
Niciuna ramasa deschisa.
|
||
|
||
## 8. Verificare (rezumat E2E per canal)
|
||
|
||
- API: `curl GET /v1/ping` cu cheie reala pe instanta locala (8010) → 200 cont corect; import `postman.json`.
|
||
- Web: browser pe `/integrare` — comuta limbaj, copiaza snippet, vede account_id+endpoint reale, testeaza cheia.
|
||
- Regresia de aur (neatinsa, dar de confirmat ca nu s-a stricat): `POST /v1/prezentari` (sau import → commit) →
|
||
worker → `FINALIZATA` la RAR test.
|
||
|
||
**Criteriu de succes al wedge-ului (CEO-review P3):** un service nou (stil VFP) ajunge de la zero la o prezentare
|
||
`FINALIZATA` la RAR test folosind DOAR pagina `/integrare` (copiaza snippet → testeaza cheia → vede `are_creds_rar` →
|
||
trimite), fara suport uman si fara sa atinga README/Swagger separat.
|
||
|
||
---
|
||
|
||
## Review-uri de plan (§5.3 — rulate 2026-06-22)
|
||
|
||
- [x] **CEO** (valoare/scope) — APROB CU MODIFICARI. Aplicat: ping/test ca readiness-check (`are_creds_rar`), CTA continuu spre
|
||
`/cont` in empty-state, VFP prim-clas + prioritate de descope (§4), criteriu de succes masurabil (§8). (P2 "ping redundant"
|
||
rezolvat prin diferentierea payload-ului: ping poarta `are_creds_rar`+`mediu`, deci e readiness, nu auth-echo.)
|
||
- [x] **Eng** (fezabilitate/teste) — APROB CU MODIFICARI. Aplicat: montare router `prefix="/v1"` in `main.py` fara dublare +
|
||
test inregistrare unica; drift-test cu `is_required()` + `prestatii` obligatoriu (US-002); template dedicat
|
||
`_integrare_test_rezultat.html` (US-004, anti fisier-partajat intre valuri); teste Bearer/cheie-goala/cheie-revocata;
|
||
`account_for_key` nu `resolve_account_id`; Postman allowlist exact 3 (nu `app.routes`); `_render_panel_for_tab` in fisierele US-003.
|
||
- [x] **Design** (UI) — APROB CU MODIFICARI, claritate plan UI 6/10 → adresat. Aplicat: IA pe 2 niveluri (limbaj primar, canal
|
||
secundar, VFP cu sub-dialect); refolosire + generalizare script ARIA tablist (scoped per container); copy din `<pre><code>` +
|
||
`aria-live` + fallback non-HTTPS; empty-state cu CTA `/?tab=cont`; card "Export & referinta" cu `.cardlink`; doar tokens de
|
||
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.
|