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:
215
tests/test_integrare_api.py
Normal file
215
tests/test_integrare_api.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""Teste US-001: endpoint-uri de integrare (GET /v1/ping + export Postman).
|
||||
|
||||
TDD — toate testele RED inainte de implementare.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Fixture client izolat #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@pytest.fixture()
|
||||
def client(monkeypatch):
|
||||
"""Client FastAPI cu DB temporara izolata, require_api_key=False (dev)."""
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "false")
|
||||
monkeypatch.setenv("AUTOPASS_RAR_ENV", "test")
|
||||
from app.config import get_settings
|
||||
get_settings.cache_clear()
|
||||
from app.main import app
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client_prod(monkeypatch):
|
||||
"""Client cu require_api_key=True (mod prod)."""
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "true")
|
||||
monkeypatch.setenv("AUTOPASS_RAR_ENV", "prod")
|
||||
from app.config import get_settings
|
||||
get_settings.cache_clear()
|
||||
from app.main import app
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def _creeaza_cheie(monkeypatch) -> str:
|
||||
"""Seed cont id=1 + creeaza cheie API; intoarce cheia in clar."""
|
||||
from app.db import get_connection
|
||||
from app.auth import create_api_key
|
||||
conn = get_connection()
|
||||
try:
|
||||
cheie = create_api_key(conn, 1)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return cheie
|
||||
|
||||
|
||||
def _seteaza_rar_creds(monkeypatch=None) -> None:
|
||||
"""Seteaza rar_creds_enc pe contul id=1."""
|
||||
from app.db import get_connection
|
||||
from app.crypto import encrypt_creds
|
||||
conn = get_connection()
|
||||
try:
|
||||
enc = encrypt_creds({"email": "test@rar.ro", "password": "secret"})
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=? WHERE id=1", (enc,))
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Teste ping #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_ping_cu_cheie_valida_200(client, monkeypatch):
|
||||
"""GET /v1/ping cu X-API-Key valida -> 200 cu campurile cerute."""
|
||||
cheie = _creeaza_cheie(monkeypatch)
|
||||
r = client.get("/v1/ping", headers={"X-API-Key": cheie})
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert "account_id" in body
|
||||
assert "mediu" in body
|
||||
assert "autentificat_cu_cheie" in body
|
||||
assert "are_creds_rar" in body
|
||||
assert "ts" in body
|
||||
assert body["autentificat_cu_cheie"] is True
|
||||
assert body["mediu"] == "test"
|
||||
|
||||
|
||||
def test_ping_cu_bearer_valid_200(client, monkeypatch):
|
||||
"""GET /v1/ping cu Authorization: Bearer valida -> 200, autentificat_cu_cheie=True."""
|
||||
cheie = _creeaza_cheie(monkeypatch)
|
||||
r = client.get("/v1/ping", headers={"Authorization": f"Bearer {cheie}"})
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["autentificat_cu_cheie"] is True
|
||||
assert body["account_id"] == 1
|
||||
|
||||
|
||||
def test_ping_fara_cheie_dev_cont_implicit(client, monkeypatch):
|
||||
"""Fara cheie, require_api_key=False -> cont 1, autentificat_cu_cheie=False."""
|
||||
r = client.get("/v1/ping")
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["account_id"] == 1
|
||||
assert body["autentificat_cu_cheie"] is False
|
||||
|
||||
|
||||
def test_ping_x_api_key_gol_in_dev_cont_implicit(client, monkeypatch):
|
||||
"""X-API-Key cu doar spatii = lipsa cheie -> cont 1, autentificat_cu_cheie=False in dev."""
|
||||
r = client.get("/v1/ping", headers={"X-API-Key": " "})
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["account_id"] == 1
|
||||
assert body["autentificat_cu_cheie"] is False
|
||||
|
||||
|
||||
def test_ping_are_creds_rar_reflecta_contul(client, monkeypatch):
|
||||
"""Cont fara creds -> are_creds_rar=False; dupa setare -> True."""
|
||||
# Fara creds
|
||||
r1 = client.get("/v1/ping")
|
||||
assert r1.status_code == 200
|
||||
assert r1.json()["are_creds_rar"] is False
|
||||
|
||||
# Seteaza creds pe cont 1
|
||||
_seteaza_rar_creds()
|
||||
|
||||
r2 = client.get("/v1/ping")
|
||||
assert r2.status_code == 200
|
||||
assert r2.json()["are_creds_rar"] is True
|
||||
|
||||
|
||||
def test_ping_cheie_invalida_401(client, monkeypatch):
|
||||
"""Cheie invalida -> 401, indiferent de require_api_key."""
|
||||
r = client.get("/v1/ping", headers={"X-API-Key": "rfak_cheie_falsa_xxxxxxxx"})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_ping_prod_fara_cheie_401(client_prod, monkeypatch):
|
||||
"""require_api_key=True, fara cheie -> 401."""
|
||||
r = client_prod.get("/v1/ping")
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_ruta_ping_inregistrata_o_singura_data(client, monkeypatch):
|
||||
"""Ruta /v1/ping trebuie sa apara exact o data in app.routes."""
|
||||
from app.main import app
|
||||
rute_ping = [r for r in app.routes if hasattr(r, "path") and r.path == "/v1/ping"]
|
||||
assert len(rute_ping) == 1, f"Asteptat 1 ruta /v1/ping, gasit: {len(rute_ping)}"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Teste export Postman #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_postman_export_json_valid(client, monkeypatch):
|
||||
"""GET /v1/integrare/postman.json -> 200, Content-Type JSON, structura Postman v2.1.0."""
|
||||
r = client.get("/v1/integrare/postman.json")
|
||||
assert r.status_code == 200, r.text
|
||||
assert "application/json" in r.headers.get("content-type", "")
|
||||
body = r.json()
|
||||
assert "info" in body
|
||||
assert "v2.1.0" in body["info"].get("schema", ""), f"Schema gresita: {body['info'].get('schema')}"
|
||||
|
||||
|
||||
def test_postman_contine_exact_trei_requesturi(client, monkeypatch):
|
||||
"""Colectia Postman trebuie sa contina exact 3 requesturi cu headers si url corecte."""
|
||||
r = client.get("/v1/integrare/postman.json")
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
items = body.get("item", [])
|
||||
assert len(items) == 3, f"Asteptat 3 requesturi, gasit: {len(items)}"
|
||||
|
||||
for item in items:
|
||||
req = item.get("request", {})
|
||||
# Fiecare trebuie sa aiba header X-API-Key cu valoarea {{api_key}}
|
||||
headers = req.get("header", [])
|
||||
cheie_header = [h for h in headers if h.get("key") == "X-API-Key"]
|
||||
assert len(cheie_header) == 1, f"Header X-API-Key lipsa in request '{item.get('name')}'"
|
||||
assert cheie_header[0].get("value") == "{{api_key}}", \
|
||||
f"Valoare header gresita: {cheie_header[0].get('value')}"
|
||||
|
||||
# URL-ul trebuie sa contina {{base_url}}
|
||||
url = req.get("url", {})
|
||||
url_raw = url.get("raw", "") if isinstance(url, dict) else str(url)
|
||||
assert "{{base_url}}" in url_raw, \
|
||||
f"{{{{base_url}}}} lipsa in url pentru '{item.get('name')}': {url_raw}"
|
||||
|
||||
|
||||
def test_postman_nu_deriva_din_app_routes(client, monkeypatch):
|
||||
"""Colectia Postman contine EXACT cele 3 rute allowlist, nu mai mult."""
|
||||
r = client.get("/v1/integrare/postman.json")
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
items = body.get("item", [])
|
||||
|
||||
# Extrage URL-urile / path-urile din colectie
|
||||
cai_expuse = set()
|
||||
for item in items:
|
||||
req = item.get("request", {})
|
||||
url = req.get("url", {})
|
||||
url_raw = url.get("raw", "") if isinstance(url, dict) else str(url)
|
||||
# Normalizeaza: scoate {{base_url}} si parametrii query
|
||||
cale = url_raw.replace("{{base_url}}", "").split("?")[0]
|
||||
cai_expuse.add(cale)
|
||||
|
||||
# Allowlist: exact acestea 3
|
||||
allowlist = {"/v1/prezentari", "/v1/import", "/v1/ping"}
|
||||
assert cai_expuse == allowlist, \
|
||||
f"Colectia expune cai neasteptate: {cai_expuse - allowlist} sau lipsesc: {allowlist - cai_expuse}"
|
||||
Reference in New Issue
Block a user