Fix get_container_status: clean name + dual-format response handling

- get_container_status now strips hash prefix from user input and calls
  SYNO.Docker.Container/get with the clean name (e.g. 'jenkins'), not the
  hash-prefixed form — the get endpoint accepts only the clean name
- _format_container_detail: unwraps 'container' wrapper key if present
  (DSM may return {"container": {State, Config, ...}} at the data level)
- Flat-format fallback: reads lowercase 'status'/'image' fields when
  Docker Engine nested format (State/Config) is absent
- Diagnostic stderr logging for data_keys, unwrap, status, image
- 25 container tests all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 21:49:30 +02:00
parent c8cda5ef2b
commit 584d53e6e4
2 changed files with 100 additions and 23 deletions
+53 -9
View File
@@ -408,18 +408,16 @@ async def test_container_stats_strips_hash_prefix():
@pytest.mark.asyncio
async def test_get_container_status_resolves_hash_prefix():
"""get_container_status resolves 'jenkins' to 'f93cb8b504f7_jenkins' for DSM call."""
async def test_get_container_status_uses_clean_name():
"""get_container_status strips hash prefix and calls get with clean name."""
from mcp_synology_container.modules.containers import register_containers
detail_data = {"State": {"Status": "running", "Running": True}, "Config": {"Image": "jenkins/jenkins:lts"}}
async def mock_request(api, method, **kwargs):
if api == "SYNO.Docker.Container" and method == "list":
return HASH_PREFIXED_CONTAINERS_DATA
if api == "SYNO.Docker.Container" and method == "get":
# Must be called with the hash-prefixed name
assert kwargs["params"]["name"] == "f93cb8b504f7_jenkins"
# Must be called with the CLEAN name (no hash prefix)
assert kwargs["params"]["name"] == "jenkins"
return detail_data
return {}
@@ -429,12 +427,58 @@ async def test_get_container_status_resolves_hash_prefix():
mcp, tools = make_mock_mcp()
register_containers(mcp, make_config(), client)
result = await tools["get_container_status"]("jenkins")
assert "Error" not in result
# Display name should be clean (no hash prefix)
# User passes hash-prefixed name → should be stripped before get call
result = await tools["get_container_status"]("f93cb8b504f7_jenkins")
assert "running" in result
assert "f93cb8b504f7" not in result
@pytest.mark.asyncio
async def test_get_container_status_container_key_unwrap():
"""get_container_status unwraps DSM 'container' wrapper key."""
from mcp_synology_container.modules.containers import register_containers
# DSM may wrap the inspect data under a "container" key
wrapped_data = {
"container": {
"State": {"Status": "running", "Running": True},
"Config": {"Image": "jenkins/jenkins:lts"},
}
}
client = AsyncMock()
client.request.return_value = wrapped_data
mcp, tools = make_mock_mcp()
register_containers(mcp, make_config(), client)
result = await tools["get_container_status"]("jenkins")
assert "running" in result
assert "jenkins/jenkins:lts" in result
@pytest.mark.asyncio
async def test_get_container_status_flat_format():
"""get_container_status handles DSM flat format (lowercase status/image)."""
from mcp_synology_container.modules.containers import register_containers
flat_data = {
"status": "running",
"image": "jenkins/jenkins:lts",
"name": "jenkins",
}
client = AsyncMock()
client.request.return_value = flat_data
mcp, tools = make_mock_mcp()
register_containers(mcp, make_config(), client)
result = await tools["get_container_status"]("jenkins")
assert "running" in result
assert "jenkins/jenkins:lts" in result
@pytest.mark.asyncio
async def test_get_container_logs_resolves_hash_prefix():
"""get_container_logs resolves 'jenkins' to 'f93cb8b504f7_jenkins' for DSM call."""