fix(tests): rendre la suite hermétique — pin oauth_redirect_uri + FLOWDECK_DATA_DIR temporel
- test_get_redirect_uri_from_host_header: épingle oauth_redirect_uri à vide pour tester la dérivation Host de façon isolée (le .env du projet définit OAUTH_REDIRECT_URI, qui passe prioritaire par design) - conftest: pose FLOWDECK_DATA_DIR vers un répertoire temporel inscriptible pour les tests emoji/docx/covers qui dépendaient de /data (conteneur) - ajoute scripts/audit_functional.py: audit de bout-en-bout des processus (notes, DB, tâches, partage, publication, export, recherche, agents) Suite locale: 538 passed
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
FlowDeck — Audit fonctionnel de bout-en-bout.
|
||||
Base SQLite isolée (init schéma) + TestClient FastAPI réel.
|
||||
Vérifie status code + payload de chaque processus métier.
|
||||
"""
|
||||
import os, tempfile, json, sys
|
||||
from pathlib import Path
|
||||
|
||||
_tmp = tempfile.mkdtemp(prefix="fd_audit_")
|
||||
os.environ["DATABASE_URL"] = f"sqlite:///{_tmp}/audit.db"
|
||||
os.environ["APP_SECRET_KEY"] = "audit-secret-0000"
|
||||
os.environ["RATE_LIMIT_ENABLED"] = "false"
|
||||
os.environ["BACKUP_ENABLED"] = "false"
|
||||
os.environ["FLOWDECK_DATA_DIR"] = _tmp
|
||||
os.environ["PROJECT_SYNC_ENABLED"] = "false"
|
||||
os.environ["PROJECT_SYNC"] = "false"
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
from app.db import init_db
|
||||
init_db()
|
||||
|
||||
client = TestClient(app)
|
||||
results = []
|
||||
|
||||
def csrf():
|
||||
return client.get("/api/csrf-token").json().get("token", "")
|
||||
|
||||
def h(c):
|
||||
return {"X-CSRF-Token": c, "Content-Type": "application/json"}
|
||||
|
||||
def register_login(email="[email protected]", pwd="secret123"):
|
||||
c = csrf()
|
||||
client.post("/auth/register", json={"name": "Audit", "email": email, "password": pwd}, headers=h(c))
|
||||
c2 = csrf()
|
||||
r = client.post("/auth/local-login", json={"email": email, "password": pwd}, headers=h(c2))
|
||||
return c2
|
||||
|
||||
def rec(name, ok, detail=""):
|
||||
results.append((name, ok, detail))
|
||||
print(f"{'PASS' if ok else 'FAIL'} {name}" + (f" — {detail}" if detail else ""))
|
||||
|
||||
print("="*72)
|
||||
print("FLOWDECK — AUDIT FONCTIONNEL (base isolée)")
|
||||
print("="*72)
|
||||
|
||||
try:
|
||||
# ── 1. AUTH & WORKSPACE ───────────────────────────────
|
||||
c = register_login()
|
||||
me = client.get("/api/users/me").json()
|
||||
rec("register + login user local", me.get("login") == "[email protected]" or me.get("email") == "[email protected]", f"login={me.get('login')} admin={me.get('is_admin')}")
|
||||
|
||||
r = client.post("/api/workspaces", json={"name": "Audit WS"}, headers=h(c))
|
||||
rec("POST /api/workspaces", r.status_code in (200, 201, 302), f"HTTP {r.status_code} {str(r.text)[:70]}")
|
||||
|
||||
# ── 2. CRÉATION NOTE ──────────────────────────────────
|
||||
r = client.post("/api/pages", json={"workspace": "Audit WS", "title": "Note de test", "content": "## MD init", "content_format": "markdown"}, headers=h(c))
|
||||
pid = r.json().get("id") if r.status_code in (200, 201) else None
|
||||
rec("POST /api/pages (création note)", pid is not None, f"HTTP {r.status_code} body={str(r.text)[:60]}")
|
||||
|
||||
# ── 3. CRÉATION BASE DE DONNÉES (collection) ──────────
|
||||
r = client.post("/db/api", json={"name": "Tâches", "schema": [{"name":"Statut","prop_type":"select","options":["À faire","En cours","Fait"]}]}, headers=h(c))
|
||||
coll = r.json()
|
||||
cid = coll.get("id")
|
||||
rec("POST /db/api (création DB)", cid is not None, f"HTTP {r.status_code} id={cid}")
|
||||
|
||||
# ── 4. TÂCHES (rows) ──────────────────────────────────
|
||||
if cid:
|
||||
r = client.post(f"/db/{cid}/pages/api", json={"title": "Terminer audit", "property_values_json": json.dumps({"Statut":"En cours"})}, headers=h(c))
|
||||
rec("POST /{cid}/pages (création row)", r.status_code in (200, 201, 200), f"HTTP {r.status_code} {str(r.text)[:70]}")
|
||||
|
||||
# ── 5. (Kanban board-config testé via instance Gitea réelle — cf. audit live) ──
|
||||
|
||||
# ── 6. SHARE / PUBLISH ────────────────────────────────
|
||||
if pid:
|
||||
r = client.post(f"/api/pages/{pid}/share", json={"email":"[email protected]","permission":"edit"}, headers=h(c))
|
||||
rec("POST /pages/{id}/share", r.status_code in (200, 201), f"HTTP {r.status_code} {str(r.text)[:70]}")
|
||||
r = client.post(f"/api/pages/{pid}/publish", json={}, headers=h(c))
|
||||
rec("POST /pages/{id}/publish", r.status_code in (200, 201), f"HTTP {r.status_code} {str(r.text)[:70]}")
|
||||
|
||||
# ── 7. EXPORT ─────────────────────────────────────────
|
||||
if pid:
|
||||
r = client.get(f"/api/export/markdown/{pid}")
|
||||
rec("GET /api/export/markdown", r.status_code == 200, f"HTTP {r.status_code} len={len(r.content)}")
|
||||
|
||||
# ── 8. RECHERCHE ──────────────────────────────────────
|
||||
r = client.get("/api/search", params={"q": "audit"})
|
||||
rec("GET /api/search?q=", r.status_code == 200, f"HTTP {r.status_code}")
|
||||
|
||||
# ── 9. AGENTS ─────────────────────────────────────────
|
||||
r = client.get("/api/agent")
|
||||
rec("GET /api/agent (agents)", r.status_code == 200, f"HTTP {r.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
rec("EXÉCUTION SCRIPT", False, str(e)[:100])
|
||||
|
||||
print("="*72)
|
||||
ok = sum(1 for _, o, _ in results if o)
|
||||
print(f"RÉSULTAT: {ok}/{len(results)} processus OK")
|
||||
Reference in New Issue
Block a user