Newer
Older
navi-1 / tests / unit / mcp / test_ui_server.py
"""Unit tests for the internal navi_ui MCP server."""

import pytest
from unittest.mock import AsyncMock

from navi.mcp import ui_server


@pytest.fixture(autouse=True)
def reset_ui_server_state():
    """Reset global orchestrator holder before/after every test."""
    ui_server.clear_orchestrator()
    yield
    ui_server.clear_orchestrator()


class TestRenderComponent:
    async def test_requires_component_name(self):
        result = await ui_server.render_component("", {"x": 1}, "s1")
        assert "component_name" in result.lower()
        assert "Error" in result

    async def test_requires_dict_payload(self):
        result = await ui_server.render_component("table", [1, 2, 3], "s1")
        assert "payload must be a JSON object" in result

    async def test_requires_session_id(self):
        result = await ui_server.render_component("table", {"x": 1}, None)
        assert "session_id" in result.lower()
        assert "Error" in result

    async def test_sends_event_when_orchestrator_ready(self):
        mock_orchestrator = AsyncMock()
        mock_orchestrator._notify_session = AsyncMock()
        ui_server.set_orchestrator(mock_orchestrator)

        result = await ui_server.render_component("table", {"rows": 3}, "sess-123")

        assert "table" in result
        assert "sess-123" in result
        assert "Error" not in result
        mock_orchestrator._notify_session.assert_awaited_once_with(
            "sess-123",
            {
                "type": "ui_component",
                "component": "table",
                "payload": {"rows": 3},
            },
        )

    async def test_times_out_when_orchestrator_never_set(self):
        original_timeout = ui_server._ORCHESTRATOR_TIMEOUT
        ui_server._ORCHESTRATOR_TIMEOUT = 0.05
        try:
            result = await ui_server.render_component("table", {"x": 1}, "s1")
            assert "not ready" in result.lower() or "unavailable" in result.lower()
            assert "Error" in result
        finally:
            ui_server._ORCHESTRATOR_TIMEOUT = original_timeout

    async def test_returns_error_when_notify_session_fails(self):
        mock_orchestrator = AsyncMock()
        mock_orchestrator._notify_session.side_effect = RuntimeError("boom")
        ui_server.set_orchestrator(mock_orchestrator)

        result = await ui_server.render_component("table", {"x": 1}, "s1")

        assert "failed to send" in result.lower()
        assert "Error" in result


class TestOrchestratorHolder:
    def test_set_and_clear(self):
        mock_orchestrator = AsyncMock()
        ui_server.set_orchestrator(mock_orchestrator)
        assert ui_server._orchestrator is mock_orchestrator
        assert ui_server._orchestrator_ready.is_set()

        ui_server.clear_orchestrator()
        assert ui_server._orchestrator is None
        assert not ui_server._orchestrator_ready.is_set()