refactor: unify SSH machines as services

This commit is contained in:
Developer
2026-07-14 20:58:46 +00:00
parent fe90feb1b7
commit 37533dd219
42 changed files with 3103 additions and 4960 deletions
+26 -26
View File
@@ -14,7 +14,7 @@ from media_library_viewer_api.integrations.jellyfin import test_connection as jf
from media_library_viewer_api.integrations.nextcloud import test_connection as nc_test
from media_library_viewer_api.integrations.prometheus import test_connection as prom_test
from media_library_viewer_api.integrations.qbittorrent import test_connection as qbit_test
from media_library_viewer_api.integrations.ssh_tasks import test_connection as ssh_test
from media_library_viewer_api.integrations.remote_machine import test_connection as ssh_test
# ---------------------------------------------------------------------------
# translate_connection_error (CT-119)
@@ -26,32 +26,32 @@ class TestTranslateConnectionError:
resp = SimpleNamespace(status_code=401)
exc = requests.HTTPError(response=resp)
result = translate_connection_error(exc)
assert result.ok is False
assert not result.ok
assert "Authentication failed" in result.detail
def test_http_403_maps_to_auth_message(self) -> None:
resp = SimpleNamespace(status_code=403)
exc = requests.HTTPError(response=resp)
result = translate_connection_error(exc)
assert result.ok is False
assert not result.ok
assert "Authentication failed" in result.detail
def test_connection_error_dns_maps_to_host_not_found(self) -> None:
exc = requests.ConnectionError("getaddrinfo failed")
result = translate_connection_error(exc)
assert result.ok is False
assert not result.ok
assert "Host not found" in result.detail
def test_timeout_maps_to_timed_out(self) -> None:
exc = requests.Timeout("timed out")
result = translate_connection_error(exc)
assert result.ok is False
assert not result.ok
assert "timed out" in result.detail.lower()
def test_generic_fallback_includes_context(self) -> None:
exc = ValueError("something weird happened")
result = translate_connection_error(exc, context="qBittorrent")
assert result.ok is False
assert not result.ok
assert "qBittorrent" in result.detail
assert "something weird happened" in result.detail
@@ -71,7 +71,7 @@ class TestQbittorrentTestConnection:
{"username": "u", "password": "p"},
MagicMock(),
)
assert result.ok is True
assert result.ok
assert result.evidence == "v4.6.0"
def test_login_failed_translates_to_auth_message(self) -> None:
@@ -82,7 +82,7 @@ class TestQbittorrentTestConnection:
)
with patch("media_library_viewer_api.integrations.qbittorrent.QbittorrentClient", return_value=mock_client):
result = qbit_test({"base_url": "http://qb:8080"}, {"username": "u", "password": "p"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Authentication failed" in result.detail
def test_gateway_error_does_not_masquerade_as_auth_failure(self) -> None:
@@ -94,7 +94,7 @@ class TestQbittorrentTestConnection:
)
with patch("media_library_viewer_api.integrations.qbittorrent.QbittorrentClient", return_value=mock_client):
result = qbit_test({"base_url": "http://qb:8080"}, {"username": "u", "password": "p"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Authentication failed" not in result.detail
assert "504" in result.detail
@@ -103,7 +103,7 @@ class TestQbittorrentTestConnection:
mock_client.maindata.side_effect = requests.ConnectionError("Connection refused")
with patch("media_library_viewer_api.integrations.qbittorrent.QbittorrentClient", return_value=mock_client):
result = qbit_test({"base_url": "http://qb:8080"}, {"username": "u", "password": "p"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Connection refused" in result.detail
@@ -121,17 +121,17 @@ class TestPrometheusTestConnection:
{"grafana_api_key": "tok"},
MagicMock(),
)
assert result.ok is True
assert result.ok
assert "Gateway" in (result.evidence or "")
def test_missing_url_returns_error_without_network(self) -> None:
result = prom_test({}, {"grafana_api_key": "tok"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "URL" in result.detail
def test_missing_api_key_returns_error_without_network(self) -> None:
result = prom_test({"grafana_url": "http://grafana:3000"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
assert "API key" in result.detail
def test_http_401_translates_to_auth(self) -> None:
@@ -143,7 +143,7 @@ class TestPrometheusTestConnection:
{"grafana_api_key": "wrong"},
MagicMock(),
)
assert result.ok is False
assert not result.ok
assert "Authentication failed" in result.detail
@@ -160,7 +160,7 @@ class TestAlertmanagerTestConnection:
)
with patch("media_library_viewer_api.integrations.alertmanager.requests.get", return_value=payload):
result = am_test({"base_url": "http://am:9093"}, {}, MagicMock())
assert result.ok is True
assert result.ok
assert result.evidence == "0.27.0"
def test_connection_refused_translates(self) -> None:
@@ -169,7 +169,7 @@ class TestAlertmanagerTestConnection:
side_effect=requests.ConnectionError("refused"),
):
result = am_test({"base_url": "http://am:9093"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Connection refused" in result.detail
@@ -184,7 +184,7 @@ class TestJellyfinTestConnection:
mock_client.users.return_value = [{"Name": "a"}, {"Name": "b"}]
with patch("media_library_viewer_api.integrations.jellyfin.JellyfinClient", return_value=mock_client):
result = jf_test({"base_url": "http://jf:8096"}, {"api_key": "k"}, MagicMock())
assert result.ok is True
assert result.ok
assert "2 users" == result.evidence
def test_http_401_translates_to_auth(self) -> None:
@@ -192,7 +192,7 @@ class TestJellyfinTestConnection:
mock_client.users.side_effect = requests.HTTPError(response=SimpleNamespace(status_code=401))
with patch("media_library_viewer_api.integrations.jellyfin.JellyfinClient", return_value=mock_client):
result = jf_test({"base_url": "http://jf:8096"}, {"api_key": "wrong"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Authentication failed" in result.detail
@@ -207,7 +207,7 @@ class TestAuthentikTestConnection:
mock_client.users.return_value = {"total": 5, "items": []}
with patch("media_library_viewer_api.integrations.authentik.AuthentikClient", return_value=mock_client):
result = ak_test({"base_url": "http://ak:9000"}, {"api_token": "tok"}, MagicMock())
assert result.ok is True
assert result.ok
assert "5 users" == result.evidence
def test_connection_error_translates(self) -> None:
@@ -215,7 +215,7 @@ class TestAuthentikTestConnection:
mock_client.users.side_effect = requests.ConnectionError("refused")
with patch("media_library_viewer_api.integrations.authentik.AuthentikClient", return_value=mock_client):
result = ak_test({"base_url": "http://ak:9000"}, {"api_token": "tok"}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Connection refused" in result.detail
@@ -229,7 +229,7 @@ class TestSshTasksTestConnection:
mock_client = MagicMock()
with patch("media_library_viewer_api.services.task_runner.build_ssh_client", return_value=mock_client):
result = ssh_test({"host": "srv", "port": 22, "username": "u"}, {"passphrase": ""}, MagicMock())
assert result.ok is True
assert result.ok
assert "Connected to srv:22" == result.evidence
def test_auth_failed_translates_to_ssh_auth_message(self) -> None:
@@ -237,7 +237,7 @@ class TestSshTasksTestConnection:
mock_client.connect.side_effect = Exception("SSH authentication failed")
with patch("media_library_viewer_api.services.task_runner.build_ssh_client", return_value=mock_client):
result = ssh_test({"host": "srv", "port": 22, "username": "u"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
assert "SSH authentication failed" in result.detail
def test_protocol_banner_translates(self) -> None:
@@ -245,12 +245,12 @@ class TestSshTasksTestConnection:
mock_client.connect.side_effect = Exception("protocol banner error")
with patch("media_library_viewer_api.services.task_runner.build_ssh_client", return_value=mock_client):
result = ssh_test({"host": "srv", "port": 22, "username": "u"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
assert "SSH banner" in result.detail
def test_missing_host_returns_value_error(self) -> None:
result = ssh_test({"host": "", "username": "u"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
# ---------------------------------------------------------------------------
@@ -263,7 +263,7 @@ class TestNextcloudTestConnection:
payload = SimpleNamespace(raise_for_status=lambda: None, json=lambda: {"version": "29.0.0"})
with patch("media_library_viewer_api.integrations.nextcloud.requests.get", return_value=payload):
result = nc_test({"base_url": "http://nc:80"}, {}, MagicMock())
assert result.ok is True
assert result.ok
assert result.evidence == "29.0.0"
def test_connection_error_translates(self) -> None:
@@ -272,5 +272,5 @@ class TestNextcloudTestConnection:
side_effect=requests.ConnectionError("refused"),
):
result = nc_test({"base_url": "http://nc:80"}, {}, MagicMock())
assert result.ok is False
assert not result.ok
assert "Connection refused" in result.detail