test: A32 phase 2d — dashboard +10 routes couvertes (v7.15.0)
Scan strict dashboard.py : 44 routes à 0 référence stricte sur 63. 10
couvertes cette passe (test_smoke_uncovered.py → 32 tests) :
- Tags CRUD complet : POST (nom lowercasé `SmokeTag` → `smoketag`), GET all
(présent), PUT couleur (relue), DELETE (absente de la liste ensuite)
- Vie d'une page : GET /api/pages/{id}/content (contenu seedé relu) →
PUT rename (ok + **400 titre vide** + titre relu en base) →
POST trash (parent_section='Trash' + deleted_at RELUS en base) ;
nettoyage en finally
- GET /api/sidebar/workspace-tree : 200 HTML, fragment « No pages yet »
(pas de cookie workspace)
- POST /api/settings/avatar-color : couleur relue SUR L'UTILISATEUR DE LA
SESSION (pas LIMIT 1), avatar_color/avatar_url d'origine restaurés
- GET /api/workspace/1/members : shape {"members": [...]}
Helper _seed_page : les colonnes par défaut sont surchargeables (content=)
pour les seeds à contenu.
Reste A32 : dashboard 34 routes à 0 ref (fichiers/avatars/imports…) +
6 routes Gitea d'api.py (stub transport httpx).
suite **1069/1069** · `ruff check app tests` OK · docs à jour
This commit is contained in:
@@ -1,5 +1,23 @@
|
|||||||
# Changelog - FlowDeck
|
# Changelog - FlowDeck
|
||||||
|
|
||||||
|
## v7.15.0 (2026-10-01) — Audit : A32 phase 2d (dashboard +10 routes)
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Scan strict `dashboard.py` : **44 routes à 0 référence** (sur 63) — 10
|
||||||
|
couvertes cette passe dans `test_smoke_uncovered.py` (32 tests au fichier) :
|
||||||
|
· **Tags CRUD** : création (`SmokeTag` → `smoketag` lowercasé), présence
|
||||||
|
dans la liste, changement de couleur relu, suppression puis absence
|
||||||
|
· **Vie d'une page** : GET `content` (contenu seedé relu) → PUT `rename`
|
||||||
|
(ok + **400 sur titre vide** + titre relu en base) → POST `trash`
|
||||||
|
(`parent_section='Trash'` + `deleted_at` **relus en base**)
|
||||||
|
· `sidebar/workspace-tree` : 200 HTML, fragment « No pages yet » sans cookie
|
||||||
|
· `settings/avatar-color` : couleur relue **sur l'utilisateur de la
|
||||||
|
session** (pas `LIMIT 1`), valeurs d'origine restaurées en `finally`
|
||||||
|
· `workspace/{id}/members` : shape `{"members": [...]}`
|
||||||
|
- Helper `_seed_page` : surcharge des colonnes par défaut (`content=`, …)
|
||||||
|
- Suite complète : **1069/1069**
|
||||||
|
|
||||||
## v7.14.0 (2026-10-01) — Audit : A32 phase 2c (api_v2 +5 routes)
|
## v7.14.0 (2026-10-01) — Audit : A32 phase 2c (api_v2 +5 routes)
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|||||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# WORKLOAD — FlowDeck Notion Clone
|
# WORKLOAD — FlowDeck Notion Clone
|
||||||
|
|
||||||
> **Début**: 2026-07-08 | **Version**: v7.14.0 (audit — A32 phase 2c : api_v2 +5 routes à 0 ref) | **Statut**: EN COURS 🔄
|
> **Début**: 2026-07-08 | **Version**: v7.15.0 (audit — A32 phase 2d : dashboard +10 routes) | **Statut**: EN COURS 🔄
|
||||||
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
|
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
|
||||||
|
|
||||||
## Avancement Global
|
## Avancement Global
|
||||||
|
|||||||
+1
-1
@@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="FlowDeck",
|
title="FlowDeck",
|
||||||
version="7.14.0",
|
version="7.15.0",
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "FlowDeck",
|
"title": "FlowDeck",
|
||||||
"version": "7.14.0"
|
"version": "7.15.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/auth/register": {
|
"/auth/register": {
|
||||||
|
|||||||
@@ -116,9 +116,11 @@ def _seed_page(title: str, **cols) -> int:
|
|||||||
"""Insert minimale (title/content/format + colonnes surnuméraires), id renvoyé."""
|
"""Insert minimale (title/content/format + colonnes surnuméraires), id renvoyé."""
|
||||||
from app.db import get_conn
|
from app.db import get_conn
|
||||||
|
|
||||||
columns = ["workspace", "title", "content", "content_format", *cols]
|
base = {"workspace": "", "title": title, "content": "", "content_format": "markdown"}
|
||||||
|
base.update(cols) # surcharge (content=, parent_id=, parent_section=…)
|
||||||
|
columns = list(base)
|
||||||
placeholders = ", ".join("?" for _ in columns)
|
placeholders = ", ".join("?" for _ in columns)
|
||||||
values = ["", title, "", "markdown", *cols.values()]
|
values = list(base.values())
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
cur = conn.execute(
|
cur = conn.execute(
|
||||||
f"INSERT INTO pages ({', '.join(columns)}) VALUES ({placeholders})", values
|
f"INSERT INTO pages ({', '.join(columns)}) VALUES ({placeholders})", values
|
||||||
@@ -402,3 +404,99 @@ def test_v2_webhook_verify_signature(client):
|
|||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
assert bad.json()["valid"] is False
|
assert bad.json()["valid"] is False
|
||||||
|
|
||||||
|
|
||||||
|
# ── dashboard.py (17/63 → 27/63) ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_tags_crud(client):
|
||||||
|
"""POST/GET/PUT/DELETE /api/settings/tags — cycle complet vérifié."""
|
||||||
|
r = client.post("/api/settings/tags", json={"name": "SmokeTag", "color": "#111111"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
tag = r.json()["tag"]
|
||||||
|
assert tag["name"] == "smoketag" and tag["color"] == "#111111" # lowercasé
|
||||||
|
|
||||||
|
allr = client.get("/api/settings/tags/all").json()["tags"]
|
||||||
|
assert any(t["id"] == tag["id"] for t in allr)
|
||||||
|
|
||||||
|
assert client.put(
|
||||||
|
f"/api/settings/tags/{tag['id']}", json={"color": "#222222"}
|
||||||
|
).json() == {"status": "ok"}
|
||||||
|
allr2 = client.get("/api/settings/tags/all").json()["tags"]
|
||||||
|
mine = next(t for t in allr2 if t["id"] == tag["id"])
|
||||||
|
assert mine["color"] == "#222222"
|
||||||
|
|
||||||
|
assert client.delete(f"/api/settings/tags/{tag['id']}").json() == {"status": "ok"}
|
||||||
|
assert all(t["id"] != tag["id"] for t in client.get("/api/settings/tags/all").json()["tags"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_page_content_rename_trash(client):
|
||||||
|
"""GET content → PUT rename → POST trash : la vie d'une page vérifiée en base."""
|
||||||
|
from app.db import get_conn
|
||||||
|
|
||||||
|
pid = _seed_page("Page A32", content="contenu a32")
|
||||||
|
try:
|
||||||
|
r = client.get(f"/api/pages/{pid}/content")
|
||||||
|
assert r.status_code == 200
|
||||||
|
d = r.json()
|
||||||
|
assert d["title"] == "Page A32" and d["content"] == "contenu a32"
|
||||||
|
|
||||||
|
rr = client.put(f"/api/pages/{pid}/rename", json={"title": "Renommée A32"})
|
||||||
|
assert rr.status_code == 200 and rr.json() == {"status": "ok", "title": "Renommée A32"}
|
||||||
|
empty = client.put(f"/api/pages/{pid}/rename", json={"title": " "})
|
||||||
|
assert empty.status_code == 400 # titre vide refusé
|
||||||
|
|
||||||
|
rt = client.post(f"/api/pages/{pid}/trash")
|
||||||
|
assert rt.status_code == 200 and rt.json() == {"status": "ok"}
|
||||||
|
with get_conn() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT title, parent_section, deleted_at FROM pages WHERE id=?", (pid,)
|
||||||
|
).fetchone()
|
||||||
|
assert row["title"] == "Renommée A32"
|
||||||
|
assert row["parent_section"] == "Trash" and row["deleted_at"] is not None
|
||||||
|
finally:
|
||||||
|
with get_conn() as conn:
|
||||||
|
conn.execute("DELETE FROM pages WHERE id=?", (pid,))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_sidebar_workspace_tree(client):
|
||||||
|
r = client.get("/api/sidebar/workspace-tree")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "text/html" in r.headers["content-type"]
|
||||||
|
# pas de cookie workspace → fragment « No pages yet »
|
||||||
|
assert "No pages yet" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_avatar_color_persisted(client):
|
||||||
|
"""POST avatar-color → couleur relue SUR L'UTILISATEUR DE LA SESSION."""
|
||||||
|
from app.db import get_conn
|
||||||
|
|
||||||
|
uid = client.get("/api/users/me").json()["id"]
|
||||||
|
with get_conn() as conn:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT avatar_color, avatar_url FROM users WHERE id=?", (uid,)
|
||||||
|
).fetchone()
|
||||||
|
prev_color, prev_url = row["avatar_color"], row["avatar_url"]
|
||||||
|
try:
|
||||||
|
r = client.post("/api/settings/avatar-color", json={"color": "#112233"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json() == {"status": "ok", "color": "#112233"}
|
||||||
|
with get_conn() as conn:
|
||||||
|
row2 = conn.execute(
|
||||||
|
"SELECT avatar_color, avatar_url FROM users WHERE id=?", (uid,)
|
||||||
|
).fetchone()
|
||||||
|
assert row2["avatar_color"] == "#112233" and row2["avatar_url"] == ""
|
||||||
|
finally:
|
||||||
|
with get_conn() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE users SET avatar_color=?, avatar_url=? WHERE id=?",
|
||||||
|
(prev_color, prev_url, uid),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_workspace_members_list(client):
|
||||||
|
r = client.get("/api/workspace/1/members")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert isinstance(r.json()["members"], list)
|
||||||
|
|||||||
Reference in New Issue
Block a user