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:
2026-05-18 11:38:36 +02:00
parent 3f73ed0aef
commit 8adcf93b6a
7 changed files with 72 additions and 24 deletions
+9 -12
View File
@@ -898,20 +898,17 @@ async def test_delete_project_happy_path():
@pytest.mark.asyncio
async def test_delete_project_running_returns_stop_hint():
"""DSM refusing to delete a running project produces a clean 'stop_project' hint
rather than a raw error."""
from mcp_synology_container.dsm_client import SynologyError
client, calls = make_delete_project_client(
project=SAMPLE_PROJECT_RUNNING,
delete_raises=SynologyError("Project is running", code=2103),
)
async def test_delete_project_running_blocked_connector_side():
"""Live test showed that DSM does NOT reject Project/delete on a running project
it silently orphans the containers. The connector must therefore block the call
itself when the project is RUNNING, without ever calling client.request(delete)."""
client, calls = make_delete_project_client(project=SAMPLE_PROJECT_RUNNING)
tools = make_projects_tools(client)
result = await tools["delete_project"]("myapp", confirmed=True)
assert "RUNNING" in result
assert "stop_project" in result
assert "running" in result.lower()
# No "deleted" success line
assert "deleted (registration removed)" not in result
# The delete endpoint must NOT have been called — no orphaned containers.
delete_calls = [m for _, m, _ in calls if m == "delete"]
assert delete_calls == []