feat(maf): add secure roundtable workflow runtime

This commit is contained in:
董宏凯 committed 2026-08-01 17:51:53 +08:00
1 parent e690475e6e
commit bd4131b4e2
52 files changed
+10315 -96

No files matched your search

@@ -9,7 +9,7 @@ Covers the full HTTP status code matrix:
- **503 Service Unavailable**: feature disabled, mock events disabled
Also verifies that identity comes from ``request.state.user`` (stamped by auth
middleware), not from body parameters — a client cannot forge ``user_id`` in the
middleware), not from body parameters —a client cannot forge ``user_id`` in the
request body to impersonate another user.
"""
@@ -19,10 +19,10 @@ import pytest
import pytest_asyncio
from fastapi import FastAPI, Request
from httpx import ASGITransport, AsyncClient
from maf_test_helpers import make_maf_service
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from app.gateway.routers.maf_roundtable import router as maf_router
from app.maf_roundtable import MafRoundtableService
from deerflow.config.maf_roundtable_config import MafRoundtableConfig
from deerflow.persistence.base import Base
from deerflow.persistence.maf_roundtable import make_maf_roundtable_store
@@ -48,14 +48,15 @@ async def app_and_client(tmp_path):
store = make_maf_roundtable_store(sf)
cfg = MafRoundtableConfig(enabled=True, admin_diagnostic_access=True)
svc = MafRoundtableService(store, enabled=True, admin_diagnostic_access=True, config=cfg)
svc = make_maf_service(store, enabled=True, admin_diagnostic_access=True, config=cfg)
app = FastAPI()
app.state.maf_roundtable_service = svc
app.state.maf_auto_dispatch = False
app.include_router(maf_router)
# Fake auth middleware: reads X-Test-User / X-Test-Role headers and stamps
# request.state.user. No header → 401 (unauthenticated).
# request.state.user. No header →401 (unauthenticated).
@app.middleware("http")
async def fake_auth(request: Request, call_next):
user_id = request.headers.get("X-Test-User")
@@ -99,7 +100,7 @@ async def test_403_admin_cannot_update_other_user_session(app_and_client):
assert resp.status_code == 200
session_id = resp.json()["id"]
# admin tries to update it → 403
# admin tries to update it →403
resp = await client.put(
f"/api/maf-roundtable/sessions/{session_id}",
json={"title": "hijack"},
@@ -146,7 +147,7 @@ async def test_404_other_user_session_not_leaked(app_and_client):
headers={"X-Test-User": "alice"},
)
session_id = resp.json()["id"]
# bob (non-admin) tries to read alice's session → 404 (not 403, so existence is not leaked)
# bob (non-admin) tries to read alice's session →404 (not 403, so existence is not leaked)
resp = await client.get(
f"/api/maf-roundtable/sessions/{session_id}",
headers={"X-Test-User": "bob"},
@@ -173,7 +174,7 @@ async def test_409_second_run_launch_conflicts(app_and_client):
headers={"X-Test-User": "alice"},
)
assert resp.status_code == 200
# Second run launch → 409
# Second run launch →409
resp = await client.post(
f"/api/maf-roundtable/sessions/{session_id}/runs",
json={},
@@ -208,7 +209,7 @@ async def test_409_approval_already_decided(app_and_client):
headers={"X-Test-User": "alice"},
)
assert resp.status_code == 200
# Second decision → 409
# Second decision →409
resp = await client.post(
f"/api/maf-roundtable/approvals/{appr['id']}/decision",
json={"decision": "rejected"},
@@ -268,10 +269,11 @@ async def test_503_feature_disabled(tmp_path):
store = make_maf_roundtable_store(sf)
cfg = MafRoundtableConfig(enabled=False)
svc = MafRoundtableService(store, enabled=False, admin_diagnostic_access=True, config=cfg)
svc = make_maf_service(store, enabled=False, admin_diagnostic_access=True, config=cfg)
app = FastAPI()
app.state.maf_roundtable_service = svc
app.state.maf_auto_dispatch = False
app.include_router(maf_router)
@app.middleware("http")
@@ -289,7 +291,7 @@ async def test_503_feature_disabled(tmp_path):
@pytest.mark.asyncio
async def test_404_post_events_endpoint_removed(app_and_client):
"""POST /runs/{id}/events is deliberately absent — no public mock write path."""
"""POST /runs/{id}/events is deliberately absent —no public mock write path."""
_, client, _ = app_and_client
resp = await client.post(
"/api/maf-roundtable/sessions",