mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
fix(storage): remove unassigned action (unassignedDevices not in live API)
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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", [])}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user