feat: add 28 GraphQL mutations across storage, info, docker, and new settings tool

- storage: flash_backup mutation (initiates rclone flash backup, destructive)
- info: update_server and update_ssh mutations
- docker: 11 organizer mutations (create_folder, set_folder_children,
  delete_entries, move_to_folder, move_to_position, rename_folder,
  create_folder_with_items, update_view_prefs, sync_templates,
  reset_template_mappings, refresh_digests); delete_entries and
  reset_template_mappings added to DESTRUCTIVE_ACTIONS
- settings: new unraid_settings tool with 9 mutations (update,
  update_temperature, update_time, configure_ups, update_api,
  connect_sign_in, connect_sign_out, setup_remote_access,
  enable_dynamic_remote_access); registered in server.py
- tests: 82 new tests (28 settings, 23 docker organizer, 7 info, 6 storage
  + 18 existing fixes for notification regex and safety audit list)
- bump version 0.3.0 → 0.4.0 (11 tools, ~104 actions)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jacob Magar
2026-03-13 03:03:37 -04:00
parent 4af1e74b4a
commit 9aee3a2448
11 changed files with 994 additions and 7 deletions

View File

@@ -282,3 +282,46 @@ class TestInfoNetworkErrors:
tool_fn = _make_tool()
with pytest.raises(ToolError, match="Invalid JSON"):
await tool_fn(action="network")
class TestInfoMutations:
async def test_update_server_requires_name(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool()
with pytest.raises(ToolError, match="server_name"):
await tool_fn(action="update_server")
async def test_update_server_success(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"updateServerIdentity": {"id": "s:1", "name": "tootie", "comment": None, "status": "online"}}
tool_fn = _make_tool()
result = await tool_fn(action="update_server", server_name="tootie")
assert result["success"] is True
assert result["data"]["name"] == "tootie"
async def test_update_server_passes_optional_fields(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"updateServerIdentity": {"id": "s:1", "name": "x", "comment": None, "status": "online"}}
tool_fn = _make_tool()
await tool_fn(action="update_server", server_name="x", sys_model="custom")
assert _mock_graphql.call_args[0][1]["sysModel"] == "custom"
async def test_update_ssh_requires_enabled(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool()
with pytest.raises(ToolError, match="ssh_enabled"):
await tool_fn(action="update_ssh", ssh_port=22)
async def test_update_ssh_requires_port(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool()
with pytest.raises(ToolError, match="ssh_port"):
await tool_fn(action="update_ssh", ssh_enabled=True)
async def test_update_ssh_success(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"updateSshSettings": {"id": "s:1", "useSsh": True, "portssh": 22}}
tool_fn = _make_tool()
result = await tool_fn(action="update_ssh", ssh_enabled=True, ssh_port=22)
assert result["success"] is True
assert result["data"]["useSsh"] is True
async def test_update_ssh_passes_correct_input(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"updateSshSettings": {"id": "s:1", "useSsh": False, "portssh": 2222}}
tool_fn = _make_tool()
await tool_fn(action="update_ssh", ssh_enabled=False, ssh_port=2222)
assert _mock_graphql.call_args[0][1] == {"input": {"enabled": False, "port": 2222}}