Files
flowdeck/tests/test_v751_plus_menu.py
T
bruno a68e8848ea
FlowDeck CI / lint (push) Successful in 2m12s
FlowDeck CI / test (push) Failing after 14m56s
FlowDeck CI / docker (push) Skipped
feat: menu + — Design System – Canevas — phase 3/8 (v7.53.0)
- Section « Design System – Canevas » : liste des canevas (intégrés +
  personnels, GET /board/api/page-templates) puis page de détail avec
  « Créer une page à partir du canevas », « Insérer dans le document ouvert »
  (désactivé sans document ouvert) et « Enregistrer le document ouvert comme
  canevas ».
- GET /board/api/page-templates/{id}/blocks (nouvelle route) : blocs d'un
  canevas — intégré via template_id=0&key=, sinon id ; 404 clé/id inconnu.
  Insertion côté front via ensureBlockIds() global + concat dans E.blocks
  + autoSave/render.
- Canevas « Design System » ajouté aux builtins (callout tokens, TOC, toggles
  composants, grille, checklist revue UI, citation).
- Fix : le front du panneau appelait /api/page-templates (404 silencieux) —
  le router est sous prefix=/board → /board/api/page-templates.
- Piège : pour /use la clé builtin se passe DANS LE BODY (body.key), pas en
  query string — test garde-fou ajouté.
- Tests : tests/test_v753_canvases.py (5) + test_v751 adapté. Suite complète
  1285 verts (-n auto), ruff 0, eslint 0 problème. OpenAPI 516 chemins.
2026-10-06 21:59:34 -04:00

128 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""v7.51.0 — Menu + de l'assistant : sections, parcours fichiers/répertoires,
jeton ``folder:<id>`` résolu par ContextBuilder."""
from pathlib import Path
from app.db import get_conn
from app.services.context_builder import ContextBuilder
ROOT = Path(__file__).resolve().parent.parent
JS_PATH = ROOT / "static" / "js" / "agent_panel_2.js"
def _uid() -> int:
with get_conn() as conn:
row = conn.execute("SELECT id FROM users LIMIT 1").fetchone()
return row["id"] if row else 1
def _folder_with_children(n_children: int = 2, body: str = "Contenu enfant.") -> int:
with get_conn() as conn:
cur = conn.execute(
"INSERT INTO pages (workspace, title, content, content_format, parent_section) "
"VALUES ('ws', 'Dossier Specs', '', 'blocks', 'Workspace')"
)
fid = cur.lastrowid
for i in range(n_children):
conn.execute(
"INSERT INTO pages (workspace, title, content, content_format, "
"parent_section, parent_id) VALUES ('ws', ?, ?, 'markdown', 'Private', ?)",
(f"Spec {i}", body * 10 if i else body, fid),
)
conn.commit()
return fid
def test_folder_mention_resolves_children(client):
"""folder:<id> = titre du dossier + contenu de ses documents directs."""
fid = _folder_with_children()
ctx = ContextBuilder(_uid()).build(mentions=[f"folder:{fid}"])
assert "Dossier Specs" in ctx
assert "Contenu enfant." in ctx
def test_folder_mention_truncates_on_budget(client):
"""Le contenu d'un dossier est borné (~12k) : un gros dossier n'explose pas le prompt."""
fid = _folder_with_children(n_children=12, body="X" * 800)
ctx = ContextBuilder(_uid()).build(mentions=[f"folder:{fid}"])
assert "tronqué" in ctx
assert len(ctx) < 20000
def test_folder_mention_unknown_id(client):
ctx = ContextBuilder(_uid()).build(mentions=["folder:999999"])
assert "introuvable" in ctx
def test_browse_contract_nav_menu(client):
"""Contrat que le parcours du menu + consomme : dossier = icon 'folder',
un niveau par appel via parent_id."""
with get_conn() as conn:
conn.execute("INSERT OR IGNORE INTO users (id, login) VALUES (1, 'tester')")
cur = conn.execute("INSERT INTO workspaces (name, owner_id) VALUES ('BrowseWS', 1)")
ws_id = cur.lastrowid
cur = conn.execute(
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, "
"parent_section, parent_id) VALUES ('BrowseWS', ?, 'Dossier A', '', 'blocks', "
"'Workspace', NULL)",
(ws_id,),
)
folder_id = cur.lastrowid
conn.execute(
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, "
"parent_section, parent_id) VALUES ('BrowseWS', ?, 'Note enfant', '', 'markdown', "
"'Private', ?)",
(ws_id, folder_id),
)
conn.commit()
root = client.get(f"/api/nav/menu?workspace_id={ws_id}").json()["items"]
names = {i["name"]: i for i in root}
assert names["Dossier A"]["icon"] == "folder"
kids = client.get(f"/api/nav/menu?workspace_id={ws_id}&parent_id={folder_id}").json()["items"]
kid = {i["name"]: i for i in kids}["Note enfant"]
assert kid["icon"] != "folder"
assert kid["url"] == f"/pages/{kid['id']}"
def test_agent_panel_renders_plus_menu(client):
"""La page rend bien le menu + (markup + bouton)."""
resp = client.get("/accounts")
assert resp.status_code == 200
html = resp.text
assert "fd-ap-plus-menu" in html
assert "fd-ap-add" in html
assert "Menu d'ajout au contexte" in html
def test_plus_menu_sections_and_disabled_states():
"""Les 6 sections du menu + existent ; seules les sections déjà livrées sont
actives (le menu ne promet rien que le code ne fait)."""
src = JS_PATH.read_text(encoding="utf-8")
root = src.split("var FD_PLUS_MENU = [", 1)[1].split("];", 1)[0]
for key in ("'files'", "'skills'", "'connectors'", "'canvases'", "'plugins'", "'memory'"):
assert f"key:{key}" in root, key
assert "action:'files'" in root and "action:'skills'" in root
# phases 1-3 livrées → il reste 3 sections « bientôt » (connecteurs, plugins, mémoire)
assert root.count("disabled:true") == 3
files = src.split("var FD_PLUS_FILES = [", 1)[1].split("];", 1)[0]
assert "action:'search'" in files and "action:'browse'" in files
assert "folder:" in src and "document:" in src
def test_folder_chip_token_reaches_run_payload():
"""Le jeton folder:<id> épinglé part dans payload.mentions (contrat front→agent)."""
src = JS_PATH.read_text(encoding="utf-8")
# composerMentions() pioche dans contextChips[].token : le parcours doit
# produire des items avec token folder:/document:.
assert "token: 'folder:' + cur.id" in src
assert "token: (isFolder ? 'folder:' : 'document:') + x.id" in src
# ... et le backend sait les résoudre.
cb = (ROOT / "app" / "services" / "context_builder.py").read_text(
encoding="utf-8"
)
assert 'm.startswith("folder:")' in cb
assert "def _single_folder" in cb