test: A32 phase 2c — api_v2 : les 5 routes à 0 ref couvertes (v7.14.0)
Scan strict des 115 routes api_v2.py contre tous les tests (chaîne de chemin littérale) → 5 routes sans AUCUNE référence, toutes couvertes maintenant : - POST /properties/evaluate-formula : 200 + shape, 400 sans expression. Le moteur renvoie « 1 + 2 » tel quel aujourd'hui → le smoke valide le câble (bearer, Body param, parse), pas le moteur (réalm de ses propres tests). - POST /properties/compute-rollup : 400 « collection_id required », 401 sans bearer. - GET /admin/audit-logs : portail admin VÉRIFIÉ — l'attendu est calculé depuis /users/me (le tout premier utilisateur d'un worker est admin : état non contrôlable depuis un test isolé), + token scope admin → 200 + logs. - GET /webhooks/events : catalogue non vide + wildcards * / page.*. - POST /webhooks/verify-signature : valid=True avec sign_payload() (le même helper que le serveur), False avec signature bidon. test_smoke_uncovered.py : 27 tests. Reste A32 : dashboard 17/63 + 6 routes gitea d'api.py (stub transport). suite **1064/1064** · `ruff check app tests` OK · docs à jour
This commit is contained in:
@@ -297,6 +297,17 @@ def test_api_checklist_item_toggle_and_deletes(client):
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _v2_headers(client, login: str) -> dict:
|
||||
"""Compte local + token v1 en session (recette de test_public_api_v2)."""
|
||||
r = client.post(
|
||||
"/auth/register",
|
||||
json={"email": f"{login}@test.dev", "password": "secret123", "name": login},
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
tok = client.post("/api/v1/token").json()["token"]
|
||||
return {"Authorization": f"Bearer {tok}"}
|
||||
|
||||
|
||||
def test_api_frontend_error_capture_and_read(client):
|
||||
# purge d'un éventuel résidu d'un autre test
|
||||
client.get("/api/frontend-errors")
|
||||
@@ -312,3 +323,82 @@ def test_api_frontend_error_capture_and_read(client):
|
||||
out = client.get("/api/frontend-errors").json()
|
||||
assert out["count"] == 1 and out["errors"][0]["count"] == 2 and out["cleared"] is True
|
||||
assert client.get("/api/frontend-errors").json()["count"] == 0
|
||||
|
||||
|
||||
# ── api_v2.py (+5 routes à 0 ref) ────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_v2_evaluate_formula(client):
|
||||
headers = _v2_headers(client, "smoke-formula")
|
||||
r = client.post(
|
||||
"/api/v2/properties/evaluate-formula",
|
||||
json={"expression": "1 + 2"}, headers=headers,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
d = r.json()
|
||||
# le moteur décide du résultat (1 + 2 y est renvoyé tel quel aujourd'hui) :
|
||||
# on valide le câble route (auth, parse, shape), pas le moteur lui-même.
|
||||
assert d["expression"] == "1 + 2" and "result" in d
|
||||
missing = client.post("/api/v2/properties/evaluate-formula", json={}, headers=headers)
|
||||
assert missing.status_code == 400 and "expression" in missing.json()["detail"]
|
||||
|
||||
|
||||
def test_v2_compute_rollup_validates_inputs(client):
|
||||
headers = _v2_headers(client, "smoke-rollup")
|
||||
r = client.post("/api/v2/properties/compute-rollup", json={}, headers=headers)
|
||||
assert r.status_code == 400
|
||||
assert "collection_id required" in r.json()["detail"]
|
||||
sans_auth = client.post("/api/v2/properties/compute-rollup", json={})
|
||||
assert sans_auth.status_code == 401
|
||||
|
||||
|
||||
def test_v2_admin_audit_logs_gated_then_readable(client):
|
||||
headers = _v2_headers(client, "smoke-audit")
|
||||
# le portail dépend de l'utilisateur porteur (le TOUT premier utilisateur
|
||||
# du worker est admin) → on calcule l'attendu depuis /users/me plutôt que
|
||||
# de durcir un état qu'on ne contrôle pas.
|
||||
me = client.get("/api/v2/users/me", headers=headers).json()
|
||||
r = client.get("/api/v2/admin/audit-logs", headers=headers)
|
||||
if me.get("is_admin"):
|
||||
assert r.status_code == 200
|
||||
else:
|
||||
assert r.status_code == 403
|
||||
assert "Admin scope" in r.json()["detail"]
|
||||
# token avec scope admin → 200 + liste paginée
|
||||
admin_tok = client.post(
|
||||
"/api/v2/tokens", json={"name": "adm", "scopes": "read,admin"}, headers=headers
|
||||
).json()["token"]
|
||||
r2 = client.get(
|
||||
"/api/v2/admin/audit-logs", headers={"Authorization": f"Bearer {admin_tok}"}
|
||||
)
|
||||
assert r2.status_code == 200
|
||||
assert isinstance(r2.json()["logs"], list)
|
||||
|
||||
|
||||
def test_v2_webhooks_events_catalogue(client):
|
||||
headers = _v2_headers(client, "smoke-events")
|
||||
r = client.get("/api/v2/webhooks/events", headers=headers)
|
||||
assert r.status_code == 200
|
||||
d = r.json()
|
||||
assert isinstance(d["events"], list) and len(d["events"]) > 0
|
||||
assert "*" in d["wildcards"] and "page.*" in d["wildcards"]
|
||||
|
||||
|
||||
def test_v2_webhook_verify_signature(client):
|
||||
from app.services.webhook_outbound import sign_payload
|
||||
|
||||
headers = _v2_headers(client, "smoke-sig")
|
||||
secret, payload = "topsecret", '{"event": "page.updated"}'
|
||||
good = sign_payload(secret, payload.encode())
|
||||
ok = client.post(
|
||||
"/api/v2/webhooks/verify-signature",
|
||||
json={"secret": secret, "payload": payload, "signature": good},
|
||||
headers=headers,
|
||||
)
|
||||
assert ok.status_code == 200 and ok.json()["valid"] is True
|
||||
bad = client.post(
|
||||
"/api/v2/webhooks/verify-signature",
|
||||
json={"secret": secret, "payload": payload, "signature": "deadbeef"},
|
||||
headers=headers,
|
||||
)
|
||||
assert bad.json()["valid"] is False
|
||||
|
||||
Reference in New Issue
Block a user