from __future__ import annotations import uuid from types import SimpleNamespace import pytest from src.plugins.mcp import gateway as gateway_module from src.plugins.mcp.gateway import GateCheckResult, GatewayContext, McpGateway from src.plugins.mcp.interfaces import MCPTool, MCPToolProvider, MCPToolResult class FakeDb: def __init__(self) -> None: self.added: list[object] = [] self.flush_count = 0 def add(self, item: object) -> None: self.added.append(item) async def flush(self) -> None: self.flush_count += 1 @pytest.mark.asyncio async def test_write_audit_persists_blocked_gate_without_tool_row() -> None: db = FakeDb() run_id = uuid.uuid4() await McpGateway(db)._write_audit( ctx=GatewayContext( project_id="awoooi", agent_id="openclaw-sre", tool_name="missing_tool", run_id=run_id, trace_id="trace-audit-gap", ), tool_row=None, parameters={"namespace": "awoooi-prod"}, result=None, gate_result=GateCheckResult(), result_status="blocked", block_gate=1, block_reason="E-MCP-GATE-001: project blocked", latency_ms=12, ) assert db.flush_count == 1 assert len(db.added) == 1 audit = db.added[0] assert audit.project_id == "awoooi" assert audit.run_id == run_id assert audit.tool_id is None assert audit.tool_name == "missing_tool" assert audit.result_status == "blocked" assert audit.block_gate == 1 assert audit.gate_result["schema_version"] == "awooop_mcp_gateway_audit_v1" assert audit.gate_result["gateway_path"] == "awooop_mcp_gateway" assert audit.gate_result["policy_enforced"] is True class FakeProvider(MCPToolProvider): def __init__(self) -> None: self.calls: list[tuple[str, dict]] = [] @property def name(self) -> str: return "kubernetes" async def list_tools(self) -> list[MCPTool]: return [MCPTool(name="kubectl_get", description="", input_schema={})] async def execute(self, tool_name: str, parameters: dict) -> MCPToolResult: self.calls.append((tool_name, parameters)) return MCPToolResult(success=True, execution_id="provider-ok", output={"ok": True}) class FakeProviderRegistry: def __init__(self, provider: FakeProvider) -> None: self.provider = provider def get(self, _name: str): return None def all(self) -> list[FakeProvider]: return [self.provider] @pytest.mark.asyncio async def test_execute_tool_resolves_provider_by_tool_manifest( monkeypatch: pytest.MonkeyPatch, ) -> None: provider = FakeProvider() monkeypatch.setattr( gateway_module, "get_provider_registry", lambda: FakeProviderRegistry(provider), ) result = await McpGateway(FakeDb())._execute_tool( GatewayContext( project_id="awoooi", agent_id="pre_decision_investigator", tool_name="kubectl_get", trace_id="INC-GW", ), SimpleNamespace(tool_name="kubectl_get"), { "namespace": "awoooi-prod", "_mcp_audit": { "incident_id": "INC-GW", "session_id": "incident:INC-GW:pre_decision", "flywheel_node": "sense", "agent_role": "pre_decision_investigator", }, }, ) assert result.success is True assert provider.calls tool_name, parameters = provider.calls[0] assert tool_name == "kubectl_get" assert parameters["_mcp_audit"]["gateway_path"] == "awooop_mcp_gateway" assert parameters["_mcp_audit"]["incident_id"] == "INC-GW" assert parameters["_mcp_audit"]["flywheel_node"] == "sense"