Files
unraid-mcp/tests/test_settings.py
Jacob Magar a3ea468bd9 fix(tools): remove 10 dead actions referencing mutations absent from live API
settings: remove update_temperature, update_time, update_api, connect_sign_in,
connect_sign_out, setup_remote_access, enable_dynamic_remote_access, update_ssh
info: remove update_server
notifications: remove create_unique

All 10 reference GraphQL mutations that do not exist in live Unraid API v4.29.2.
Verified via live schema introspection against tootie.
2026-03-15 23:22:33 -04:00

118 lines
4.0 KiB
Python

"""Tests for the unraid_settings tool."""
from collections.abc import Generator
from typing import get_args
from unittest.mock import AsyncMock, patch
import pytest
from fastmcp import FastMCP
from unraid_mcp.core.exceptions import ToolError
from unraid_mcp.tools.settings import SETTINGS_ACTIONS, register_settings_tool
@pytest.fixture
def _mock_graphql() -> Generator[AsyncMock, None, None]:
with patch("unraid_mcp.tools.settings.make_graphql_request", new_callable=AsyncMock) as mock:
yield mock
def _make_tool() -> AsyncMock:
test_mcp = FastMCP("test")
register_settings_tool(test_mcp)
# FastMCP 3.x stores tools in providers[0]._components keyed as "tool:{name}@"
local_provider = test_mcp.providers[0]
tool = local_provider._components["tool:unraid_settings@"] # ty: ignore[unresolved-attribute]
return tool.fn
# ---------------------------------------------------------------------------
# Regression: removed actions must not appear in SETTINGS_ACTIONS
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"action",
[
"update_temperature",
"update_time",
"update_api",
"connect_sign_in",
"connect_sign_out",
"setup_remote_access",
"enable_dynamic_remote_access",
"update_ssh",
],
)
def test_removed_settings_actions_are_gone(action: str) -> None:
assert action not in get_args(SETTINGS_ACTIONS), (
f"{action} references a non-existent mutation and must not be in SETTINGS_ACTIONS"
)
# ---------------------------------------------------------------------------
# Validation
# ---------------------------------------------------------------------------
class TestSettingsValidation:
"""Tests for action validation and destructive guard."""
async def test_invalid_action(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool()
with pytest.raises(ToolError, match="Invalid action"):
await tool_fn(action="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="configure_ups", ups_config={"mode": "slave"})
# ---------------------------------------------------------------------------
# update
# ---------------------------------------------------------------------------
class TestSettingsUpdate:
"""Tests for update action."""
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="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="update", settings_input={"shareCount": 5})
assert result["success"] is True
assert result["action"] == "update"
# ---------------------------------------------------------------------------
# configure_ups
# ---------------------------------------------------------------------------
class TestUpsConfig:
"""Tests for configure_ups action."""
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="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="configure_ups", confirm=True, ups_config={"mode": "master", "cable": "usb"}
)
assert result["success"] is True
assert result["action"] == "configure_ups"