Fix get_container_status: clean name + dual-format response handling
DSM response has two top-level keys: details → Docker Engine inspect (State, NetworkSettings, Mounts) profile → DSM format (image, port_bindings) _format_container_detail now reads: Status/Running/StartedAt from details.State Image from profile.image IP addresses from details.NetworkSettings.Networks Port bindings from profile.port_bindings Mounts from details.Mounts Also: debug_container_response tool removed, json/sys imports cleaned up. 27 container tests all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -407,18 +407,48 @@ async def test_container_stats_strips_hash_prefix():
|
||||
assert "CPU" in result
|
||||
|
||||
|
||||
DSM_CONTAINER_RESPONSE = {
|
||||
"details": {
|
||||
"State": {
|
||||
"Status": "running",
|
||||
"Running": True,
|
||||
"StartedAt": "2025-01-01T10:00:00Z",
|
||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||
"ExitCode": 0,
|
||||
},
|
||||
"NetworkSettings": {
|
||||
"Networks": {
|
||||
"bridge": {"IPAddress": "172.17.0.2"},
|
||||
}
|
||||
},
|
||||
"Mounts": [
|
||||
{
|
||||
"Type": "bind",
|
||||
"Source": "/volume1/docker/jenkins",
|
||||
"Destination": "/var/jenkins_home",
|
||||
"RW": True,
|
||||
}
|
||||
],
|
||||
},
|
||||
"profile": {
|
||||
"image": "jenkins/jenkins:2.558-jdk21",
|
||||
"port_bindings": [
|
||||
{"host_port": 8080, "container_port": 8080, "type": "tcp"},
|
||||
{"host_port": 50000, "container_port": 50000, "type": "tcp"},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
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 == "get":
|
||||
# Must be called with the CLEAN name (no hash prefix)
|
||||
assert kwargs["params"]["name"] == "jenkins"
|
||||
return detail_data
|
||||
return DSM_CONTAINER_RESPONSE
|
||||
return {}
|
||||
|
||||
client = AsyncMock()
|
||||
@@ -427,56 +457,74 @@ async def test_get_container_status_uses_clean_name():
|
||||
mcp, tools = make_mock_mcp()
|
||||
register_containers(mcp, make_config(), client)
|
||||
|
||||
# User passes hash-prefixed name → should be stripped before get call
|
||||
# User passes hash-prefixed name → 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."""
|
||||
async def test_get_container_status_details_profile_structure():
|
||||
"""get_container_status reads status from details.State, image from profile."""
|
||||
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
|
||||
client.request.return_value = DSM_CONTAINER_RESPONSE
|
||||
|
||||
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
|
||||
assert "jenkins/jenkins:2.558-jdk21" in result
|
||||
assert "True" in result # Running field
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_container_status_flat_format():
|
||||
"""get_container_status handles DSM flat format (lowercase status/image)."""
|
||||
async def test_get_container_status_shows_ip():
|
||||
"""get_container_status shows IP address from NetworkSettings."""
|
||||
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
|
||||
client.request.return_value = DSM_CONTAINER_RESPONSE
|
||||
|
||||
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
|
||||
assert "172.17.0.2" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_container_status_shows_ports():
|
||||
"""get_container_status shows port bindings from profile."""
|
||||
from mcp_synology_container.modules.containers import register_containers
|
||||
|
||||
client = AsyncMock()
|
||||
client.request.return_value = DSM_CONTAINER_RESPONSE
|
||||
|
||||
mcp, tools = make_mock_mcp()
|
||||
register_containers(mcp, make_config(), client)
|
||||
|
||||
result = await tools["get_container_status"]("jenkins")
|
||||
assert "8080" in result
|
||||
assert "50000" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_container_status_shows_mounts():
|
||||
"""get_container_status shows mount paths from details.Mounts."""
|
||||
from mcp_synology_container.modules.containers import register_containers
|
||||
|
||||
client = AsyncMock()
|
||||
client.request.return_value = DSM_CONTAINER_RESPONSE
|
||||
|
||||
mcp, tools = make_mock_mcp()
|
||||
register_containers(mcp, make_config(), client)
|
||||
|
||||
result = await tools["get_container_status"]("jenkins")
|
||||
assert "/volume1/docker/jenkins" in result
|
||||
assert "/var/jenkins_home" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user