feat(maf): add secure roundtable workflow runtime
This commit is contained in:
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",
|
||||
|
||||
Reference in new issue
Block a user