Files
rar-autopass/tests/test_integrare_examples.py
Claude Agent 5dc963a02c feat(api): rar_credentials optional pe POST /v1/prezentari
Cand `rar_credentials` lipseste din cerere, submission-ul intra fara creds
efemere, iar worker-ul cade pe creds-urile RAR durabile ale contului
(accounts.rar_creds_enc). Identificarea contului ramane pe cheia API.
Trimiterea explicita a creds-urilor suprascrie creds-urile contului pe acea
cerere (back-compat: fluxul vechi ROAAUTO merge identic).

- models.py: rar_credentials: RarCredentials | None = None
- router.py: cripteaza creds doar daca exista (altfel creds_enc=NULL)
- worker NEATINS: avea deja fallback _creds_for(...) or _creds_from_account(...)

Pagina /integrare aliniata: exemplele cod (7 limbaje) + export Postman nu mai
includ rar_credentials in payload; nota noua explica modelul (creds pe cont,
optional in payload). README rescris compact + reflecta optionalitatea.

Test nou: enqueue fara creds -> submission fara creds efemere -> fallback pe
contul cu creds salvate. Suita: 673 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:39:53 +00:00

240 lines
9.7 KiB
Python

"""Teste pentru app.web.integrare_examples — modul PUR, fara I/O.
Ordinea: RED (fara implementare) -> GREEN (dupa implementare).
"""
import json
import pytest
from app.models import PrezentareIn
from app.web.integrare_examples import exemple
BASE_URL = "https://autopass.example.com"
ACCOUNT_ID = 7
RESULT = exemple(BASE_URL, ACCOUNT_ID)
LIMBAJE_OBLIGATORII = ["curl", "python", "php", "csharp", "node", "vfp_msxml", "vfp_winhttp"]
# ---------------------------------------------------------------------------
# Structura de baza
# ---------------------------------------------------------------------------
def test_toate_limbajele_prezente():
"""Toate limbajele obligatorii trebuie sa fie chei in dictionar."""
for limbaj in LIMBAJE_OBLIGATORII:
assert limbaj in RESULT, f"lipseste limbajul: {limbaj}"
def test_ambele_canale_per_limbaj():
"""Fiecare limbaj are atat 'prezentari' cat si 'import'."""
for limbaj in LIMBAJE_OBLIGATORII:
assert "prezentari" in RESULT[limbaj], f"{limbaj} lipseste canalul 'prezentari'"
assert "import" in RESULT[limbaj], f"{limbaj} lipseste canalul 'import'"
assert isinstance(RESULT[limbaj]["prezentari"], str), f"{limbaj}.prezentari nu e string"
assert isinstance(RESULT[limbaj]["import"], str), f"{limbaj}.import nu e string"
# ---------------------------------------------------------------------------
# curl — prezentari
# ---------------------------------------------------------------------------
def test_snippet_curl_prezentari_contine_endpoint_si_header():
"""Snippetul curl prezentari contine URL-ul corect si header-ul X-API-Key."""
snippet = RESULT["curl"]["prezentari"]
assert f"{BASE_URL}/v1/prezentari" in snippet, "lipseste endpoint-ul /v1/prezentari"
assert "X-API-Key" in snippet, "lipseste header-ul X-API-Key"
assert "rfak_..." in snippet, "placeholder-ul cheii trebuie sa fie rfak_..."
# ---------------------------------------------------------------------------
# python — import (multipart)
# ---------------------------------------------------------------------------
def test_snippet_python_import_upload_multipart():
"""Snippetul python import foloseste multipart (files=) pe /v1/import."""
snippet = RESULT["python"]["import"]
assert f"{BASE_URL}/v1/import" in snippet, "lipseste endpoint-ul /v1/import"
assert "files=" in snippet or 'files =' in snippet, "lipseste multipart files="
assert "rfak_..." in snippet, "placeholder-ul cheii trebuie sa fie rfak_..."
# ---------------------------------------------------------------------------
# VFP — doua dialecte distincte
# ---------------------------------------------------------------------------
def test_vfp_msxml_si_winhttp_distincte():
"""vfp_msxml foloseste MSXML2.ServerXMLHTTP.6.0; vfp_winhttp foloseste WinHttp.WinHttpRequest.5.1."""
msxml_snippet = RESULT["vfp_msxml"]["prezentari"]
winhttp_snippet = RESULT["vfp_winhttp"]["prezentari"]
assert "MSXML2.ServerXMLHTTP.6.0" in msxml_snippet, (
"vfp_msxml trebuie sa foloseasca MSXML2.ServerXMLHTTP.6.0"
)
assert "WinHttp.WinHttpRequest.5.1" in winhttp_snippet, (
"vfp_winhttp trebuie sa foloseasca WinHttp.WinHttpRequest.5.1"
)
# Distincte — nu acelasi obiect COM in ambele
assert "MSXML2.ServerXMLHTTP.6.0" not in winhttp_snippet, (
"vfp_winhttp nu trebuie sa contina MSXML2 (dialect gresit)"
)
assert "WinHttp.WinHttpRequest.5.1" not in msxml_snippet, (
"vfp_msxml nu trebuie sa contina WinHttp (dialect gresit)"
)
# ---------------------------------------------------------------------------
# Drift-test schema — campuri obligatorii din model
# ---------------------------------------------------------------------------
def test_payload_acopera_campurile_obligatorii_din_model():
"""Snippetul curl prezentari contine toate campurile obligatorii din PrezentareIn.
Deriva lista din model_fields pentru a fi rezistenta la schimbari de schema.
Campurile cu default (odometru_initial, obs, b64_image, sistem_reparat) nu sunt
obligatorii si nu trebuie sa cauzeze drift fals.
"""
obligatorii = {
camp
for camp, field in PrezentareIn.model_fields.items()
if field.is_required()
}
# Obligatorii asteptate conform spec: vin, nr_inmatriculare, data_prestatie,
# odometru_final, prestatii
snippet = RESULT["curl"]["prezentari"]
for camp in obligatorii:
assert camp in snippet, f"camp obligatoriu absent din snippet: {camp}"
def test_payload_nu_include_credentiale_rar():
"""rar_credentials e OPTIONAL: snippet-ul exemplu trimite doar cheia API + datele
prezentarii (creds-urile RAR se configureaza pe cont, nu in fiecare cerere)."""
for limbaj in LIMBAJE_OBLIGATORII:
snippet = RESULT[limbaj]["prezentari"]
assert "rar_credentials" not in snippet, (
f"{limbaj}.prezentari include rar_credentials — nu mai e necesar in payload"
)
def test_prestatii_in_snippet_are_cod():
"""Snippetul prezentari contine cod_prestatie sau cod_op_service in payload."""
for limbaj in LIMBAJE_OBLIGATORII:
snippet = RESULT[limbaj]["prezentari"]
are_cod = "cod_prestatie" in snippet or "cod_op_service" in snippet
assert are_cod, f"{limbaj}.prezentari lipseste cod_prestatie / cod_op_service"
# ---------------------------------------------------------------------------
# Placeholder cheie
# ---------------------------------------------------------------------------
def test_placeholder_cheie_nu_e_valoare_reala():
"""Toate snippet-urile cu autentificare contin literal 'rfak_', nu o cheie reala."""
for limbaj in LIMBAJE_OBLIGATORII:
for canal in ["prezentari", "import"]:
snippet = RESULT[limbaj][canal]
assert "rfak_" in snippet, (
f"{limbaj}.{canal} lipseste placeholder-ul rfak_ (cheie API)"
)
# ---------------------------------------------------------------------------
# FIX-1 — C# si VFP: JSON compact (fara newline in string literal)
# ---------------------------------------------------------------------------
def test_csharp_payload_pe_o_singura_linie():
"""Snippetul C# prezentari: JSON-ul din var json = "..." e pe o singura linie.
Un JSON multi-linie intr-un string literal C# NU compileaza.
Verificam ca helper-ul compact produce un JSON fara newline
si ca acel JSON compact apare in snippetul C#.
"""
from app.web.integrare_examples import _payload_json_compact, _snippet_csharp_prezentari
compact = _payload_json_compact(ACCOUNT_ID)
assert "\n" not in compact, (
"_payload_json_compact produce newline — nu e compact"
)
snippet = _snippet_csharp_prezentari(BASE_URL, ACCOUNT_ID)
# JSON-ul compact (cu ghilimele escape-uite) trebuie sa fie substring in snippet
escaped = compact.replace('"', '\\"')
assert escaped in snippet, (
"JSON-ul compact (escaped) nu apare in snippetul C#; "
"probabil _snippet_csharp_prezentari inca foloseste _payload_json_str"
)
# Verificare directa: linia var json = "..." nu contine newline in interiorul ei
# In C# linia se termina cu "; (ghilimea de inchidere + punct-virgula)
for line in snippet.splitlines():
if 'var json = "' in line:
stripped = line.rstrip()
# Linia trebuie sa se inchida pe acelasi rand (cu "; sau ")
assert stripped.endswith('";') or stripped.endswith('"'), (
f"Linia 'var json = \"...' nu se termina pe acelasi rand: {line!r}"
)
break
else:
assert False, "Linia 'var json = \"...' nu a fost gasita in snippetul C#"
def test_vfp_payload_pe_o_singura_linie():
"""Snippetul VFP prezentari (ambele dialecte): cPayload = "..." e pe o linie.
Un string literal VFP multi-linie NU e valid.
Verificam ambele dialecte: vfp_msxml si vfp_winhttp.
"""
from app.web.integrare_examples import _payload_json_compact
compact = _payload_json_compact(ACCOUNT_ID)
# In VFP ghilimele se dubleaza
compact_vfp = compact.replace('"', '""')
for dialect in ("vfp_msxml", "vfp_winhttp"):
snippet = RESULT[dialect]["prezentari"]
# Payload-ul VFP (cu doubling) trebuie sa fie substring in snippet
assert compact_vfp in snippet, (
f"{dialect}: JSON-ul compact (cu \"\" doubling) nu apare in snippet; "
"probabil inca se foloseste _payload_json_str cu indent"
)
# Linia cPayload = "..." nu trebuie sa contina newline in interiorul valorii
for line in snippet.splitlines():
if 'cPayload = "' in line:
assert line.rstrip().endswith('"'), (
f"{dialect}: linia 'cPayload = \"...' nu se termina pe acelasi rand: {line!r}"
)
break
else:
assert False, f"{dialect}: linia 'cPayload = \"...' nu a fost gasita in snippet"
# ---------------------------------------------------------------------------
# FIX-2 — Node import: FormData/Blob globale, fara import din node:buffer
# ---------------------------------------------------------------------------
def test_node_import_nu_foloseste_node_buffer():
"""Snippetul Node import nu importa din 'node:buffer' si foloseste FormData globala.
'node:buffer' nu exporta FormData — `new FormData()` ar arunca TypeError.
In Node 18+ FormData si Blob sunt globale.
"""
snippet = RESULT["node"]["import"]
assert "node:buffer" not in snippet, (
"Snippetul Node import contine 'node:buffer' — invalid, FormData nu e acolo"
)
assert "new FormData()" in snippet, (
"Snippetul Node import nu contine 'new FormData()' — FormData trebuie folosita global"
)