"""P1 admin write-boundary + P2 config-default tests for the MAF roundtable. P1 —admin diagnostic access is **read-only**: - An admin with ``admin_diagnostic_access=True`` can *read* another user's session / run / action / approval (cross-user diagnostics). - The same admin **cannot** *write* another user's resources: update / delete session, start / cancel run, append event, decide approval all raise ``MafForbiddenError`` (→HTTP 403), never silently succeeding. - Normal users remain fully isolated from each other. P2 —config-driven create defaults: - Omitted ``mode`` / ``write_policy`` / ``max_rounds`` fall back to the deployed ``MafRoundtableConfig`` values. - An explicit request value overrides the config default. """ from __future__ import annotations import pytest import pytest_asyncio from maf_test_helpers import make_maf_service from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from app.maf_roundtable.service import MafForbiddenError, MafNotFoundError from deerflow.config.maf_roundtable_config import MafRoundtableConfig from deerflow.persistence.base import Base from deerflow.persistence.maf_roundtable import make_maf_roundtable_store @pytest_asyncio.fixture async def store(tmp_path): engine = create_async_engine(f"sqlite+aiosqlite:///{tmp_path / 'maf_adm.db'}") async with engine.begin() as conn: import deerflow.persistence.maf_roundtable.model # noqa: F401 await conn.run_sync(Base.metadata.create_all) sf = async_sessionmaker(engine, expire_on_commit=False) yield make_maf_roundtable_store(sf) await engine.dispose() def _svc(store, *, enabled=True, admin_diagnostic_access=True, config=None): return make_maf_service( store, enabled=enabled, admin_diagnostic_access=admin_diagnostic_access, config=config, ) # ── P1: admin can READ cross-user ───────────────────────────────────────── @pytest.mark.asyncio async def test_admin_can_read_other_user_session(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) fetched = await svc.get_session(session_id=s["id"], user_id="admin-1", is_admin=True) assert fetched["id"] == s["id"] assert fetched["user_id"] == "alice" @pytest.mark.asyncio async def test_admin_can_read_other_user_run_and_action(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) run = await svc.create_run(session_id=s["id"], user_id="alice") action = await store.actions.create_action( {"session_id": s["id"], "run_id": run["id"], "tool_name": "t", "idempotency_key": "k", "status": "pending"} ) # Admin reads run + action cross-user without error. assert (await svc.get_run(run_id=run["id"], user_id="admin-1", is_admin=True))["id"] == run["id"] assert (await svc.get_action(action_id=action["id"], user_id="admin-1", is_admin=True))["id"] == action["id"] assert len(await svc.list_runs(session_id=s["id"], user_id="admin-1", is_admin=True)) == 1 # ── P1: admin CANNOT WRITE cross-user ───────────────────────────────────── @pytest.mark.asyncio async def test_admin_cannot_update_other_user_session(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) with pytest.raises(MafForbiddenError): await svc.update_session(session_id=s["id"], user_id="admin-1", fields={"title": "hijack"}, is_admin=True) # Unchanged. assert (await svc.get_session(session_id=s["id"], user_id="alice"))["title"] == "" @pytest.mark.asyncio async def test_admin_cannot_delete_other_user_session(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) with pytest.raises(MafForbiddenError): await svc.delete_session(session_id=s["id"], user_id="admin-1", is_admin=True) # Still present. assert (await svc.get_session(session_id=s["id"], user_id="alice"))["id"] == s["id"] @pytest.mark.asyncio async def test_admin_cannot_start_run_on_other_user_session(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) with pytest.raises(MafForbiddenError): await svc.create_run(session_id=s["id"], user_id="admin-1", is_admin=True) # No run was created. assert await svc.list_runs(session_id=s["id"], user_id="alice") == [] @pytest.mark.asyncio async def test_admin_cannot_cancel_other_user_run(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) run = await svc.create_run(session_id=s["id"], user_id="alice") with pytest.raises(MafForbiddenError): await svc.cancel_run(run_id=run["id"], user_id="admin-1", is_admin=True) assert (await svc.get_run(run_id=run["id"], user_id="alice"))["status"] == "pending" @pytest.mark.asyncio async def test_non_owner_cannot_cancel_run_gets_not_found(store): """A non-admin user cancelling another user's run gets 404 (not 403).""" svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) run = await svc.create_run(session_id=s["id"], user_id="alice") with pytest.raises(MafNotFoundError): await svc.cancel_run(run_id=run["id"], user_id="bob") assert (await svc.get_run(run_id=run["id"], user_id="alice"))["status"] == "pending" @pytest.mark.asyncio async def test_admin_cannot_append_event_to_other_user_run(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) run = await svc.create_run(session_id=s["id"], user_id="alice") with pytest.raises(MafForbiddenError): await svc.append_event(run_id=run["id"], user_id="admin-1", event_type="run.started", is_admin=True) @pytest.mark.asyncio async def test_admin_cannot_decide_other_user_approval(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) run = await svc.create_run(session_id=s["id"], user_id="alice") appr = await store.approvals.create_approval( {"session_id": s["id"], "run_id": run["id"], "summary": "x", "status": "pending"} ) with pytest.raises(MafForbiddenError): await svc.decide_approval(approval_id=appr["id"], decision="approved", user_id="admin-1", is_admin=True) assert (await store.approvals.get_approval(appr["id"]))["status"] == "pending" @pytest.mark.asyncio async def test_admin_diagnostic_off_cannot_even_read_cross_user(store): svc = _svc(store, admin_diagnostic_access=False) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) with pytest.raises(MafNotFoundError): await svc.get_session(session_id=s["id"], user_id="admin-1", is_admin=True) @pytest.mark.asyncio async def test_normal_users_remain_fully_isolated(store): svc = _svc(store, admin_diagnostic_access=True) s = await svc.create_session(user_id="alice", body={"task_text": "t"}) # bob is a non-admin →isolated on both read and write. with pytest.raises(MafNotFoundError): await svc.get_session(session_id=s["id"], user_id="bob") with pytest.raises(MafNotFoundError): await svc.update_session(session_id=s["id"], user_id="bob", fields={"title": "x"}) # ── P2: config-driven create defaults ───────────────────────────────────── @pytest.mark.asyncio async def test_create_session_uses_config_defaults_when_omitted(store): cfg = MafRoundtableConfig( default_mode="group_chat", default_write_policy="confirm_all_writes", default_max_rounds=12, ) svc = _svc(store, config=cfg) s = await svc.create_session(user_id="u1", body={"task_text": "t"}) assert s["mode"] == "group_chat" assert s["write_policy"] == "confirm_all_writes" assert s["max_rounds"] == 12 @pytest.mark.asyncio async def test_create_session_explicit_values_override_config(store): cfg = MafRoundtableConfig( default_mode="group_chat", default_write_policy="confirm_all_writes", default_max_rounds=12, ) svc = _svc(store, config=cfg) s = await svc.create_session( user_id="u1", body={"task_text": "t", "mode": "structured", "write_policy": "auto", "max_rounds": 3}, ) assert s["mode"] == "structured" assert s["write_policy"] == "auto" assert s["max_rounds"] == 3