- Table agent_connectors (migration 32) : colonnes plates name, url, secret_encrypted, enabled, status, detail — config_json remis (YAGNI, les scopes OAuth des phases 6-7 ajouteront le leur). - app/services/connectors.py : 1 fichier au lieu du package connectors/ — 3 natifs (gitea, github, web) servis à la volée avec statut sans réseau, CRUD des personnels (URL validée par _validate_url = garde SSRF, clé chiffrée Fernet et jamais renvoyée — seul has_secret), probe() qui persiste status/detail, connector_fetch() borné à 20 000 car. - API /api/agent/connectors : GET, POST (400 URL privée), PATCH, DELETE, POST /connectors/probe — 401 sans session, CSRF global. OpenAPI 519 chemins. - Outil LLM connector_fetch (26e outil) : un seul outil qui dispatche vers Gitea/GitHub/web/personnalisé (id ou nom ou kind + path + query) au lieu d'un outil par connecteur ; désactivé/inconnu = erreur outil, jamais de run cassé. - Menu + : section « Connecteurs » (catalogue avec badge ✓/✗/⚠/?), fiche par connecteur (Tester, Activer/Désactiver, Supprimer — masqués pour les natifs), formulaire « Ajouter un connecteur personnalisé » (clé en type=password). - Tests : tests/test_v755_connectors.py (13) — 3 natifs, roundtrip Fernet de la clé, 5 URLs refusées (localhost, 127.0.0.1, metadata cloud, ftp:, file:), toggle persisté, 401, probe OK/erreur, outil complet, câblage menu ; test_v751/v753/v754 adaptés (1 seule section « bientôt » = plugins). Suite complète 1306 verts (-n auto), ruff 0, eslint 0 problème.
74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
"""v7.53.0 — Menu + : Design System – Canevas (créer / insérer / enregistrer)."""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from app.db import get_conn
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
JS_PATH = ROOT / "static" / "js" / "agent_panel_2.js"
|
||
|
||
|
||
def test_canvases_list_includes_design_system(client):
|
||
tpl = {t["key"]: t for t in client.get("/board/api/page-templates").json()["templates"] if t.get("builtin")}
|
||
assert "design_system" in tpl
|
||
assert tpl["design_system"]["name"] == "Design System"
|
||
assert tpl["design_system"]["icon"] == "🎨"
|
||
|
||
|
||
def test_blocks_route_builtin(client):
|
||
r = client.get("/board/api/page-templates/0/blocks", params={"key": "design_system"})
|
||
assert r.status_code == 200, r.text
|
||
blocks = r.json()["blocks"]
|
||
assert isinstance(blocks, list) and blocks
|
||
assert blocks[0]["type"] == "heading_1" and blocks[0]["content"] == "Design System"
|
||
# les builtins sont livrés sans id : le front appelle ensureBlockIds()
|
||
assert "id" not in blocks[0]
|
||
|
||
assert client.get("/board/api/page-templates/0/blocks", params={"key": "nope"}).status_code == 404
|
||
empty = client.get("/board/api/page-templates/0/blocks", params={"key": "empty"})
|
||
assert empty.status_code == 200 and empty.json()["blocks"]
|
||
|
||
|
||
def test_blocks_route_user_template(client):
|
||
blocks = [{"type": "heading_1", "content": "Mon canevas"},
|
||
{"type": "paragraph", "content": "Contenu."}]
|
||
created = client.post("/board/api/page-templates", json={"name": "Mon canevas", "blocks": blocks})
|
||
assert created.status_code == 200, created.text
|
||
tid = created.json()["id"]
|
||
|
||
got = client.get(f"/board/api/page-templates/{tid}/blocks")
|
||
assert got.status_code == 200
|
||
assert [b["content"] for b in got.json()["blocks"]] == ["Mon canevas", "Contenu."]
|
||
|
||
assert client.get("/board/api/page-templates/999999/blocks").status_code == 404
|
||
|
||
|
||
def test_use_design_system_creates_page(client):
|
||
r = client.post("/board/api/page-templates/0/use", json={"key": "design_system"})
|
||
assert r.status_code == 200, r.text
|
||
page_id = r.json()["id"]
|
||
with get_conn() as conn:
|
||
row = conn.execute(
|
||
"SELECT content, content_format FROM pages WHERE id=?", (page_id,)
|
||
).fetchone()
|
||
assert row["content_format"] == "blocks"
|
||
blocks = json.loads(row["content"])
|
||
texts = [b.get("content", "") for b in blocks]
|
||
assert "Design System" in texts
|
||
# les ids sont assignés côté serveur (_ensure_block_ids)
|
||
assert all(b.get("id") for b in blocks)
|
||
|
||
|
||
def test_canvases_menu_wired(client):
|
||
src = JS_PATH.read_text(encoding="utf-8")
|
||
root = src.split("var FD_PLUS_MENU = [", 1)[1].split("];", 1)[0]
|
||
assert "action:'canvases'" in root
|
||
# phases 1-5 livrées → 1 section « bientôt » restante (plugins)
|
||
assert root.count("disabled:true") == 1
|
||
for action in ("'canvas'", "'canvas-create'", "'canvas-insert'", "'canvas-save'"):
|
||
assert action in src, action
|
||
# piège /use : la clé builtin part DANS LE BODY, pas en query string
|
||
assert "body: JSON.stringify(c.builtin ? {key: c.key} : {})" in src
|
||
assert "ensureBlockIds" in src
|