test: A32 phase 2g — dashboard +13 routes, cycles items/tags (v7.18.0)
FlowDeck CI / lint (push) Successful in 1m58s
FlowDeck CI / test (push) Successful in 14m55s
FlowDeck CI / docker (push) Canceled after 0s

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
This commit is contained in:
2026-10-01 15:11:45 -04:00
parent 2339fa2586
commit 0cb476e336
7 changed files with 154 additions and 6 deletions
+128
View File
@@ -596,6 +596,134 @@ def test_dashboard_settings_account_update(client):
conn.commit()
def test_dashboard_gitea_workspace_page(client):
r = client.get("/gitea-workspace")
assert r.status_code in (200, 302), r.status_code
if r.status_code == 200:
assert "text/html" in r.headers["content-type"]
def test_dashboard_workspace_projects_crud(client):
from app.db import get_conn
listing = client.get("/api/workspace/projects")
assert listing.status_code == 200
d = listing.json()
assert isinstance(d["builtin"], list) and isinstance(d["gitea"], list)
assert d["github"] == []
created = client.post("/api/workspace/projects", json={"name": "Projet A32"})
assert created.status_code == 200
body = created.json()
assert body["name"] == "Projet A32" and body["forge"] == "builtin"
try:
# la page créée est un projet racine → elle apparaît dans builtin
names = [p["name"] for p in client.get("/api/workspace/projects").json()["builtin"]]
assert "Projet A32" in names
vide = client.post("/api/workspace/projects", json={"name": " "})
assert "error" in vide.json() # quirk : 200 + message
finally:
with get_conn() as conn:
conn.execute("DELETE FROM pages WHERE id=?", (body["id"],))
conn.commit()
def test_dashboard_local_workspace_item_lifecycle(client):
"""POST → rename → move → soft-delete → restore (5 routes, vérifié en base)."""
from app.db import get_conn
created = client.post("/api/local-workspace/items", json={"name": "Fichier A32"})
assert created.status_code == 200
item = created.json()
iid = item["id"]
try:
assert item["name"] == "Fichier A32" and item["type"] == "page"
with get_conn() as conn:
row = conn.execute("SELECT title FROM pages WHERE id=?", (iid,)).fetchone()
assert row["title"] == "Fichier A32"
assert client.put(
f"/api/local-workspace/items/{iid}", json={"name": "Renommé A32"}
).json() == {"status": "ok"}
with get_conn() as conn:
row = conn.execute("SELECT title FROM pages WHERE id=?", (iid,)).fetchone()
assert row["title"] == "Renommé A32"
assert client.put(
f"/api/local-workspace/items/{iid}/move", json={"parent_id": None}
).json() == {"status": "ok"}
assert client.delete(f"/api/local-workspace/items/{iid}").json() == {"status": "ok"}
with get_conn() as conn:
row = conn.execute("SELECT deleted_at FROM pages WHERE id=?", (iid,)).fetchone()
assert row["deleted_at"] is not None
assert client.post(f"/api/local-workspace/items/{iid}/restore").json() == {
"status": "ok"
}
with get_conn() as conn:
row = conn.execute("SELECT deleted_at FROM pages WHERE id=?", (iid,)).fetchone()
assert row["deleted_at"] is None
finally:
with get_conn() as conn:
conn.execute("DELETE FROM pages WHERE id=?", (iid,))
conn.commit()
def test_dashboard_item_tags_lifecycle(client):
"""POST tag item → lecture → liste workspace → search → suppression.
L'utilisateur et son workspace sont créés DANS le test : /tags a besoin
d'un workspace actif (fallback « premier workspace du user ») et on ne
veut pas que ce workspace appartienne à l'utilisateur fixture partagé.
"""
from app.db import get_conn
reg = client.post(
"/auth/register",
json={"email": "[email protected]", "password": "secret123", "name": "taguser"},
)
assert reg.status_code == 200, reg.text
uid = client.get("/api/users/me").json()["id"]
with get_conn() as conn:
cur = conn.execute(
"INSERT INTO workspaces (name, owner_id) VALUES ('Ws A32', ?)", (uid,)
)
ws_id = cur.lastrowid
conn.commit()
iid = _seed_page("Item taggé A32")
tag_id = None
try:
created = client.post(
f"/api/local-workspace/items/{iid}/tags", json={"name": "UrgentA32"}
)
assert created.status_code == 200
tag_id = created.json()["tag"]["id"]
assert created.json()["tag"]["name"] == "urgenta32" # lowercasé
item_tags = client.get(f"/api/local-workspace/items/{iid}/tags").json()["tags"]
assert any(t["id"] == tag_id for t in item_tags)
all_tags = client.get("/api/local-workspace/tags")
assert all_tags.status_code == 200
assert any(t["id"] == tag_id for t in all_tags.json()["tags"])
search = client.get("/api/local-workspace/tags/search", params={"tags": "urgentA32"})
assert search.status_code == 200 and isinstance(search.json()["items"], list)
assert client.delete(
f"/api/local-workspace/items/{iid}/tags/{tag_id}"
).json() == {"status": "ok"}
after = client.get(f"/api/local-workspace/items/{iid}/tags").json()["tags"]
assert all(t["id"] != tag_id for t in after)
finally:
with get_conn() as conn:
if tag_id is not None:
conn.execute("DELETE FROM tags WHERE id=?", (tag_id,))
conn.execute("DELETE FROM pages WHERE id=?", (iid,))
conn.execute("DELETE FROM workspaces WHERE id=?", (ws_id,))
conn.commit()
def test_dashboard_files_traversal_denied_and_serve(client):
"""A16 : /api/files refuse le traversal et sert les vrais fichiers."""
import pathlib as _pathlib