ec8f1c87f1
ping(8) -W flag is milliseconds on macOS, seconds on Linux. Code used -W 1 for both, giving macOS a 1ms timeout that fails any host with RTT >1ms (typical wifi/mesh is 2-10ms). Use -W 1000 on darwin to match the intended 1s timeout. Add regression test asserting the platform-specific value.
246 lines
7.7 KiB
Python
246 lines
7.7 KiB
Python
"""Tests for status_checker service: each check method."""
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from app.services.status_checker import _ping, _tcp_connect, check_node
|
|
|
|
# --- check_node dispatcher ---
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_none_always_online():
|
|
result = await check_node("none", None, None)
|
|
assert result["status"] == "online"
|
|
assert result["response_time_ms"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_none_always_online_ignores_host():
|
|
"""'none' returns online immediately without any network call."""
|
|
with patch("app.services.status_checker._ping", new_callable=AsyncMock) as mock_ping:
|
|
result = await check_node("none", None, "192.168.1.1")
|
|
mock_ping.assert_not_called()
|
|
assert result["status"] == "online"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_unknown_without_host():
|
|
result = await check_node("ping", None, None)
|
|
assert result["status"] == "unknown"
|
|
assert result["response_time_ms"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_ping_online():
|
|
with patch("app.services.status_checker._ping", new_callable=AsyncMock, return_value=True):
|
|
result = await check_node("ping", None, "192.168.1.1")
|
|
assert result["status"] == "online"
|
|
assert result["response_time_ms"] is not None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_ping_offline():
|
|
with patch("app.services.status_checker._ping", new_callable=AsyncMock, return_value=False):
|
|
result = await check_node("ping", None, "192.168.1.1")
|
|
assert result["status"] == "offline"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_http_online():
|
|
with patch("app.services.status_checker._http_get", new_callable=AsyncMock, return_value=True):
|
|
result = await check_node("http", "192.168.1.1:8080", None)
|
|
assert result["status"] == "online"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_http_prepends_scheme():
|
|
"""If target doesn't start with http, http:// is prepended."""
|
|
captured = {}
|
|
|
|
async def fake_http_get(url, verify=False):
|
|
captured["url"] = url
|
|
return True
|
|
|
|
with patch("app.services.status_checker._http_get", side_effect=fake_http_get):
|
|
await check_node("http", "192.168.1.1:8080", None)
|
|
|
|
assert captured["url"].startswith("http://")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_https_uses_verify():
|
|
captured = {}
|
|
|
|
async def fake_http_get(url, verify=False):
|
|
captured["verify"] = verify
|
|
return True
|
|
|
|
with patch("app.services.status_checker._http_get", side_effect=fake_http_get):
|
|
await check_node("https", "https://myserver", None)
|
|
|
|
assert captured["verify"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_ssh():
|
|
with patch("app.services.status_checker._tcp_connect", new_callable=AsyncMock, return_value=True) as mock_tcp:
|
|
result = await check_node("ssh", None, "192.168.1.5")
|
|
mock_tcp.assert_called_once_with("192.168.1.5", 22)
|
|
assert result["status"] == "online"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_tcp_parses_port():
|
|
captured = {}
|
|
|
|
async def fake_tcp(host, port):
|
|
captured["host"] = host
|
|
captured["port"] = port
|
|
return True
|
|
|
|
with patch("app.services.status_checker._tcp_connect", side_effect=fake_tcp):
|
|
await check_node("tcp", "192.168.1.10:9090", None)
|
|
|
|
assert captured["host"] == "192.168.1.10"
|
|
assert captured["port"] == 9090
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_prometheus_appends_metrics():
|
|
captured = {}
|
|
|
|
async def fake_http_get(url, verify=False):
|
|
captured["url"] = url
|
|
return True
|
|
|
|
with patch("app.services.status_checker._http_get", side_effect=fake_http_get):
|
|
await check_node("prometheus", "192.168.1.10:9090", None)
|
|
|
|
assert "/metrics" in captured["url"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_health_appends_health():
|
|
captured = {}
|
|
|
|
async def fake_http_get(url, verify=False):
|
|
captured["url"] = url
|
|
return True
|
|
|
|
with patch("app.services.status_checker._http_get", side_effect=fake_http_get):
|
|
await check_node("health", "192.168.1.10:8080", None)
|
|
|
|
assert "/health" in captured["url"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_unknown_method_falls_back_to_ping():
|
|
with patch("app.services.status_checker._ping", new_callable=AsyncMock, return_value=True) as mock_ping:
|
|
result = await check_node("foobar", None, "10.0.0.1")
|
|
mock_ping.assert_called_once()
|
|
assert result["status"] == "online"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_node_exception_returns_offline():
|
|
with patch("app.services.status_checker._ping", new_callable=AsyncMock, side_effect=RuntimeError("boom")):
|
|
result = await check_node("ping", None, "10.0.0.1")
|
|
assert result["status"] == "offline"
|
|
assert result["response_time_ms"] is None
|
|
|
|
|
|
# --- _ping platform args ---
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ping_uses_unix_args_on_non_windows():
|
|
captured = {}
|
|
|
|
async def fake_exec(*args, **kwargs):
|
|
captured["args"] = args
|
|
proc = MagicMock()
|
|
proc.returncode = 0
|
|
proc.wait = AsyncMock()
|
|
return proc
|
|
|
|
with patch("app.services.status_checker.sys.platform", "linux"), \
|
|
patch("asyncio.create_subprocess_exec", side_effect=fake_exec):
|
|
await _ping("192.168.1.1")
|
|
|
|
assert "-c" in captured["args"]
|
|
assert "-W" in captured["args"]
|
|
assert "-n" not in captured["args"]
|
|
# Linux: -W is in seconds; 1s is the intended timeout
|
|
w_idx = captured["args"].index("-W")
|
|
assert captured["args"][w_idx + 1] == "1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ping_uses_macos_millisecond_timeout():
|
|
"""macOS ping(8) -W is milliseconds, not seconds. 1ms would fail any RTT >1ms."""
|
|
captured = {}
|
|
|
|
async def fake_exec(*args, **kwargs):
|
|
captured["args"] = args
|
|
proc = MagicMock()
|
|
proc.returncode = 0
|
|
proc.wait = AsyncMock()
|
|
return proc
|
|
|
|
with patch("app.services.status_checker.sys.platform", "darwin"), \
|
|
patch("asyncio.create_subprocess_exec", side_effect=fake_exec):
|
|
await _ping("192.168.1.1")
|
|
|
|
assert "-c" in captured["args"]
|
|
assert "-W" in captured["args"]
|
|
w_idx = captured["args"].index("-W")
|
|
assert captured["args"][w_idx + 1] == "1000"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ping_uses_windows_args_on_win32():
|
|
captured = {}
|
|
|
|
async def fake_exec(*args, **kwargs):
|
|
captured["args"] = args
|
|
proc = MagicMock()
|
|
proc.returncode = 0
|
|
proc.wait = AsyncMock()
|
|
return proc
|
|
|
|
with patch("app.services.status_checker.sys.platform", "win32"), \
|
|
patch("asyncio.create_subprocess_exec", side_effect=fake_exec):
|
|
await _ping("192.168.1.1")
|
|
|
|
assert "-n" in captured["args"]
|
|
assert "-w" in captured["args"]
|
|
assert "-c" not in captured["args"]
|
|
|
|
|
|
# --- _tcp_connect ---
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tcp_connect_success():
|
|
writer_mock = MagicMock()
|
|
writer_mock.close = MagicMock()
|
|
writer_mock.wait_closed = AsyncMock()
|
|
with patch("asyncio.open_connection", new_callable=AsyncMock, return_value=(MagicMock(), writer_mock)):
|
|
result = await _tcp_connect("192.168.1.1", 22)
|
|
assert result is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tcp_connect_timeout():
|
|
async def timeout_open(*args, **kwargs):
|
|
raise TimeoutError()
|
|
|
|
with patch("asyncio.open_connection", side_effect=timeout_open):
|
|
result = await _tcp_connect("192.168.1.1", 22)
|
|
assert result is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tcp_connect_os_error():
|
|
with patch("asyncio.open_connection", new_callable=AsyncMock, side_effect=OSError("refused")):
|
|
result = await _tcp_connect("192.168.1.1", 9999)
|
|
assert result is False
|