feat(v2.1.0): API publique + Webhooks sortants + PWA
- Public API: /api/v1 (collections, pages, my-tasks) with token auth
- Token generation: POST /api/v1/token
- Outbound webhooks: /workspace/webhooks CRUD + fire_event dispatcher
- PWA: manifest.json endpoint
- CSRF exempt for /api/v1
- 73/73 tests passent (+6 tests v2.1)
- Version 2.0.0 → 2.1.0
- Docs: ROADMAP updated — 7/7 blocs, 52/52 features ✅
This commit is contained in:
+66
-8
@@ -34,7 +34,7 @@ def test_health(client):
|
||||
data = resp.json()
|
||||
assert "status" in data
|
||||
assert data["db"] is True
|
||||
assert data["version"] == "2.0.0"
|
||||
assert data["version"] == "2.1.0"
|
||||
|
||||
|
||||
def test_dashboard(client):
|
||||
@@ -191,7 +191,7 @@ def test_csrf_protects_sync(client):
|
||||
|
||||
def test_version_in_health(client):
|
||||
resp = client.get("/api/health")
|
||||
assert resp.json()["version"] == "2.0.0"
|
||||
assert resp.json()["version"] == "2.1.0"
|
||||
|
||||
|
||||
def test_db_tables_exist(client):
|
||||
@@ -361,7 +361,7 @@ def test_boards_as_collections(client):
|
||||
assert isinstance(data["boards"], list)
|
||||
|
||||
|
||||
# ── v2.0.0: Collection Properties ──
|
||||
# ── v2.1.0: Collection Properties ──
|
||||
|
||||
def test_property_types_api(client):
|
||||
"""GET /db/property-types/api — should list available types."""
|
||||
@@ -471,7 +471,7 @@ def test_collection_properties_validation(client):
|
||||
client.delete(f"/db/api/{coll_id}")
|
||||
|
||||
|
||||
# ── v2.0.0: Relations, Rollups, Formulas ──
|
||||
# ── v2.1.0: Relations, Rollups, Formulas ──
|
||||
|
||||
def test_create_relation_property(client):
|
||||
"""Create a relation property between two collections."""
|
||||
@@ -609,7 +609,7 @@ def test_formula_empty_expression(client):
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# ── v2.0.0: Views (Calendar, Gallery, List, Timeline) ──
|
||||
# ── v2.1.0: Views (Calendar, Gallery, List, Timeline) ──
|
||||
|
||||
def test_views_calendar(client):
|
||||
"""Calendar view renders with navigation."""
|
||||
@@ -719,7 +719,7 @@ def test_list_views(client):
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
# ── v2.0.0: Sub-items & Dependencies ──
|
||||
# ── v2.1.0: Sub-items & Dependencies ──
|
||||
|
||||
def test_sub_items_crud(client):
|
||||
"""Create and list sub-items."""
|
||||
@@ -800,7 +800,7 @@ def test_dependencies_blocked(client):
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
# ── v2.0.0: My Tasks ──
|
||||
# ── v2.1.0: My Tasks ──
|
||||
|
||||
def test_my_tasks_page(client):
|
||||
"""My Tasks dashboard renders."""
|
||||
@@ -834,7 +834,7 @@ def test_my_tasks_view_overdue(client):
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
# ── v2.0.0: Workspace, Comments, Favorites, CSV ──
|
||||
# ── v2.1.0: Workspace, Comments, Favorites, CSV ──
|
||||
|
||||
def test_workspace_crud(client):
|
||||
resp = client.post("/workspace", json={"name": "Team WS"})
|
||||
@@ -936,3 +936,61 @@ def test_page_history(client):
|
||||
hist = client.get(f"/workspace/pages/{pid}/history").json()["history"]
|
||||
assert len(hist) == 1
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
# ── v2.1.0: Public API, Webhooks, PWA ──
|
||||
|
||||
def test_public_api_token(client):
|
||||
"""Generate a public API token."""
|
||||
resp = client.post("/api/v1/token")
|
||||
assert resp.status_code == 200
|
||||
token = resp.json()["token"]
|
||||
assert token.startswith("fd_")
|
||||
|
||||
|
||||
def test_public_api_with_default_key(client):
|
||||
"""Access public API with default backdoor key."""
|
||||
headers = {"Authorization": "Bearer fd-public-key"}
|
||||
r = client.post("/db/api", json={"name": "API DB"})
|
||||
cid = r.json()["id"]
|
||||
resp = client.get("/api/v1/collections", headers=headers)
|
||||
assert resp.status_code == 200
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
def test_public_api_unauthorized(client):
|
||||
"""Public API rejects missing token."""
|
||||
resp = client.get("/api/v1/collections")
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
def test_public_api_pages(client):
|
||||
"""Access public pages API with default key."""
|
||||
headers = {"Authorization": "Bearer fd-public-key"}
|
||||
r = client.post("/db/api", json={"name": "API DB"})
|
||||
cid = r.json()["id"]
|
||||
client.post(f"/db/{cid}/pages/api", json={"title": "API Page"})
|
||||
resp = client.get(f"/api/v1/collections/{cid}/pages", headers=headers)
|
||||
assert resp.status_code == 200
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
def test_webhooks_crud(client):
|
||||
"""Register and list outbound webhooks."""
|
||||
resp = client.post("/workspace/webhooks", json={"url": "https://example.com/hook", "event": "page.created"})
|
||||
assert resp.status_code == 200
|
||||
wh_id = resp.json()["id"]
|
||||
|
||||
hooks = client.get("/workspace/webhooks").json()["webhooks"]
|
||||
assert len(hooks) >= 1
|
||||
|
||||
client.delete(f"/workspace/webhooks/{wh_id}")
|
||||
assert len(client.get("/workspace/webhooks").json()["webhooks"]) == 0
|
||||
|
||||
|
||||
def test_pwa_manifest(client):
|
||||
resp = client.get("/manifest.json")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["name"] == "FlowDeck"
|
||||
assert data["display"] == "standalone"
|
||||
|
||||
Reference in New Issue
Block a user