mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
Resolves review threads: - PRRT_kwDOO6Hdxs50fewG (setup.py): non-eliciting clients now return True from elicit_reset_confirmation so they can reconfigure without being blocked - PRRT_kwDOO6Hdxs50fewM (test-tools.sh): add notification/recalculate smoke test - PRRT_kwDOO6Hdxs50fewP (test-tools.sh): add system/array smoke test - PRRT_kwDOO6Hdxs50fewT (resources.py): surface manager error state instead of reporting 'connecting' for permanently failed subscriptions - PRRT_kwDOO6Hdxs50feAj (resources.py): use is not None check for empty cached dicts - PRRT_kwDOO6Hdxs50fewY (integration tests): remove duplicate snapshot-registration tests already covered in test_resources.py - PRRT_kwDOO6Hdxs50fewe (test_resources.py): replace brittle import-detail test with behavior tests for connecting/error states - PRRT_kwDOO6Hdxs50fewh (test_customization.py): strengthen public_theme assertion - PRRT_kwDOO6Hdxs50fewk (test_customization.py): strengthen theme assertion - PRRT_kwDOO6Hdxs50fewo (__init__.py): correct subaction count ~88 -> ~107 - PRRT_kwDOO6Hdxs50fewx (test_oidc.py): assert providers list value directly - PRRT_kwDOO6Hdxs50fewz (unraid.py): remove unreachable raise after vm handler - PRRT_kwDOO6Hdxs50few2 (unraid.py): remove unreachable raise after docker handler - PRRT_kwDOO6Hdxs50fev8 (CLAUDE.md): replace legacy 15-tool table with unified unraid action/subaction table - PRRT_kwDOO6Hdxs50fev_ (test_oidc.py): assert providers + defaultAllowedOrigins - PRRT_kwDOO6Hdxs50feAz (CLAUDE.md): update tool categories to unified API shape - PRRT_kwDOO6Hdxs50feBE (CLAUDE.md/setup.py): update unraid_health refs to unraid(action=health, subaction=setup)
103 lines
4.2 KiB
Python
103 lines
4.2 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.queries import SNAPSHOT_ACTIONS
|
|
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_ensure_started():
|
|
with patch(
|
|
"unraid_mcp.subscriptions.resources.ensure_subscriptions_started",
|
|
new_callable=AsyncMock,
|
|
) as mock:
|
|
yield mock
|
|
|
|
|
|
class TestLiveResourcesUseManagerCache:
|
|
"""All live resources must read from the persistent SubscriptionManager cache."""
|
|
|
|
@pytest.mark.parametrize("action", list(SNAPSHOT_ACTIONS.keys()))
|
|
async def test_resource_returns_cached_data(
|
|
self, action: str, _mock_ensure_started: AsyncMock
|
|
) -> None:
|
|
cached = {"systemMetricsCpu": {"percentTotal": 12.5}}
|
|
with patch("unraid_mcp.subscriptions.resources.subscription_manager") as mock_mgr:
|
|
mock_mgr.get_resource_data = AsyncMock(return_value=cached)
|
|
mcp = _make_resources()
|
|
resource = mcp.providers[0]._components[f"resource:unraid://live/{action}@"]
|
|
result = await resource.fn()
|
|
assert json.loads(result) == cached
|
|
|
|
@pytest.mark.parametrize("action", list(SNAPSHOT_ACTIONS.keys()))
|
|
async def test_resource_returns_connecting_when_no_cache_and_no_error(
|
|
self, action: str, _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)
|
|
mock_mgr.last_error = {}
|
|
mcp = _make_resources()
|
|
resource = mcp.providers[0]._components[f"resource:unraid://live/{action}@"]
|
|
result = await resource.fn()
|
|
parsed = json.loads(result)
|
|
assert parsed["status"] == "connecting"
|
|
|
|
@pytest.mark.parametrize("action", list(SNAPSHOT_ACTIONS.keys()))
|
|
async def test_resource_returns_error_status_on_permanent_failure(
|
|
self, action: str, _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)
|
|
mock_mgr.last_error = {action: "WebSocket auth failed"}
|
|
mcp = _make_resources()
|
|
resource = mcp.providers[0]._components[f"resource:unraid://live/{action}@"]
|
|
result = await resource.fn()
|
|
parsed = json.loads(result)
|
|
assert parsed["status"] == "error"
|
|
assert "auth failed" in parsed["message"]
|
|
|
|
|
|
class TestSnapshotSubscriptionsRegistered:
|
|
"""All SNAPSHOT_ACTIONS must be registered in the SubscriptionManager with auto_start=True."""
|
|
|
|
def test_all_snapshot_actions_in_configs(self) -> None:
|
|
from unraid_mcp.subscriptions.manager import subscription_manager
|
|
|
|
for action in SNAPSHOT_ACTIONS:
|
|
assert action in subscription_manager.subscription_configs, (
|
|
f"'{action}' not registered in subscription_configs"
|
|
)
|
|
|
|
def test_all_snapshot_actions_autostart(self) -> None:
|
|
from unraid_mcp.subscriptions.manager import subscription_manager
|
|
|
|
for action in SNAPSHOT_ACTIONS:
|
|
config = subscription_manager.subscription_configs[action]
|
|
assert config.get("auto_start") is True, (
|
|
f"'{action}' missing auto_start=True in subscription_configs"
|
|
)
|
|
|
|
|
|
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
|