7.77.0 — corbeille réparée (FK favorites/recents) + MCP workspaces
FlowDeck CI / lint (push) Failing after 1m28s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 2h18m54s

- detach_page_references : nettoyage des références NO ACTION avant
  toute suppression définitive (unitaire, Empty Trash, purge 30 j)
- MCP : list_workspaces / get_workspace ; descriptions collections
  clarifiées (bases de données, pas des workspaces)
- 96 tests verts sur le périmètre (MCP, trash, cohérence)
This commit is contained in:
Spark
2026-10-22 09:03:43 -04:00
parent 215990adc0
commit f6782ee5b3
8 changed files with 141 additions and 7 deletions
+26
View File
@@ -32,6 +32,32 @@ def _row(page_id):
).fetchone()
def test_permanent_delete_page_referenced_by_favorites_and_recents(client):
"""Régression 7.77.0 (cas réel de Bruno, 2026-10-22) : une page
réelle — ouverte (recents) et/ou favorite — ne pouvait plus être
supprimée définitivement : favorites.page_id et recents.page_id
référencent pages(id) sans CASCADE, PRAGMA foreign_keys=ON →
IntegrityError → 500 « Delete failed », et Empty Trash (une seule
transaction) échouait en bloc. Les pages synthétiques des anciens
tests n'avaient ni favori ni récent : elles passaient."""
from app.db import get_conn
login_test_client(client)
pid = _mk_page(title="Page réelle de Bruno")
with get_conn() as conn:
conn.execute("INSERT INTO favorites (user_id, page_id, position) VALUES (1, ?, 0)", (pid,))
conn.execute("INSERT INTO recents (user_id, page_id, workspace) VALUES (1, ?, 'x')", (pid,))
conn.commit()
r = client.post(f"/api/pages/{pid}/trash")
assert r.status_code == 200
r = client.delete(f"/board/api/trash/{pid}")
assert r.status_code == 200, r.text
assert _row(pid) is None
with get_conn() as conn:
assert conn.execute("SELECT COUNT(*) FROM favorites WHERE page_id=?", (pid,)).fetchone()[0] == 0
assert conn.execute("SELECT COUNT(*) FROM recents WHERE page_id=?", (pid,)).fetchone()[0] == 0
def test_trash_requires_session(client):
pid = _mk_page()
anon_csrf(client) # anonyme mais CSRF valide → on veut le 401, pas le 403