mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
chore: update gitignore, bump to 0.2.1, apply CodeRabbit fixes
- Add .windsurf/, *.bak*, .1code/, .emdash.json to .gitignore - Sync standard gitignore entries per project conventions - Apply final test/tool fixes from CodeRabbit review threads - Update GraphQL schema to latest introspection snapshot - Bump version 0.2.0 → 0.2.1 Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -659,9 +659,10 @@ class TestArrayToolRequests:
|
||||
return_value=_graphql_response({"parityCheck": {"start": True}})
|
||||
)
|
||||
tool = self._get_tool()
|
||||
result = await tool(action="parity_start")
|
||||
result = await tool(action="parity_start", correct=False)
|
||||
body = _extract_request_body(route.calls.last.request)
|
||||
assert "StartParityCheck" in body["query"]
|
||||
assert body["variables"] == {"correct": False}
|
||||
assert result["success"] is True
|
||||
|
||||
@respx.mock
|
||||
@@ -858,9 +859,9 @@ class TestNotificationsToolRequests:
|
||||
async def test_create_sends_input_variables(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"notifications": {"createNotification": {
|
||||
{"createNotification": {
|
||||
"id": "n1", "title": "Test", "importance": "INFO",
|
||||
}}}
|
||||
}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -882,7 +883,7 @@ class TestNotificationsToolRequests:
|
||||
async def test_archive_sends_id_variable(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"notifications": {"archiveNotification": True}}
|
||||
{"archiveNotification": {"id": "notif-1"}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -901,7 +902,7 @@ class TestNotificationsToolRequests:
|
||||
async def test_delete_sends_id_and_type(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"notifications": {"deleteNotification": True}}
|
||||
{"deleteNotification": {"unread": {"total": 0}}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -920,7 +921,7 @@ class TestNotificationsToolRequests:
|
||||
async def test_archive_all_sends_importance_when_provided(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"notifications": {"archiveAll": True}}
|
||||
{"archiveAll": {"archive": {"total": 1}}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -1087,10 +1088,10 @@ class TestKeysToolRequests:
|
||||
async def test_create_sends_input_variables(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"createApiKey": {
|
||||
{"apiKey": {"create": {
|
||||
"id": "k2", "name": "new-key",
|
||||
"key": "secret", "roles": ["read"],
|
||||
}}
|
||||
}}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -1106,7 +1107,7 @@ class TestKeysToolRequests:
|
||||
async def test_update_sends_input_variables(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response(
|
||||
{"updateApiKey": {"id": "k1", "name": "renamed", "roles": ["admin"]}}
|
||||
{"apiKey": {"update": {"id": "k1", "name": "renamed", "roles": ["admin"]}}}
|
||||
)
|
||||
)
|
||||
tool = self._get_tool()
|
||||
@@ -1126,12 +1127,12 @@ class TestKeysToolRequests:
|
||||
@respx.mock
|
||||
async def test_delete_sends_ids_when_confirmed(self) -> None:
|
||||
route = respx.post(API_URL).mock(
|
||||
return_value=_graphql_response({"deleteApiKeys": True})
|
||||
return_value=_graphql_response({"apiKey": {"delete": True}})
|
||||
)
|
||||
tool = self._get_tool()
|
||||
result = await tool(action="delete", key_id="k1", confirm=True)
|
||||
body = _extract_request_body(route.calls.last.request)
|
||||
assert "DeleteApiKeys" in body["query"]
|
||||
assert "DeleteApiKey" in body["query"]
|
||||
assert body["variables"]["input"]["ids"] == ["k1"]
|
||||
assert result["success"] is True
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ class TestConfirmAllowsExecution:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_notifications_delete_with_confirm(self, _mock_notif_graphql: AsyncMock) -> None:
|
||||
_mock_notif_graphql.return_value = {"notifications": {"deleteNotification": True}}
|
||||
_mock_notif_graphql.return_value = {"deleteNotification": {"unread": {"total": 0}}}
|
||||
tool_fn = make_tool_fn(
|
||||
"unraid_mcp.tools.notifications", "register_notifications_tool", "unraid_notifications"
|
||||
)
|
||||
@@ -318,7 +318,7 @@ class TestConfirmAllowsExecution:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_notifications_delete_archived_with_confirm(self, _mock_notif_graphql: AsyncMock) -> None:
|
||||
_mock_notif_graphql.return_value = {"notifications": {"deleteArchivedNotifications": True}}
|
||||
_mock_notif_graphql.return_value = {"deleteArchivedNotifications": {"archive": {"total": 0}}}
|
||||
tool_fn = make_tool_fn(
|
||||
"unraid_mcp.tools.notifications", "register_notifications_tool", "unraid_notifications"
|
||||
)
|
||||
@@ -332,7 +332,7 @@ class TestConfirmAllowsExecution:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_keys_delete_with_confirm(self, _mock_keys_graphql: AsyncMock) -> None:
|
||||
_mock_keys_graphql.return_value = {"deleteApiKeys": True}
|
||||
_mock_keys_graphql.return_value = {"apiKey": {"delete": True}}
|
||||
tool_fn = make_tool_fn("unraid_mcp.tools.keys", "register_keys_tool", "unraid_keys")
|
||||
result = await tool_fn(action="delete", key_id="key-123", confirm=True)
|
||||
assert result["success"] is True
|
||||
|
||||
@@ -39,12 +39,17 @@ class TestArrayValidation:
|
||||
with pytest.raises(ToolError, match="Invalid action"):
|
||||
await tool_fn(action=action)
|
||||
|
||||
async def test_parity_start_requires_correct(self, _mock_graphql: AsyncMock) -> None:
|
||||
tool_fn = _make_tool()
|
||||
with pytest.raises(ToolError, match="correct is required"):
|
||||
await tool_fn(action="parity_start")
|
||||
|
||||
|
||||
class TestArrayActions:
|
||||
async def test_parity_start(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"parityCheck": {"start": True}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="parity_start")
|
||||
result = await tool_fn(action="parity_start", correct=False)
|
||||
assert result["success"] is True
|
||||
assert result["action"] == "parity_start"
|
||||
_mock_graphql.assert_called_once()
|
||||
@@ -94,14 +99,14 @@ class TestArrayMutationFailures:
|
||||
async def test_parity_start_mutation_returns_false(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"parityCheck": {"start": False}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="parity_start")
|
||||
result = await tool_fn(action="parity_start", correct=False)
|
||||
assert result["success"] is True
|
||||
assert result["data"] == {"parityCheck": {"start": False}}
|
||||
|
||||
async def test_parity_start_mutation_returns_null(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"parityCheck": {"start": None}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="parity_start")
|
||||
result = await tool_fn(action="parity_start", correct=False)
|
||||
assert result["success"] is True
|
||||
assert result["data"] == {"parityCheck": {"start": None}}
|
||||
|
||||
@@ -110,7 +115,7 @@ class TestArrayMutationFailures:
|
||||
) -> None:
|
||||
_mock_graphql.return_value = {"parityCheck": {"start": {}}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="parity_start")
|
||||
result = await tool_fn(action="parity_start", correct=False)
|
||||
assert result["success"] is True
|
||||
assert result["data"] == {"parityCheck": {"start": {}}}
|
||||
|
||||
@@ -128,7 +133,7 @@ class TestArrayNetworkErrors:
|
||||
_mock_graphql.side_effect = ToolError("HTTP error 500: Internal Server Error")
|
||||
tool_fn = _make_tool()
|
||||
with pytest.raises(ToolError, match="HTTP error 500"):
|
||||
await tool_fn(action="parity_start")
|
||||
await tool_fn(action="parity_start", correct=False)
|
||||
|
||||
async def test_connection_refused(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.side_effect = ToolError("Network connection error: Connection refused")
|
||||
|
||||
@@ -65,7 +65,9 @@ class TestKeysActions:
|
||||
|
||||
async def test_create(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {
|
||||
"createApiKey": {"id": "k:new", "name": "new-key", "key": "secret123", "roles": []}
|
||||
"apiKey": {
|
||||
"create": {"id": "k:new", "name": "new-key", "key": "secret123", "roles": []}
|
||||
}
|
||||
}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="create", name="new-key")
|
||||
@@ -74,11 +76,13 @@ class TestKeysActions:
|
||||
|
||||
async def test_create_with_roles(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {
|
||||
"createApiKey": {
|
||||
"id": "k:new",
|
||||
"name": "admin-key",
|
||||
"key": "secret",
|
||||
"roles": ["admin"],
|
||||
"apiKey": {
|
||||
"create": {
|
||||
"id": "k:new",
|
||||
"name": "admin-key",
|
||||
"key": "secret",
|
||||
"roles": ["admin"],
|
||||
}
|
||||
}
|
||||
}
|
||||
tool_fn = _make_tool()
|
||||
@@ -86,13 +90,15 @@ class TestKeysActions:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_update(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"updateApiKey": {"id": "k:1", "name": "renamed", "roles": []}}
|
||||
_mock_graphql.return_value = {
|
||||
"apiKey": {"update": {"id": "k:1", "name": "renamed", "roles": []}}
|
||||
}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="update", key_id="k:1", name="renamed")
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_delete(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"deleteApiKeys": True}
|
||||
_mock_graphql.return_value = {"apiKey": {"delete": True}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="delete", key_id="k:1", confirm=True)
|
||||
assert result["success"] is True
|
||||
|
||||
@@ -82,9 +82,7 @@ class TestNotificationsActions:
|
||||
|
||||
async def test_create(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {
|
||||
"notifications": {
|
||||
"createNotification": {"id": "n:new", "title": "Test", "importance": "INFO"}
|
||||
}
|
||||
"createNotification": {"id": "n:new", "title": "Test", "importance": "INFO"}
|
||||
}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(
|
||||
@@ -97,13 +95,13 @@ class TestNotificationsActions:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_archive_notification(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"notifications": {"archiveNotification": True}}
|
||||
_mock_graphql.return_value = {"archiveNotification": {"id": "n:1"}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="archive", notification_id="n:1")
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_delete_with_confirm(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"notifications": {"deleteNotification": True}}
|
||||
_mock_graphql.return_value = {"deleteNotification": {"unread": {"total": 0}}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(
|
||||
action="delete",
|
||||
@@ -114,13 +112,13 @@ class TestNotificationsActions:
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_archive_all(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"notifications": {"archiveAll": True}}
|
||||
_mock_graphql.return_value = {"archiveAll": {"archive": {"total": 1}}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="archive_all")
|
||||
assert result["success"] is True
|
||||
|
||||
async def test_unread_notification(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"notifications": {"unreadNotification": True}}
|
||||
_mock_graphql.return_value = {"unreadNotification": {"id": "n:1"}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="unread", notification_id="n:1")
|
||||
assert result["success"] is True
|
||||
@@ -140,7 +138,7 @@ class TestNotificationsActions:
|
||||
assert filter_var["offset"] == 5
|
||||
|
||||
async def test_delete_archived(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {"notifications": {"deleteArchivedNotifications": True}}
|
||||
_mock_graphql.return_value = {"deleteArchivedNotifications": {"archive": {"total": 0}}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(action="delete_archived", confirm=True)
|
||||
assert result["success"] is True
|
||||
@@ -180,9 +178,7 @@ class TestNotificationsCreateValidation:
|
||||
)
|
||||
|
||||
async def test_alert_importance_accepted(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {
|
||||
"notifications": {"createNotification": {"id": "n:1", "importance": "ALERT"}}
|
||||
}
|
||||
_mock_graphql.return_value = {"createNotification": {"id": "n:1", "importance": "ALERT"}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(
|
||||
action="create", title="T", subject="S", description="D", importance="alert"
|
||||
@@ -223,9 +219,7 @@ class TestNotificationsCreateValidation:
|
||||
)
|
||||
|
||||
async def test_title_at_max_accepted(self, _mock_graphql: AsyncMock) -> None:
|
||||
_mock_graphql.return_value = {
|
||||
"notifications": {"createNotification": {"id": "n:1", "importance": "NORMAL"}}
|
||||
}
|
||||
_mock_graphql.return_value = {"createNotification": {"id": "n:1", "importance": "NORMAL"}}
|
||||
tool_fn = _make_tool()
|
||||
result = await tool_fn(
|
||||
action="create",
|
||||
|
||||
Reference in New Issue
Block a user