fix(storage): remove unassigned action (unassignedDevices not in live API)

This commit is contained in:
Jacob Magar
2026-03-15 21:59:06 -04:00
parent 83df768135
commit 16bb5a6146
2 changed files with 27 additions and 20 deletions

View File

@@ -1,6 +1,7 @@
"""Tests for unraid_storage tool.""" """Tests for unraid_storage tool."""
from collections.abc import Generator from collections.abc import Generator
from typing import get_args
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
@@ -8,6 +9,13 @@ from conftest import make_tool_fn
from unraid_mcp.core.exceptions import ToolError from unraid_mcp.core.exceptions import ToolError
from unraid_mcp.core.utils import format_bytes, format_kb, safe_get from unraid_mcp.core.utils import format_bytes, format_kb, safe_get
from unraid_mcp.tools.storage import STORAGE_ACTIONS
def test_unassigned_action_removed() -> None:
assert "unassigned" not in get_args(STORAGE_ACTIONS), (
"unassigned action references unassignedDevices which is not in live API"
)
# --- Unit tests for helpers --- # --- Unit tests for helpers ---
@@ -235,12 +243,6 @@ class TestStorageActions:
with pytest.raises(ToolError, match="not found"): with pytest.raises(ToolError, match="not found"):
await tool_fn(action="disk_details", disk_id="d:missing") await tool_fn(action="disk_details", disk_id="d:missing")
async def test_unassigned(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"unassignedDevices": []}
tool_fn = _make_tool()
result = await tool_fn(action="unassigned")
assert result["devices"] == []
async def test_log_files(self, _mock_graphql: AsyncMock) -> None: async def test_log_files(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"logFiles": [{"name": "syslog", "path": "/var/log/syslog"}]} _mock_graphql.return_value = {"logFiles": [{"name": "syslog", "path": "/var/log/syslog"}]}
tool_fn = _make_tool() tool_fn = _make_tool()
@@ -289,7 +291,9 @@ class TestStorageFlashBackup:
async def test_flash_backup_requires_confirm(self, _mock_graphql: AsyncMock) -> None: async def test_flash_backup_requires_confirm(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool() tool_fn = _make_tool()
with pytest.raises(ToolError, match="destructive"): with pytest.raises(ToolError, match="destructive"):
await tool_fn(action="flash_backup", remote_name="r", source_path="/boot", destination_path="r:b") await tool_fn(
action="flash_backup", remote_name="r", source_path="/boot", destination_path="r:b"
)
async def test_flash_backup_requires_remote_name(self, _mock_graphql: AsyncMock) -> None: async def test_flash_backup_requires_remote_name(self, _mock_graphql: AsyncMock) -> None:
tool_fn = _make_tool() tool_fn = _make_tool()
@@ -309,12 +313,25 @@ class TestStorageFlashBackup:
async def test_flash_backup_success(self, _mock_graphql: AsyncMock) -> None: async def test_flash_backup_success(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"initiateFlashBackup": {"status": "started", "jobId": "j:1"}} _mock_graphql.return_value = {"initiateFlashBackup": {"status": "started", "jobId": "j:1"}}
tool_fn = _make_tool() tool_fn = _make_tool()
result = await tool_fn(action="flash_backup", confirm=True, remote_name="r", source_path="/boot", destination_path="r:b") result = await tool_fn(
action="flash_backup",
confirm=True,
remote_name="r",
source_path="/boot",
destination_path="r:b",
)
assert result["success"] is True assert result["success"] is True
assert result["data"]["status"] == "started" assert result["data"]["status"] == "started"
async def test_flash_backup_passes_options(self, _mock_graphql: AsyncMock) -> None: async def test_flash_backup_passes_options(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"initiateFlashBackup": {"status": "started", "jobId": "j:2"}} _mock_graphql.return_value = {"initiateFlashBackup": {"status": "started", "jobId": "j:2"}}
tool_fn = _make_tool() tool_fn = _make_tool()
await tool_fn(action="flash_backup", confirm=True, remote_name="r", source_path="/boot", destination_path="r:b", backup_options={"dryRun": True}) await tool_fn(
action="flash_backup",
confirm=True,
remote_name="r",
source_path="/boot",
destination_path="r:b",
backup_options={"dryRun": True},
)
assert _mock_graphql.call_args[0][1]["input"]["options"] == {"dryRun": True} assert _mock_graphql.call_args[0][1]["input"]["options"] == {"dryRun": True}

View File

@@ -1,7 +1,7 @@
"""Storage and disk management. """Storage and disk management.
Provides the `unraid_storage` tool with 6 actions for shares, physical disks, Provides the `unraid_storage` tool with 6 actions for shares, physical disks,
unassigned devices, log files, and log content retrieval. log files, and log content retrieval.
""" """
import os import os
@@ -39,11 +39,6 @@ QUERIES: dict[str, str] = {
} }
} }
""", """,
"unassigned": """
query GetUnassignedDevices {
unassignedDevices { id device name size type }
}
""",
"log_files": """ "log_files": """
query ListLogFiles { query ListLogFiles {
logFiles { name path size modifiedAt } logFiles { name path size modifiedAt }
@@ -73,7 +68,6 @@ STORAGE_ACTIONS = Literal[
"shares", "shares",
"disks", "disks",
"disk_details", "disk_details",
"unassigned",
"log_files", "log_files",
"logs", "logs",
"flash_backup", "flash_backup",
@@ -109,7 +103,6 @@ def register_storage_tool(mcp: FastMCP) -> None:
shares - List all user shares with capacity info shares - List all user shares with capacity info
disks - List all physical disks disks - List all physical disks
disk_details - Detailed SMART info for a disk (requires disk_id) disk_details - Detailed SMART info for a disk (requires disk_id)
unassigned - List unassigned devices
log_files - List available log files log_files - List available log files
logs - Retrieve log content (requires log_path, optional tail_lines) logs - Retrieve log content (requires log_path, optional tail_lines)
flash_backup - Initiate flash backup via rclone (requires remote_name, source_path, destination_path, confirm=True) flash_backup - Initiate flash backup via rclone (requires remote_name, source_path, destination_path, confirm=True)
@@ -203,9 +196,6 @@ def register_storage_tool(mcp: FastMCP) -> None:
} }
return {"summary": summary, "details": raw} return {"summary": summary, "details": raw}
if action == "unassigned":
return {"devices": data.get("unassignedDevices", [])}
if action == "log_files": if action == "log_files":
return {"log_files": data.get("logFiles", [])} return {"log_files": data.get("logFiles", [])}