mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 04:29:17 -07:00
Registers cpu, memory, cpu_telemetry, array_state, parity_progress,
ups_status, notifications_overview, owner, and server_status as MCP
resources under unraid://live/{action}. Each opens a transient WebSocket
via subscribe_once() and returns JSON; exceptions degrade gracefully to
an error JSON dict rather than raising. Skips log_tail and
notification_feed (require params, not suitable as resources).
98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
"""Tests for MCP subscription resources."""
|
|
|
|
import json
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from fastmcp import FastMCP
|
|
|
|
from unraid_mcp.subscriptions.resources import register_subscription_resources
|
|
|
|
|
|
def _make_resources():
|
|
"""Register resources on a test FastMCP instance and return it."""
|
|
test_mcp = FastMCP("test")
|
|
register_subscription_resources(test_mcp)
|
|
return test_mcp
|
|
|
|
|
|
@pytest.fixture
|
|
def _mock_subscribe_once():
|
|
with patch(
|
|
"unraid_mcp.subscriptions.resources.subscribe_once",
|
|
new_callable=AsyncMock,
|
|
) as mock:
|
|
yield mock
|
|
|
|
|
|
@pytest.fixture
|
|
def _mock_ensure_started():
|
|
with patch(
|
|
"unraid_mcp.subscriptions.resources.ensure_subscriptions_started",
|
|
new_callable=AsyncMock,
|
|
) as mock:
|
|
yield mock
|
|
|
|
|
|
class TestLiveResources:
|
|
@pytest.mark.parametrize(
|
|
"action",
|
|
[
|
|
"cpu",
|
|
"memory",
|
|
"cpu_telemetry",
|
|
"array_state",
|
|
"parity_progress",
|
|
"ups_status",
|
|
"notifications_overview",
|
|
"owner",
|
|
"server_status",
|
|
],
|
|
)
|
|
async def test_resource_returns_json(
|
|
self,
|
|
action: str,
|
|
_mock_subscribe_once: AsyncMock,
|
|
_mock_ensure_started: AsyncMock,
|
|
) -> None:
|
|
_mock_subscribe_once.return_value = {"data": "ok"}
|
|
mcp = _make_resources()
|
|
|
|
local_provider = mcp.providers[0]
|
|
resource_key = f"resource:unraid://live/{action}@"
|
|
resource = local_provider._components[resource_key]
|
|
result = await resource.fn()
|
|
|
|
parsed = json.loads(result)
|
|
assert parsed == {"data": "ok"}
|
|
|
|
async def test_resource_returns_error_dict_on_failure(
|
|
self,
|
|
_mock_subscribe_once: AsyncMock,
|
|
_mock_ensure_started: AsyncMock,
|
|
) -> None:
|
|
from fastmcp.exceptions import ToolError
|
|
|
|
_mock_subscribe_once.side_effect = ToolError("WebSocket timeout")
|
|
mcp = _make_resources()
|
|
|
|
local_provider = mcp.providers[0]
|
|
resource = local_provider._components["resource:unraid://live/cpu@"]
|
|
result = await resource.fn()
|
|
|
|
parsed = json.loads(result)
|
|
assert "error" in parsed
|
|
assert "WebSocket timeout" in parsed["error"]
|
|
|
|
|
|
class TestLogsStreamResource:
|
|
async def test_logs_stream_no_data(self, _mock_ensure_started: AsyncMock) -> None:
|
|
with patch("unraid_mcp.subscriptions.resources.subscription_manager") as mock_mgr:
|
|
mock_mgr.get_resource_data = AsyncMock(return_value=None)
|
|
mcp = _make_resources()
|
|
local_provider = mcp.providers[0]
|
|
resource = local_provider._components["resource:unraid://logs/stream@"]
|
|
result = await resource.fn()
|
|
parsed = json.loads(result)
|
|
assert "status" in parsed
|