mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 04:29:17 -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)
132 lines
4.7 KiB
Python
132 lines
4.7 KiB
Python
# tests/test_live.py
|
|
"""Tests for live subactions of the consolidated unraid tool."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from conftest import make_tool_fn
|
|
|
|
|
|
def _make_tool():
|
|
return make_tool_fn("unraid_mcp.tools.unraid", "register_unraid_tool", "unraid")
|
|
|
|
|
|
@pytest.fixture
|
|
def _mock_subscribe_once():
|
|
with patch("unraid_mcp.subscriptions.snapshot.subscribe_once") as m:
|
|
yield m
|
|
|
|
|
|
@pytest.fixture
|
|
def _mock_subscribe_collect():
|
|
with patch("unraid_mcp.subscriptions.snapshot.subscribe_collect") as m:
|
|
yield m
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cpu_returns_snapshot(_mock_subscribe_once):
|
|
_mock_subscribe_once.return_value = {"systemMetricsCpu": {"percentTotal": 23.5, "cpus": []}}
|
|
result = await _make_tool()(action="live", subaction="cpu")
|
|
assert result["success"] is True
|
|
assert result["data"]["systemMetricsCpu"]["percentTotal"] == 23.5
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_memory_returns_snapshot(_mock_subscribe_once):
|
|
_mock_subscribe_once.return_value = {
|
|
"systemMetricsMemory": {"total": 32000000000, "used": 10000000000, "percentTotal": 31.2}
|
|
}
|
|
result = await _make_tool()(action="live", subaction="memory")
|
|
assert result["success"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_log_tail_requires_path(_mock_subscribe_collect):
|
|
_mock_subscribe_collect.return_value = []
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
with pytest.raises(ToolError, match="path"):
|
|
await _make_tool()(action="live", subaction="log_tail")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_log_tail_with_path(_mock_subscribe_collect):
|
|
_mock_subscribe_collect.return_value = [
|
|
{"logFile": {"path": "/var/log/syslog", "content": "line1\nline2", "totalLines": 2}}
|
|
]
|
|
result = await _make_tool()(
|
|
action="live", subaction="log_tail", path="/var/log/syslog", collect_for=1.0
|
|
)
|
|
assert result["success"] is True
|
|
assert result["event_count"] == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notification_feed_collects_events(_mock_subscribe_collect):
|
|
_mock_subscribe_collect.return_value = [
|
|
{"notificationAdded": {"id": "1", "title": "Alert"}},
|
|
{"notificationAdded": {"id": "2", "title": "Info"}},
|
|
]
|
|
result = await _make_tool()(action="live", subaction="notification_feed", collect_for=2.0)
|
|
assert result["event_count"] == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invalid_subaction_raises():
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
with pytest.raises(ToolError, match="Invalid subaction"):
|
|
await _make_tool()(action="live", subaction="nonexistent")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_snapshot_propagates_tool_error(_mock_subscribe_once):
|
|
"""Non-event-driven (streaming) actions still propagate timeout as ToolError."""
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
_mock_subscribe_once.side_effect = ToolError("Subscription timed out after 10s")
|
|
with pytest.raises(ToolError, match="timed out"):
|
|
await _make_tool()(action="live", subaction="cpu")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_driven_timeout_returns_no_recent_events(_mock_subscribe_once):
|
|
"""Event-driven subscriptions return a graceful no_recent_events response on timeout."""
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
_mock_subscribe_once.side_effect = ToolError("Subscription timed out after 10s")
|
|
result = await _make_tool()(action="live", subaction="notifications_overview")
|
|
assert result["success"] is True
|
|
assert result["status"] == "no_recent_events"
|
|
assert "No events received" in result["message"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_driven_non_timeout_error_propagates(_mock_subscribe_once):
|
|
"""Non-timeout ToolErrors from event-driven subscriptions still propagate."""
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
_mock_subscribe_once.side_effect = ToolError("Subscription auth failed")
|
|
with pytest.raises(ToolError, match="auth failed"):
|
|
await _make_tool()(action="live", subaction="owner")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_log_tail_rejects_invalid_path(_mock_subscribe_collect):
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
with pytest.raises(ToolError, match="must start with"):
|
|
await _make_tool()(action="live", subaction="log_tail", path="/etc/shadow")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_snapshot_wraps_bare_exception(_mock_subscribe_once):
|
|
"""Bare exceptions from subscribe_once are wrapped in ToolError by tool_error_handler."""
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
_mock_subscribe_once.side_effect = RuntimeError("WebSocket connection refused")
|
|
with pytest.raises(ToolError):
|
|
await _make_tool()(action="live", subaction="cpu")
|