fix: v0.3.3 — delete_container params (error 114) + delete_project orphan guard
Bug 1 — delete_container (DSM error 114):
SYNO.Docker.Container/delete requires three parameters: name
(JSON-encoded), force=false, and preserve_profile=false. Previously
only a bare `name` string was sent, causing DSM to reject the call
with error 114. Added the two missing fields and JSON-encode name to
match the DSM convention. The connector-side running-container guard
is unchanged; force stays hard-coded to false.
Bug 2 — delete_project orphan containers:
Production test revealed that DSM does NOT reject Project/delete on a
running project — it silently removes the registration and leaves the
containers running without any project context. The previous
implementation tried to handle this via a caught SynologyError that
never actually fires. Fix: check the project status from _find_project
connector-side before issuing any DSM call; if RUNNING, return an
error pointing at stop_project. The delete request is never sent for
a running project.
The corresponding unit test (test_delete_project_running_returns_stop_hint)
was a false positive — it mocked a DSM rejection that real DSM never
produces. Replaced with test_delete_project_running_blocked_connector_side
which asserts that client.request("delete") is never called when the
project is RUNNING.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -317,6 +317,32 @@ async def test_delete_container_stopped_confirmed():
|
||||
assert "myapp_web" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_container_sends_required_params():
|
||||
"""SYNO.Docker.Container/delete must include name (json-encoded), force=false,
|
||||
and preserve_profile=false — without them DSM returns error 114."""
|
||||
from mcp_synology_container.modules.containers import register_containers
|
||||
|
||||
client = AsyncMock()
|
||||
client.request.return_value = {
|
||||
"details": {"State": {"Running": False, "Status": "exited"}},
|
||||
"profile": {"image": "nginx:latest"},
|
||||
}
|
||||
|
||||
mcp, tools = make_mock_mcp()
|
||||
register_containers(mcp, make_config(), client)
|
||||
|
||||
await tools["delete_container"]("myapp_web", confirmed=True)
|
||||
|
||||
# Find the delete call (second call — first is Container/get)
|
||||
delete_calls = [c for c in client.request.call_args_list if c.args[1] == "delete"]
|
||||
assert len(delete_calls) == 1
|
||||
params = delete_calls[0].kwargs.get("params") or {}
|
||||
assert params["name"] == '"myapp_web"'
|
||||
assert params["force"] == "false"
|
||||
assert params["preserve_profile"] == "false"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_container_stats_cpu_calculation():
|
||||
"""CPU% is computed via the standard Docker formula."""
|
||||
|
||||
Reference in New Issue
Block a user