"""Tests for MCP patch approval gating.""" from __future__ import annotations import asyncio import os from unittest.mock import AsyncMock, MagicMock, patch import pytest os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only") def test_repair_queues_apply_patch_when_approval_required(monkeypatch): from app.config import get_settings, settings get_settings.cache_clear() proposal = { "ok": True, "summary": "Add missing tool", "commit_message": "fix: add tool", "files": [{"path": "server.py", "content": "# patched"}], } mock_db = MagicMock() mock_db.commit = AsyncMock() mock_session = MagicMock() mock_session.__aenter__ = AsyncMock(return_value=mock_db) mock_session.__aexit__ = AsyncMock(return_value=None) async def _run(): with ( patch.object(settings, "mcp_dev_require_approval", True), patch("app.agent.mcp_development.propose_mcp_patch", new=AsyncMock(return_value=proposal)), patch( "app.agent.mcp_development.backup_proposal_files", new=AsyncMock(return_value={"server.py": "# old"}), ), patch("app.agent.mcp_development.execute_approved_patch", new=AsyncMock()) as mock_apply, patch("app.database.SessionLocal", return_value=mock_session), ): from app.agent.mcp_development import attempt_mcp_repair emit = AsyncMock() result = await attempt_mcp_repair( task_id=42, server="pfsense-mcp", tool="pfsense_list_rules", args={"interface": "wan"}, issue="tool missing", error="unknown tool", output=None, manager=AsyncMock(), emit=emit, device="pfsense", ) return result, mock_apply, emit, mock_db result, mock_apply, emit, mock_db = asyncio.run(_run()) assert result.get("pending_approval") is True assert result.get("approval_type") == "mcp_apply_patch" mock_apply.assert_not_called() mock_db.add.assert_called_once() mock_db.commit.assert_awaited_once() assert any(c.args[1] == "approval" for c in emit.call_args_list) def test_repair_applies_immediately_when_approval_not_required(monkeypatch): from app.config import get_settings, settings get_settings.cache_clear() proposal = { "ok": True, "summary": "Fix crash", "files": [{"path": "server.py", "content": "# patched"}], } async def _run(): with ( patch.object(settings, "mcp_dev_require_approval", False), patch("app.agent.mcp_development.propose_mcp_patch", new=AsyncMock(return_value=proposal)), patch( "app.agent.mcp_development.execute_approved_patch", new=AsyncMock(return_value={"ok": True, "tools": ["a"]}), ) as mock_apply, ): from app.agent.mcp_development import attempt_mcp_repair return await attempt_mcp_repair( task_id=1, server="pfsense-mcp", tool="t", args={}, issue="x", error="err", output=None, manager=AsyncMock(), emit=AsyncMock(), ), mock_apply result, mock_apply = asyncio.run(_run()) assert result.get("ok") is True mock_apply.assert_awaited_once()