"""MCP-sync RBAC: the compliance-incident + access-certification write tools must enforce the SAME gate as their HTTP twins. - ``resolve_compliance_incident`` → HTTP ``require_admin`` (global_admin only) - ``create/decide/complete_access_certification`` → ``can_manage_security`` (global_admin / auditor) A plain ``employee`` (who can self-mint a PAT) must be refused on all of them; a ``global_admin`` must pass the auth gate. Guards ``feedback_mcp_sync_rule``. """ from __future__ import annotations import pytest from app.core.exceptions import ForbiddenError from app.mcp import context as _mcp_ctx from app.mcp import tools as _tools pytestmark = pytest.mark.asyncio _FAKE_UUID = "00000000-0000-0000-0000-000000000000" # Write tools + valid (auth-reaching) kwargs. _WRITE_CALLS = [ (_tools.resolve_compliance_incident, {"incident_id": _FAKE_UUID}), (_tools.create_access_certification, {"label": "Q test review", "scope_config": {"department": "X"}}), (_tools.decide_access_certification_item, {"item_id": _FAKE_UUID, "decision": "keep"}), (_tools.complete_access_certification, {"certification_id": _FAKE_UUID}), ] @pytest.fixture() def _patch_factory(db_engine, monkeypatch): from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from app.core import database as _db monkeypatch.setattr( _db, "async_session_factory", async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False), ) def _as_user(user_id): return _mcp_ctx.mcp_user_id.set(user_id) async def test_employee_refused_on_every_compliance_write( employee_user, _patch_factory, ): user, _ = employee_user token = _as_user(user.id) try: for fn, kwargs in _WRITE_CALLS: with pytest.raises(ForbiddenError): await fn(**kwargs) finally: _mcp_ctx.mcp_user_id.reset(token) async def test_admin_can_create_access_certification(admin_user, _patch_factory): user, _ = admin_user token = _as_user(user.id) try: # A department with no seats → empty snapshot, but the campaign is # still created (admin passes the can_manage_security gate). res = await _tools.create_access_certification( label="MCP-created review", scope_config={"department": "NoSuchDept"}, ) assert res["snapshot_item_count"] == 0 assert res["status"] == "in_progress" assert res["id"] finally: _mcp_ctx.mcp_user_id.reset(token)