|
25 | 25 | CONF_SERVICE, |
26 | 26 | DOMAIN, |
27 | 27 | SERVICE_CREATE, |
| 28 | + SERVICE_EXECUTE_COMMAND, |
| 29 | + SERVICE_GET_LOGS, |
28 | 30 | SERVICE_REFRESH, |
29 | 31 | SERVICE_REMOVE, |
30 | 32 | SERVICE_RESTART, |
@@ -165,6 +167,16 @@ async def test_refresh_service_registered(self, hass, mock_ssh): |
165 | 167 | await _setup_entry(hass, entry) |
166 | 168 | assert hass.services.has_service(DOMAIN, SERVICE_REFRESH) |
167 | 169 |
|
| 170 | + async def test_get_logs_service_registered(self, hass, mock_ssh): |
| 171 | + entry = _make_entry() |
| 172 | + await _setup_entry(hass, entry) |
| 173 | + assert hass.services.has_service(DOMAIN, SERVICE_GET_LOGS) |
| 174 | + |
| 175 | + async def test_execute_command_service_registered(self, hass, mock_ssh): |
| 176 | + entry = _make_entry() |
| 177 | + await _setup_entry(hass, entry) |
| 178 | + assert hass.services.has_service(DOMAIN, SERVICE_EXECUTE_COMMAND) |
| 179 | + |
168 | 180 |
|
169 | 181 | # --------------------------------------------------------------------------- |
170 | 182 | # docker_services availability check |
@@ -892,10 +904,87 @@ async def mock_run(h, opts, cmd, timeout=60): |
892 | 904 |
|
893 | 905 | assert len(check_cmds) > 0 |
894 | 906 |
|
| 907 | + async def test_execute_command_returns_output_and_exit_status(self, hass): |
| 908 | + """execute_command issues a docker exec command and returns output + exit_status.""" |
| 909 | + entry = _make_entry(entry_id="e1") |
| 910 | + commands_seen: list[str] = [] |
895 | 911 |
|
896 | | -# --------------------------------------------------------------------------- |
897 | | -# Update entity |
898 | | -# --------------------------------------------------------------------------- |
| 912 | + async def mock_run(h, opts, cmd, timeout=60): |
| 913 | + commands_seen.append(cmd) |
| 914 | + if "exec" in cmd and "echo hello" in cmd: |
| 915 | + return "hello\n", 0 |
| 916 | + return await _default_ssh_run(h, opts, cmd, timeout) |
| 917 | + |
| 918 | + with patch("custom_components.ssh_docker.coordinator._ssh_run", side_effect=mock_run), \ |
| 919 | + patch("custom_components.ssh_docker._ssh_run", side_effect=mock_run): |
| 920 | + await _setup_entry(hass, entry) |
| 921 | + commands_seen.clear() |
| 922 | + result = await hass.services.async_call( |
| 923 | + DOMAIN, SERVICE_EXECUTE_COMMAND, |
| 924 | + {"entity_id": "sensor.ssh_docker_my_container", "command": "echo hello"}, |
| 925 | + blocking=True, |
| 926 | + return_response=True, |
| 927 | + ) |
| 928 | + await hass.async_block_till_done() |
| 929 | + |
| 930 | + assert any("exec" in c for c in commands_seen), ( |
| 931 | + f"Expected a docker exec command, got: {commands_seen}" |
| 932 | + ) |
| 933 | + assert result is not None, "Expected a service response" |
| 934 | + assert result.get("output") == "hello\n", f"Unexpected output: {result.get('output')!r}" |
| 935 | + assert result.get("exit_status") == 0, f"Unexpected exit_status: {result.get('exit_status')}" |
| 936 | + |
| 937 | + async def test_execute_command_forwards_timeout(self, hass): |
| 938 | + """execute_command forwards the timeout parameter to _ssh_run.""" |
| 939 | + entry = _make_entry(entry_id="e1") |
| 940 | + captured_timeouts: list[int] = [] |
| 941 | + |
| 942 | + async def mock_run(h, opts, cmd, timeout=60): |
| 943 | + if "exec" in cmd: |
| 944 | + captured_timeouts.append(timeout) |
| 945 | + return "output\n", 0 |
| 946 | + return await _default_ssh_run(h, opts, cmd, timeout) |
| 947 | + |
| 948 | + with patch("custom_components.ssh_docker.coordinator._ssh_run", side_effect=mock_run), \ |
| 949 | + patch("custom_components.ssh_docker._ssh_run", side_effect=mock_run): |
| 950 | + await _setup_entry(hass, entry) |
| 951 | + await hass.services.async_call( |
| 952 | + DOMAIN, SERVICE_EXECUTE_COMMAND, |
| 953 | + {"entity_id": "sensor.ssh_docker_my_container", "command": "id", "timeout": 120}, |
| 954 | + blocking=True, |
| 955 | + return_response=True, |
| 956 | + ) |
| 957 | + await hass.async_block_till_done() |
| 958 | + |
| 959 | + assert len(captured_timeouts) == 1 |
| 960 | + assert captured_timeouts[0] == 120, ( |
| 961 | + f"Expected timeout 120, got: {captured_timeouts[0]}" |
| 962 | + ) |
| 963 | + |
| 964 | + async def test_execute_command_captures_nonzero_exit_status(self, hass): |
| 965 | + """execute_command returns the non-zero exit code from the remote command.""" |
| 966 | + entry = _make_entry(entry_id="e1") |
| 967 | + |
| 968 | + async def mock_run(h, opts, cmd, timeout=60): |
| 969 | + if "exec" in cmd: |
| 970 | + return "error output\n", 42 |
| 971 | + return await _default_ssh_run(h, opts, cmd, timeout) |
| 972 | + |
| 973 | + with patch("custom_components.ssh_docker.coordinator._ssh_run", side_effect=mock_run), \ |
| 974 | + patch("custom_components.ssh_docker._ssh_run", side_effect=mock_run): |
| 975 | + await _setup_entry(hass, entry) |
| 976 | + result = await hass.services.async_call( |
| 977 | + DOMAIN, SERVICE_EXECUTE_COMMAND, |
| 978 | + {"entity_id": "sensor.ssh_docker_my_container", "command": "exit 42"}, |
| 979 | + blocking=True, |
| 980 | + return_response=True, |
| 981 | + ) |
| 982 | + await hass.async_block_till_done() |
| 983 | + |
| 984 | + assert result is not None, "Expected a service response" |
| 985 | + assert result.get("exit_status") == 42, ( |
| 986 | + f"Expected exit_status 42, got: {result.get('exit_status')}" |
| 987 | + ) |
899 | 988 |
|
900 | 989 |
|
901 | 990 | class TestUpdateEntity: |
|
0 commit comments