import uuid from unittest.mock import AsyncMock, MagicMock import pytest from api.enums import ToolCategory from api.services.workflow.pipecat_engine import PipecatEngine from api.tests.support.mcp_mock_server import running_mcp_server def _mcp_tool(url: str): t = MagicMock() t.tool_uuid = "uuid-" + uuid.uuid4().hex[:8] t.name = "Acme MCP" t.category = ToolCategory.MCP.value t.definition = { "schema_version": 1, "type": "mcp", "config": {"transport": "streamable_http", "url": url}, } return t @pytest.mark.asyncio async def test_engine_opens_and_closes_mcp_sessions(monkeypatch): async with running_mcp_server() as base_url: tool = _mcp_tool(base_url) engine = PipecatEngine.__new__(PipecatEngine) node = MagicMock() node.tool_uuids = [tool.tool_uuid] workflow = MagicMock() workflow.nodes = {"n1": node} engine.workflow = workflow engine._mcp_sessions = {} from api.db import db_client monkeypatch.setattr( db_client, "get_tools_by_uuids", AsyncMock(return_value=[tool]) ) monkeypatch.setattr( db_client, "get_credential_by_uuid", AsyncMock(return_value=None) ) engine._get_organization_id = AsyncMock(return_value=42) await engine._open_mcp_sessions() try: assert tool.tool_uuid in engine._mcp_sessions sess = engine._mcp_sessions[tool.tool_uuid] assert sess.available is True assert len(sess.function_schemas()) == 2 finally: await engine.close_mcp_sessions() assert engine._mcp_sessions == {} @pytest.mark.asyncio async def test_open_mcp_sessions_swallows_db_error(monkeypatch): engine = PipecatEngine.__new__(PipecatEngine) node = MagicMock() node.tool_uuids = ["uuid-deadbeef"] workflow = MagicMock() workflow.nodes = {"n1": node} engine.workflow = workflow engine._mcp_sessions = {} from api.db import db_client monkeypatch.setattr( db_client, "get_tools_by_uuids", AsyncMock(side_effect=RuntimeError("db down")), ) engine._get_organization_id = AsyncMock(return_value=42) # Must NOT raise await engine._open_mcp_sessions() assert engine._mcp_sessions == {} @pytest.mark.asyncio async def test_open_mcp_sessions_skips_tool_when_credential_fetch_fails(monkeypatch): tool = _mcp_tool("http://127.0.0.1:1/mcp") tool.definition["config"]["credential_uuid"] = "cred-1234" engine = PipecatEngine.__new__(PipecatEngine) node = MagicMock() node.tool_uuids = [tool.tool_uuid] workflow = MagicMock() workflow.nodes = {"n1": node} engine.workflow = workflow engine._mcp_sessions = {} from api.db import db_client monkeypatch.setattr(db_client, "get_tools_by_uuids", AsyncMock(return_value=[tool])) monkeypatch.setattr( db_client, "get_credential_by_uuid", AsyncMock(side_effect=RuntimeError("cred store down")), ) engine._get_organization_id = AsyncMock(return_value=42) # Must NOT raise, and must skip the tool (no futile unauthenticated start) await engine._open_mcp_sessions() assert engine._mcp_sessions == {}