mirror of
https://github.com/jmagar/unraid-mcp
synced 2026-04-21 13:37:53 +00:00
141 lines
5.1 KiB
Python
141 lines
5.1 KiB
Python
"""Tests for the setting subactions of the consolidated unraid tool."""
|
|
|
|
from collections.abc import Generator
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from conftest import make_tool_fn
|
|
|
|
from unraid_mcp.core.exceptions import ToolError
|
|
|
|
|
|
@pytest.fixture
|
|
def _mock_graphql() -> Generator[AsyncMock, None, None]:
|
|
with patch("unraid_mcp.core.client.make_graphql_request", new_callable=AsyncMock) as mock:
|
|
yield mock
|
|
|
|
|
|
def _make_tool():
|
|
return make_tool_fn("unraid_mcp.tools.unraid", "register_unraid_tool", "unraid")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Regression: removed subactions must raise Invalid subaction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"subaction",
|
|
[
|
|
"update_temperature",
|
|
"update_time",
|
|
"update_api",
|
|
"connect_sign_in",
|
|
"connect_sign_out",
|
|
"setup_remote_access",
|
|
"enable_dynamic_remote_access",
|
|
"update_ssh",
|
|
],
|
|
)
|
|
async def test_removed_settings_subactions_are_invalid(subaction: str) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="Invalid subaction"):
|
|
await tool_fn(action="setting", subaction=subaction)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSettingsValidation:
|
|
"""Tests for subaction validation and destructive guard."""
|
|
|
|
async def test_invalid_subaction(self, _mock_graphql: AsyncMock) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="Invalid subaction"):
|
|
await tool_fn(action="setting", subaction="nonexistent_action")
|
|
|
|
async def test_destructive_configure_ups_requires_confirm(
|
|
self, _mock_graphql: AsyncMock
|
|
) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="confirm=True"):
|
|
await tool_fn(action="setting", subaction="configure_ups", ups_config={"mode": "slave"})
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# update
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSettingsUpdate:
|
|
"""Tests for update subaction."""
|
|
|
|
async def test_update_requires_settings_input(self, _mock_graphql: AsyncMock) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="settings_input is required"):
|
|
await tool_fn(action="setting", subaction="update")
|
|
|
|
async def test_update_success(self, _mock_graphql: AsyncMock) -> None:
|
|
_mock_graphql.return_value = {
|
|
"updateSettings": {"restartRequired": False, "values": {}, "warnings": []}
|
|
}
|
|
tool_fn = _make_tool()
|
|
result = await tool_fn(
|
|
action="setting", subaction="update", settings_input={"shareCount": 5}
|
|
)
|
|
assert result["success"] is True
|
|
assert result["subaction"] == "update"
|
|
|
|
async def test_update_allows_nested_json_values(self, _mock_graphql: AsyncMock) -> None:
|
|
_mock_graphql.return_value = {
|
|
"updateSettings": {"restartRequired": False, "values": {}, "warnings": []}
|
|
}
|
|
tool_fn = _make_tool()
|
|
payload = {
|
|
"themeOverrides": {"sidebar": None, "panels": ["cpu", "memory"]},
|
|
"advanced": [1, True, {"nested": "ok"}],
|
|
}
|
|
result = await tool_fn(action="setting", subaction="update", settings_input=payload)
|
|
assert result["success"] is True
|
|
_mock_graphql.assert_awaited_once()
|
|
sent_payload = _mock_graphql.await_args.args[1]["input"]
|
|
assert sent_payload == payload
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# configure_ups
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestUpsConfig:
|
|
"""Tests for configure_ups subaction."""
|
|
|
|
async def test_configure_ups_requires_ups_config(self, _mock_graphql: AsyncMock) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="ups_config is required"):
|
|
await tool_fn(action="setting", subaction="configure_ups", confirm=True)
|
|
|
|
async def test_configure_ups_success(self, _mock_graphql: AsyncMock) -> None:
|
|
_mock_graphql.return_value = {"configureUps": True}
|
|
tool_fn = _make_tool()
|
|
result = await tool_fn(
|
|
action="setting",
|
|
subaction="configure_ups",
|
|
confirm=True,
|
|
ups_config={"mode": "master", "cable": "usb"},
|
|
)
|
|
assert result["success"] is True
|
|
assert result["subaction"] == "configure_ups"
|
|
|
|
async def test_configure_ups_rejects_nested_values(self, _mock_graphql: AsyncMock) -> None:
|
|
tool_fn = _make_tool()
|
|
with pytest.raises(ToolError, match="must be a string, number, or boolean"):
|
|
await tool_fn(
|
|
action="setting",
|
|
subaction="configure_ups",
|
|
confirm=True,
|
|
ups_config={"mode": {"nested": "invalid"}},
|
|
)
|