refactor: unify SSH machines as services
This commit is contained in:
@@ -257,8 +257,7 @@ class TestSettingsReset:
|
||||
assert payload["status"] == "reset"
|
||||
assert not media_db.exists()
|
||||
assert not media_wal.exists()
|
||||
assert store.get_machine("local") is None
|
||||
assert len(store.list_machines()) == 0
|
||||
assert store.list_services("remote_machine") == []
|
||||
|
||||
|
||||
# --- Files ---
|
||||
@@ -469,35 +468,6 @@ class TestJobs:
|
||||
# --- Monitoring ---
|
||||
|
||||
|
||||
class TestMonitoring:
|
||||
def test_prometheus_targets_empty(self, test_client):
|
||||
response = test_client.get("/api/monitoring/prometheus-targets")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
def test_prometheus_targets_returns_enabled_ssh_node_exporter(self, test_client):
|
||||
store = app.dependency_overrides[get_settings_store]()
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "remote1",
|
||||
"mode": "ssh",
|
||||
"enabled": True,
|
||||
"services": ["monitoring"],
|
||||
"host": "10.0.0.5",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
"node_exporter_port": 9200,
|
||||
"node_exporter_scrape_host": "1.2.3.4",
|
||||
}
|
||||
)
|
||||
response = test_client.get("/api/monitoring/prometheus-targets")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["targets"] == ["1.2.3.4:9200"]
|
||||
assert data[0]["labels"]["job"] == "node-exporter-remote"
|
||||
|
||||
|
||||
class TestResolveServiceRecord:
|
||||
"""Unit tests for resolve_service_record (service_id + first-enabled paths)."""
|
||||
|
||||
@@ -569,46 +539,6 @@ class TestResolveServiceRecord:
|
||||
assert resolve_service_record(store, "alertmanager", None) is None
|
||||
|
||||
|
||||
class TestSettingsMachines:
|
||||
def test_machine_appears_in_prometheus_targets(self, test_client):
|
||||
store = app.dependency_overrides[get_settings_store]()
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "remote1",
|
||||
"mode": "ssh",
|
||||
"enabled": True,
|
||||
"services": ["monitoring"],
|
||||
"host": "10.0.0.5",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
"node_exporter_port": 9200,
|
||||
"node_exporter_scrape_host": "1.2.3.4",
|
||||
}
|
||||
)
|
||||
targets = test_client.get("/api/monitoring/prometheus-targets").json()
|
||||
assert len(targets) == 1
|
||||
assert targets[0]["targets"] == ["1.2.3.4:9200"]
|
||||
|
||||
def test_delete_machine_removed_from_prometheus_targets(self, test_client):
|
||||
store = app.dependency_overrides[get_settings_store]()
|
||||
machine = store.upsert_machine(
|
||||
{
|
||||
"name": "remote1",
|
||||
"mode": "ssh",
|
||||
"enabled": True,
|
||||
"services": ["monitoring"],
|
||||
"host": "10.0.0.5",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
"node_exporter_port": 9200,
|
||||
"node_exporter_scrape_host": "1.2.3.4",
|
||||
}
|
||||
)
|
||||
response = test_client.delete(f"/api/settings/machines/{machine['id']}")
|
||||
assert response.status_code == 200
|
||||
assert test_client.get("/api/monitoring/prometheus-targets").json() == []
|
||||
|
||||
|
||||
def _am_service(name="Alertmanager", **config):
|
||||
cfg = {"base_url": "http://alertmanager:9093", "timeout_seconds": 5}
|
||||
cfg.update(config)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import sqlite3
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from media_library_viewer_api.services.secrets import decrypt_secrets, reset_encryption_key_cache
|
||||
from media_library_viewer_api.services.settings_store import SettingsStore
|
||||
|
||||
|
||||
def test_migrates_legacy_ssh_machine_and_ssh_task_service(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("MANAGE_ENCRYPTION_KEY", Fernet.generate_key().decode())
|
||||
reset_encryption_key_cache()
|
||||
db_path = tmp_path / "settings.sqlite"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.executescript("""
|
||||
CREATE TABLE monitoring_machines (
|
||||
id TEXT PRIMARY KEY, name TEXT, mode TEXT, enabled INTEGER,
|
||||
config_json TEXT, created_at INTEGER, updated_at INTEGER
|
||||
);
|
||||
CREATE TABLE services (
|
||||
id TEXT PRIMARY KEY, service_type TEXT, name TEXT, config_json TEXT,
|
||||
secrets_json TEXT, enabled INTEGER, created_at INTEGER, updated_at INTEGER
|
||||
);
|
||||
""")
|
||||
conn.execute(
|
||||
"INSERT INTO monitoring_machines VALUES (?, ?, 'ssh', 1, ?, 10, 11)",
|
||||
(
|
||||
"remote-1",
|
||||
"Storage",
|
||||
'{"host":"storage","port":2222,"username":"ops","ssh_private_key":"PRIVATE","ssh_private_key_passphrase":"phrase","password":"pw"}',
|
||||
),
|
||||
)
|
||||
conn.execute("INSERT INTO monitoring_machines VALUES (?, ?, 'local', 1, '{}', 10, 11)", ("local", "This machine"))
|
||||
conn.execute("INSERT INTO services VALUES ('task-service', 'ssh_tasks', 'Tasks', '{}', '{}', 1, 1, 1)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
store = SettingsStore(db_path)
|
||||
store.init_schema()
|
||||
remote = store.get_service("remote-1")
|
||||
assert remote and remote["service_type"] == "remote_machine"
|
||||
assert remote["config"] == {
|
||||
"host": "storage",
|
||||
"port": 2222,
|
||||
"username": "ops",
|
||||
"ssh_key_id": "legacy-key-remote-1",
|
||||
"timeout_seconds": 30,
|
||||
}
|
||||
assert decrypt_secrets(remote["secrets"]) == {"passphrase": "phrase", "password": "pw"}
|
||||
assert store.get_ssh_key("legacy-key-remote-1")["private_key"] == "PRIVATE"
|
||||
assert store.get_service("task-service")["service_type"] == "remote_machine"
|
||||
with store.connect() as check:
|
||||
assert (
|
||||
check.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='monitoring_machines'").fetchone()
|
||||
is None
|
||||
)
|
||||
store.init_schema()
|
||||
assert store.get_service("remote-1")["id"] == "remote-1"
|
||||
|
||||
|
||||
def test_migrates_task_default_service_id_to_service_id(tmp_path):
|
||||
db_path = tmp_path / "settings.sqlite"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE saved_tasks (
|
||||
id TEXT PRIMARY KEY, name TEXT NOT NULL, task_type TEXT NOT NULL,
|
||||
content TEXT NOT NULL, enabled INTEGER NOT NULL, default_service_id TEXT NOT NULL,
|
||||
notes TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute("INSERT INTO saved_tasks VALUES ('task-1', 'Check', 'shell', 'true', 1, 'remote-1', '', 1, 1)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
store = SettingsStore(db_path)
|
||||
store.init_schema()
|
||||
assert store.get_task("task-1")["service_id"] == "remote-1"
|
||||
with store.connect() as check:
|
||||
columns = {row[1] for row in check.execute("PRAGMA table_info(saved_tasks)")}
|
||||
assert "service_id" in columns
|
||||
assert "default_service_id" not in columns
|
||||
@@ -63,7 +63,7 @@ def test_registry_contains_eight_service_types():
|
||||
"alertmanager",
|
||||
"jellyfin",
|
||||
"nextcloud",
|
||||
"ssh_tasks",
|
||||
"remote_machine",
|
||||
"backups",
|
||||
"authentik",
|
||||
"qbittorrent",
|
||||
@@ -113,7 +113,7 @@ def test_definitions_declare_widget_kinds():
|
||||
assert get_service_definition("nextcloud").widget_kinds == []
|
||||
assert get_service_definition("authentik").widget_kinds == []
|
||||
assert {wk.kind for wk in get_service_definition("backups").widget_kinds} == {"summary"}
|
||||
assert {wk.kind for wk in get_service_definition("ssh_tasks").widget_kinds} == {"task_output"}
|
||||
assert {wk.kind for wk in get_service_definition("remote_machine").widget_kinds} == {"task_output"}
|
||||
|
||||
|
||||
def test_widget_kind_lookup():
|
||||
@@ -206,7 +206,7 @@ def test_list_service_types(client):
|
||||
"nextcloud",
|
||||
"prometheus",
|
||||
"qbittorrent",
|
||||
"ssh_tasks",
|
||||
"remote_machine",
|
||||
}
|
||||
|
||||
|
||||
@@ -539,7 +539,7 @@ def test_cascade_delete_removes_harness_data_across_concerns(tmp_path, monkeypat
|
||||
def test_record_and_list_service_task_runs(client):
|
||||
store = app.dependency_overrides[get_settings_store]()
|
||||
service = store.upsert_service(
|
||||
{"service_type": "ssh_tasks", "name": "box", "config": {"host": "h"}, "enabled": True}
|
||||
{"service_type": "remote_machine", "name": "box", "config": {"host": "h"}, "enabled": True}
|
||||
)
|
||||
store.record_service_task_run(
|
||||
{
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
"""Tests for Prometheus Node Exporter target discovery."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from media_library_viewer_api.services.settings_store import SettingsStore
|
||||
from media_library_viewer_api.services.targets import build_node_exporter_targets
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store(tmp_path: Path) -> SettingsStore:
|
||||
db = SettingsStore(tmp_path / "settings.sqlite")
|
||||
db.init_schema()
|
||||
return db
|
||||
|
||||
|
||||
class TestBuildNodeExporterTargets:
|
||||
def test_disabled_machine_excluded(self, store: SettingsStore):
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "remote1",
|
||||
"mode": "ssh",
|
||||
"host": "10.0.0.5",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": False,
|
||||
"node_exporter_port": 9200,
|
||||
}
|
||||
)
|
||||
assert build_node_exporter_targets(store) == []
|
||||
|
||||
def test_ssh_enabled_machine_included(self, store: SettingsStore):
|
||||
machine = store.upsert_machine(
|
||||
{
|
||||
"name": "remote1",
|
||||
"mode": "ssh",
|
||||
"host": "10.0.0.5",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
"node_exporter_port": 9200,
|
||||
"node_exporter_scrape_host": "1.2.3.4",
|
||||
}
|
||||
)
|
||||
targets = build_node_exporter_targets(store)
|
||||
assert len(targets) == 1
|
||||
assert targets[0]["targets"] == ["1.2.3.4:9200"]
|
||||
assert targets[0]["labels"]["machine_id"] == machine["id"]
|
||||
assert targets[0]["labels"]["machine_name"] == "remote1"
|
||||
assert targets[0]["labels"]["job"] == "node-exporter-remote"
|
||||
|
||||
def test_scrape_host_defaults_to_machine_host(self, store: SettingsStore):
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "remote2",
|
||||
"mode": "ssh",
|
||||
"host": "remote2.example.com",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
"node_exporter_port": 9100,
|
||||
}
|
||||
)
|
||||
targets = build_node_exporter_targets(store)
|
||||
assert targets[0]["targets"] == ["remote2.example.com:9100"]
|
||||
|
||||
def test_local_machine_excluded(self, store: SettingsStore):
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "This machine",
|
||||
"mode": "local",
|
||||
"host": "localhost",
|
||||
"username": "",
|
||||
"node_exporter_enabled": True,
|
||||
}
|
||||
)
|
||||
assert build_node_exporter_targets(store) == []
|
||||
|
||||
def test_missing_host_excluded(self, store: SettingsStore):
|
||||
store.upsert_machine(
|
||||
{
|
||||
"name": "remote3",
|
||||
"mode": "ssh",
|
||||
"host": "",
|
||||
"username": "u",
|
||||
"node_exporter_enabled": True,
|
||||
}
|
||||
)
|
||||
assert build_node_exporter_targets(store) == []
|
||||
@@ -437,18 +437,18 @@ async def test_ssh_task_adapter_missing_service():
|
||||
@pytest.mark.asyncio
|
||||
async def test_ssh_task_adapter_records_history_on_run(client):
|
||||
store = app.dependency_overrides[get_settings_store]()
|
||||
# Save a task and an ssh_tasks service instance.
|
||||
# Save a task and an remote_machine service instance.
|
||||
task = store.upsert_task(
|
||||
{
|
||||
"name": "echo",
|
||||
"task_type": "shell",
|
||||
"content": "echo hi",
|
||||
"enabled": True,
|
||||
"default_service_id": "",
|
||||
"service_id": "",
|
||||
}
|
||||
)
|
||||
service = store.upsert_service(
|
||||
{"service_type": "ssh_tasks", "name": "box", "config": {"host": "h", "username": "u"}, "enabled": True}
|
||||
{"service_type": "remote_machine", "name": "box", "config": {"host": "h", "username": "u"}, "enabled": True}
|
||||
)
|
||||
|
||||
fake_result = SimpleNamespace(exit_status=0, stdout="hi\n", stderr="")
|
||||
@@ -458,7 +458,7 @@ async def test_ssh_task_adapter_records_history_on_run(client):
|
||||
|
||||
adapter = SshTaskWidgetSource()
|
||||
service_record = ServiceRecord(
|
||||
id=service["id"], service_type="ssh_tasks", name="box", config={"host": "h", "username": "u"}
|
||||
id=service["id"], service_type="remote_machine", name="box", config={"host": "h", "username": "u"}
|
||||
)
|
||||
with (
|
||||
patch("media_library_viewer_api.widgets.sources.get_settings_store", return_value=store),
|
||||
|
||||
Reference in New Issue
Block a user