main
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1d1cdbd618 |
fix: side peek des bases repasse en vanilla JS + largeur 1100px standard (v7.49.0)
- Panneau peek: les bindings Alpine (x-data absent du conteneur) rendaient loovverture et le redimensionnement inoperants -> cblage direct sur le document. - Helper unique window.fdWirePeekResize (app.js): pointer capture, 300px-90vw, clic=fermer, largeur persiste fd_peek_width partagee entre les 4 peeks. - database-table-container margin:0 (tableau colle a gauche, marge Library). - .lib-container remonte dans app.css (trash etait pleine largeur), .db-index 1100px. - ObsiGate verifie sans code: creation .xlsx OK (openpyxl, #186). |
||
|
|
6582df3bcb |
feat: poubelle flexible (sélection groupée, purge, tri/filtres, dates) + 4 bugs (v7.45.5)
Page /trash alignée sur ce que fait ce type de section :
- sélection multiple (Select all porté au filtre courant) + barre d'actions
groupées Restore / Delete / Clear ;
- Empty Trash via POST /board/api/trash/empty (purge en masse, 1 requête) ;
- tri Recently deleted / Oldest first / Name et filtre emplacement RÉELS —
remplacent les deux boutons décoratifs « Last edited by ▾ » et « In ▾ » qui
ne faisaient rien ;
- date de suppression + jours restants par élément (rétention 30 j alignée sur
app/services/trash.py, horodatages UTC/ISO gérés) ;
- compteur de résultats, états vides distincts (vide vs filtre sans résultat +
Clear filters), toasts sur chaque action.
Bugs trouvés en route et corrigés :
1. Restore/Delete de la page ne marchaient JAMAIS : getCsrf() renvoie la chaîne
du jeton mais le code faisait csrf?.[1] → 2e caractère → 403 CSRF silencieux
avalé par if (r.ok).
2. « Move to Trash » de l'éditeur = 404 permanent : route appelée
/board/api/pages/{id}/trash (inexistante) alors que la route réelle est
/api/pages/{id}/trash, et le .then() naviguait quand même → la page partait
à l'accueil SANS être mise à la poubelle. URL corrigée + r.ok vérifié.
3. Page restaurée invisible en Library/Recents/Private : la suppression éditeur
réécrivait parent_section='Trash' et la restauration ne le remettait pas,
alors que tous les listings filtrent parent_section != 'Trash'. Écriture
retirée (deleted_at = source de vérité unique) + réparation idempotente au
boot dans db.init_db + requête sidebar /trash alignée sur deleted_at.
4. Routes trash sans aucune authentification (le CSRF ne protège pas : cookie
lisible + en-tête forgé) : 401 ajouté sur list/restore/delete/empty et sur
POST /api/pages/{id}/trash — vérifié anonyme → 401.
Gates : tests/test_trash_api.py (5) · e2e/trash_ui.spec.js (1, avec garde « on
ne vide jamais la poubelle d'autrui ») · pytest 1099 passed · ruff OK ·
e2e probe_nav_perf + partial_nav + editor_mount + logout_dnd + smoke = 10 passed ·
OpenAPI 510 chemins / 7.45.5.
|
||
|
|
7a38ddd0f6 |
test: A32 TERMINÉ — 6 routes Gitea stubbées + bug prod fd_icon (v7.20.0)
Les 6 dernières routes d'A32 (api.py, gitea) avec stub de transport — zéro
réseau réel :
- _stub_gitea() : stubs manuels sur gitea_client.gitea (create_issue,
update_issue, update_issue_labels, get_issue, get_issue_comments) avec
ÉTAT MUTABLE PARTAGÉ — le handler PATCH re-fetch l'issue via get_issue,
un canevas figé aurait masqué la mise à jour.
- POST /issues : carte INSÉRÉE sur le board (board seedé par endpoint) ;
PATCH : colonne recalculée sans perdre la carte.
- GET /issues JSON + HTML : ?format=html requis (le segment /html ne fixe pas
le paramètre, le handler le lit dans la query) ; stub qui lève → 404.
- POST /checklists + POST /checklist-items : lignes vérifiées en base,
404 sans board ; cleanup (items → checklists).
BUG PROD corrigé (trouvé par le smoke HTML) : card_detail.html utilisait la
macro fd_icon SANS l'importer → UndefinedError → 500 systématique sur
GET /api/issues/...?format=html (seul rendu du template dans le code).
Fix : {% from '_icons.html' import fd_icon %}.
A32 COMPLET : plus aucun router « 0 test » (webhooks, notes, sidebar_config,
github_routes, library, api, dashboard, api_v2 tous couverts).
test_smoke_uncovered.py : 52 tests. suite **1089/1089** · ruff OK · docs à jour
|
||
|
|
113374e499 |
test: A32 phase 2h — dashboard bloqué : 44/44 routes à 0 ref (v7.19.0)
+5 routes dashboard (fichier test_smoke_uncovered.py à 49 tests) :
- Members POST/PUT/DELETE : invitation de soi-même dans un workspace dédié
(_own_workspace), rôle admin relu en base, membre supprimé (COUNT=0).
Quirk documenté : les retours tuple des routes (`{"error": ...}, 400`)
sont sérialisés FastAPI en tableau + 200 → assert sur `[0]["error"]`.
- upload-folder : validations SEULES (structure absente → 400 « No
structure provided », JSON cassé → 400 « Invalid structure JSON ») —
zéro fichier écrit, workspace dédié nettoyé.
- convert-to-database : collection + propriété title + vue table + page en
content_format='collection' VÉRIFIÉS en base, 404 page inconnue,
cleanup dans l'ordre FK (pages avant collections — IntegrityError corrigée).
Recoupement final : scan des 44 routes strictement à 0 ref de dashboard.py →
TOUTES exercées. Les 19 résidus du scan sont des faux positifs (paths en
f-string dans les tests : /api/workspace/1/…, f"/api/pages/{id}/…", …)
rapprochés manuellement un par un.
Reste A32 : les 6 routes Gitea d'api.py (issues ×4, créations checklists)
→ stub de transport httpx (effort S).
suite **1086/1086** · `ruff check app tests` OK · docs à jour
|
||
|
|
0cb476e336 |
test: A32 phase 2g — dashboard +13 routes, cycles items/tags (v7.18.0)
Cumul dashboard : 43 → 56 des 63 routes. 4 nouveaux tests (fichier à 46) :
- GET /gitea-workspace : page HTML (200 ou redirection propre)
- workspace/projects GET+POST : shape {builtin, gitea, github} avec
github == [] ; projet créé RETROUVÉ dans builtin ; quirk « error » sans nom
; nettoyage (DELETE page)
- Cycle items local-workspace (5 routes) : POST création (titre relu),
PUT rename (relu en base), PUT move, DELETE soft-delete (deleted_at relu),
POST restore (deleted_at NULL relu) — nettoyage finally
- Cycle tags d'item (5 routes) : POST (urgenta32 lowercasé), tags de l'item,
liste workspace, search (shape), suppression vérifiée. Utilisateur DÉDIÉ +
workspace créé dans le test (le endpoint /api/local-workspace/tags exige un
workspace actif : fallback « premier workspace du user » — on n'attache pas
ce workspace à l'utilisateur fixture partagé), tout est nettoyé.
Reste A32 : dashboard 7 routes (members invite/role/unsubscribe,
upload-folder, convert-to-database) + 6 routes Gitea d'api.py (stub httpx).
suite **1083/1083** · `ruff check app tests` OK · docs à jour
|
||
|
|
2339fa2586 |
test: A32 phase 2f — dashboard +7 routes, garde-fous A16 (v7.17.0)
Cumul dashboard : 36 → 43 des 63 routes. 4 nouveaux tests (fichier à 42),
centrés sur les garde-fous A16 :
- GET /api/files/{ws}/{path} : traversal encodé %2e%2e%2f → 403
« Path traversal denied » (décodé par Starlette puis bloqué par resolve) ;
inexistant → 404 ; vrai fichier écrit dans le data_dir de test →
200 + octets exacts, nettoyé en finally
- GET /api/pages/{id}/download : page markdown → 404 « No downloadable file »
(pas de 500) ; page « file » avec chemin ../ sortant de la racine →
jamais 200 ; file-content → 404/415 sans fuite
- GET /api/local-workspace/page-content/{id} : contenu + format relus,
404 sur id inconnu
- GET /api/avatar/{id} : 302 + Location avec follow_redirects=False
(AUCUNE requête réelle vers l'URL externe), 404 sans avatar
- GET/POST /api/collections/{id}/table-data|pages : 404 inconnu, shape,
ligne créée retrouvée dans table-data, nettoyage finally
Reste A32 : dashboard 20 routes (upload/local-workspace items/members/
projects/HTML gitea) + 6 routes Gitea d'api.py (stub transport httpx).
suite **1079/1079** · `ruff check app tests` OK · docs à jour
|
||
|
|
8b48dbdd4b |
test: A32 phase 2e — dashboard +9 routes, comptes A2/A3 (v7.16.0)
Cumul dashboard : 27 → 36 des 63 routes. 6 nouveaux tests (fichier à 38) :
- /accounts + /accounts/settings : 200 HTML et « password_hash » ABSENT du
rendu (whitelist A2 vérifiée côté page)
- PUT /api/user/profile : persistance relue en base, restauration finally
- PUT /api/user/password : 403 « current password is incorrect » (A3 — la
session seule ne change pas le mdp) + quirk assumé documenté : la longueur
est validée AVANT l'auth et répond 200 + message
- POST /api/user/token : format fd_ + 64 hex ; ligne user_tokens nettoyée
- DELETE /api/user/forge/{provider} : {"status": "ok"}
- PUT /api/settings/account : full_name/email persistés + 400 sur mdp court,
restauration finally
- POST /api/workspaces/1/select : Set-Cookie flowdeck_workspace vérifié ;
GET /api/local-workspace/breadcrumb : shape liste
Reste A32 : dashboard 27 routes (fichiers/avatars/local-workspace/collections)
+ 6 routes Gitea d'api.py (stub transport httpx).
suite **1075/1075** · `ruff check app tests` OK · docs à jour
|
||
|
|
360c705fd4 |
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
|
||
|
|
0698645dbd |
test: A32 phase 2c — api_v2 : les 5 routes à 0 ref couvertes (v7.14.0)
Scan strict des 115 routes api_v2.py contre tous les tests (chaîne de chemin littérale) → 5 routes sans AUCUNE référence, toutes couvertes maintenant : - POST /properties/evaluate-formula : 200 + shape, 400 sans expression. Le moteur renvoie « 1 + 2 » tel quel aujourd'hui → le smoke valide le câble (bearer, Body param, parse), pas le moteur (réalm de ses propres tests). - POST /properties/compute-rollup : 400 « collection_id required », 401 sans bearer. - GET /admin/audit-logs : portail admin VÉRIFIÉ — l'attendu est calculé depuis /users/me (le tout premier utilisateur d'un worker est admin : état non contrôlable depuis un test isolé), + token scope admin → 200 + logs. - GET /webhooks/events : catalogue non vide + wildcards * / page.*. - POST /webhooks/verify-signature : valid=True avec sign_payload() (le même helper que le serveur), False avec signature bidon. test_smoke_uncovered.py : 27 tests. Reste A32 : dashboard 17/63 + 6 routes gitea d'api.py (stub transport). suite **1064/1064** · `ruff check app tests` OK · docs à jour |
||
|
|
b2e38aece7 |
test: A32 phase 2b — api.py 3 → 16/22 routes couvertes (v7.13.0)
+6 smokes dans test_smoke_uncovered.py (22 tests au total dans le fichier) : - board-config GET/POST : défauts 5 colonnes sans board, création puis relecture du roundtrip (seed via l'endpoint lui-même, pas de SQL brut) - col-mapping POST/DELETE : 404 sans board, upsert label vérifié, suppression - card POST : 404 sans board, ok avec - collaborators GET : gitea.get_collaborators STUBBÉ (zéro accès réseau réel) - frontend-error(s) : capture, JSON invalide → ignored, DÉDUP d'une erreur répétée (count=2), lecture qui purge (cleared=true puis 0) - checklist mutations : PATCH item (checked/content relus EN BASE), DELETE item, DELETE checklist (COUNT=0) — seed + cleanup en finally Reste api.py : 6 routes Gitea (issues ×4 + créations checklists owner/repo) → stub de transport httpx (phase suivante). Reste global : dashboard 17/63, api_v2 50/115. suite **1059/1059** (236 s) · `ruff check app tests` OK · docs à jour |
||
|
|
df9a269d76 |
test: A32 phase 2a — library 10/10 + 2 routes fantômes supprimées (v7.12.0)
- tests/test_smoke_uncovered.py : +6 tests pour library.py (1/10 → 8 routes
couvertes) : les 5 listes en boucle (recents/favorites/published/private/
workspace → 200 + items), /private avec page seedée retrouvée, /children/{id}
avec parent/enfant seedés, /repository vide ET clé (string, aucun réseau),
non-régression 404 sur les routes supprimées.
- DÉCOUVERTE (les smokes l'ont prouvé) : `/api/library/local-workspace-children`
renvoyait un 500 systématique (test vert → ASGI double response.start) et
`/api/library/local-workspace` 500 dès qu'un workspace existe — les deux
lisaient `local_workspace_items`, table AUCUNEMENT créée dans le codebase
(0 CREATE TABLE) et sans 1 seule référence front. Supprimés avec
`library._format_size` devenu mort (la copie de dashboard.py est inchangée).
`local_workspace_items` : 0 occurrence restante dans app/.
- helper `_seed_page` minimal (workspace NOT NULL inclus) + nettoyage en
finally (pas de pollution des autres tests).
suite **1053/1053** (229 s) · `ruff check app tests` OK · docs à jour
restent phase 2b : api.py 3/23, dashboard.py 17/63, api_v2.py 50/115
|
||
|
|
da7326ffde |
test: A32 phase 1 — 4 routers à 0 test couverts (10 smokes) (v7.11.0)
tests/test_smoke_uncovered.py — un smoke par route des 4 routers qui n'avaient
AUCUN test :
- webhooks.py 3/3 : réception sans secret → {"status":"ok"} ; HMAC faux → 401
(secret piloté par monkeypatch, déterministe quel que soit le .env) ;
register sans secret → 400 AVANT tout appel réseau ; status avec
gitea.list_webhooks stubbé → {"registered": False} (zéro réseau réel)
- notes.py 2/2 : GET HTML + roundtrip POST→GET (upsert persisté en base,
échappement HTML vérifié : <b> et non <b>)
- sidebar_config.py 2/2 : GET défauts ; PUT persisté puis RELU depuis
users.sidebar_config ; 400 sans config ; remise en état en fin de test
- github_routes.py 2/2 : status {"linked": False} ; disconnect {"status": "ok"}
Reste (A32 phase 2) : quasi nuls — library 1/10, api 3/23, dashboard 17/63,
api_v2 50/115 → même recette, fixture client existante.
suite **1047/1047** · `ruff check app tests` OK · docs à jour
|