68 lines
2.9 KiB
Python
68 lines
2.9 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import importlib.util
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
SCRIPT = Path(__file__).parents[1] / "scripts" / "pi_doctor_collector.py"
|
||
|
|
SPEC = importlib.util.spec_from_file_location("pi_doctor_collector", SCRIPT)
|
||
|
|
collector = importlib.util.module_from_spec(SPEC)
|
||
|
|
assert SPEC and SPEC.loader
|
||
|
|
SPEC.loader.exec_module(collector)
|
||
|
|
|
||
|
|
|
||
|
|
def snapshot(**overrides):
|
||
|
|
value = {
|
||
|
|
"system": {"timestamp": "2026-10-28T12:00:00+00:00", "cpu_percent": 10, "memory_percent": 20, "temperature_c": 50},
|
||
|
|
"storage": {"root_percent": 30, "mounts": []},
|
||
|
|
"docker": {"available": True, "containers": []},
|
||
|
|
"services": {"codexpi": {"configured": True, "available": True}, "jellyfin": {"configured": False}},
|
||
|
|
}
|
||
|
|
for section, values in overrides.items():
|
||
|
|
value[section].update(values)
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
def test_detects_thresholds_mounts_and_services_without_repairing():
|
||
|
|
value = snapshot(
|
||
|
|
system={"cpu_percent": 99, "temperature_c": 80},
|
||
|
|
storage={"root_percent": 96, "mounts": [{"path": "/mnt/nas", "state": "unavailable", "error": "timeout"}]},
|
||
|
|
services={"codexpi": {"configured": True, "available": False}, "jellyfin": {"configured": False}},
|
||
|
|
)
|
||
|
|
|
||
|
|
incidents = collector.detect_incidents(value)
|
||
|
|
|
||
|
|
assert {item["fingerprint"] for item in incidents} == {
|
||
|
|
"cpu-high", "root-disk-high", "temperature-high", "mount:/mnt/nas", "service:codexpi",
|
||
|
|
}
|
||
|
|
assert next(item for item in incidents if item["fingerprint"] == "cpu-high")["severity"] == "critical"
|
||
|
|
assert not hasattr(collector, "repair")
|
||
|
|
|
||
|
|
|
||
|
|
def test_spool_survives_failure_then_flushes_in_order(tmp_path, monkeypatch):
|
||
|
|
first = collector.enqueue(tmp_path, snapshot())
|
||
|
|
second_value = snapshot(system={"timestamp": "2026-10-28T12:01:00+00:00"})
|
||
|
|
second = collector.enqueue(tmp_path, second_value)
|
||
|
|
delivered = []
|
||
|
|
monkeypatch.setattr(collector, "deliver", lambda url, token, value: delivered.append(value["system"]["timestamp"]))
|
||
|
|
|
||
|
|
assert first.exists() and second.exists()
|
||
|
|
assert collector.flush(tmp_path, "https://codexpi.test", "token") == 2
|
||
|
|
assert delivered == ["2026-10-28T12:00:00+00:00", "2026-10-28T12:01:00+00:00"]
|
||
|
|
assert list(tmp_path.glob("*.json")) == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_delivery_posts_snapshot_then_real_detected_incidents(monkeypatch):
|
||
|
|
requests = []
|
||
|
|
monkeypatch.setattr(collector, "_post", lambda url, token, payload: requests.append((url, token, payload)))
|
||
|
|
value = snapshot(system={"memory_percent": 98})
|
||
|
|
|
||
|
|
collector.deliver("https://codexpi.test/", "secret-token", value)
|
||
|
|
|
||
|
|
assert requests[0][0].endswith("/api/pi-doctor/snapshots/import")
|
||
|
|
assert requests[0][2] == {"snapshot": value}
|
||
|
|
assert requests[1][0].endswith("/api/pi-doctor/incidents/import")
|
||
|
|
assert requests[1][2]["incidents"][0]["fingerprint"] == "memory-high"
|
||
|
|
assert {request[1] for request in requests} == {"secret-token"}
|