Files
Nexus/tests/test_watch_metrics.py
T
Nexus Agent adee25deb6 fix(watch): repair SSH probe script broken by embedded quote in os_release parse
Use chr(34) inside the bash-wrapped python -c probe so monitor slots work again after hardware detail rollout.
2026-06-13 16:10:32 +08:00

228 lines
6.7 KiB
Python

"""Watch metrics unit tests — rates, redis IO skip, failure samples."""
import pytest
from server.utils.watch_metrics import (
WatchProbeSample,
WATCH_PIN_TTL_DEFAULT_MINUTES,
apply_rates_to_sample,
compute_rates,
failure_sample,
merge_redis_and_ssh,
normalize_watch_ttl_minutes,
parse_ssh_watch_payload,
redis_sample_has_io,
redis_sample_has_basic_metrics,
sanitize_processes,
PROBE_OK,
)
def test_compute_rates_basic():
from server.utils.watch_metrics import _RateState
prev = _RateState(
net_bytes_sent=1000,
net_bytes_recv=2000,
disk_read_bytes=500,
disk_write_bytes=800,
recorded_at_ts=100.0,
)
up, down, dr, dw = compute_rates(
prev,
net_bytes_sent=1500,
net_bytes_recv=2600,
disk_read_bytes=700,
disk_write_bytes=900,
now_ts=105.0,
)
assert up == 100
assert down == 120
assert dr == 40
assert dw == 20
def test_compute_rates_no_prev():
up, down, dr, dw = compute_rates(None, net_bytes_sent=1, net_bytes_recv=1, disk_read_bytes=1, disk_write_bytes=1, now_ts=10)
assert up is None and down is None and dr is None and dw is None
def test_redis_sample_has_basic_metrics():
basic = WatchProbeSample(probe_status=PROBE_OK, source="redis", cpu_pct=10)
assert redis_sample_has_basic_metrics(basic) is True
assert redis_sample_has_io(basic) is False
assert redis_sample_has_basic_metrics(None) is False
def test_parse_json_stdout_with_prefix_noise():
from server.infrastructure.ssh.remote_probe import _parse_json_stdout
stdout = "Warning: something\nOK {\"status\": \"ok\", \"cpu_usage\": 1.0}\n"
parsed = _parse_json_stdout(stdout)
assert parsed is not None
assert parsed.get("status") == "ok"
def test_redis_sample_has_io():
complete = WatchProbeSample(
probe_status=PROBE_OK,
source="redis",
cpu_pct=10,
mem_pct=20,
net_bytes_sent=1,
net_bytes_recv=2,
disk_read_bytes=3,
disk_write_bytes=4,
)
incomplete = WatchProbeSample(probe_status=PROBE_OK, source="redis", cpu_pct=10, mem_pct=20)
assert redis_sample_has_io(complete) is True
assert redis_sample_has_io(incomplete) is False
def test_failure_sample_null_metrics():
s = failure_sample(status="ssh_timeout", source="ssh", error="timeout")
assert s.cpu_pct is None
assert s.probe_status == "ssh_timeout"
def test_parse_ssh_watch_payload():
data = {
"cpu_usage": 12.4,
"mem_usage": 45.2,
"disk_usage": 61.0,
"load_avg": [0.8, 0.9, 1.0],
"net_io": {"bytes_sent": 100, "bytes_recv": 200},
"disk_io": {"read_bytes": 300, "write_bytes": 400},
"top_processes": [{"pid": 1, "name": "nginx", "cpu_pct": 2.0, "mem_pct": 1.0}],
}
sample = parse_ssh_watch_payload(data)
assert sample.cpu_pct == 12
assert sample.net_bytes_sent == 100
assert sample.processes_json and sample.processes_json[0]["name"] == "nginx"
def test_sanitize_processes_truncates_name():
raw = [{"pid": 1, "name": "x" * 100, "cpu_pct": 1}]
out = sanitize_processes(raw)
assert out and len(out[0]["name"]) == 80
def test_apply_rates_to_sample():
from server.utils.watch_metrics import _RateState
sample = WatchProbeSample(
probe_status=PROBE_OK,
source="ssh",
net_bytes_sent=2000,
net_bytes_recv=3000,
disk_read_bytes=4000,
disk_write_bytes=5000,
)
prev = _RateState(
net_bytes_sent=1000,
net_bytes_recv=2000,
disk_read_bytes=3000,
disk_write_bytes=4000,
recorded_at_ts=0.0,
)
apply_rates_to_sample(sample, prev, 10.0)
assert sample.net_up_bps == 100
assert sample.net_down_bps == 100
def test_parse_ssh_watch_payload_includes_hardware_specs():
sample = parse_ssh_watch_payload(
{
"cpu_usage": 12.5,
"mem_usage": 40,
"disk_usage": 55,
"cpu_cores": 8,
"cpu_cores_physical": 4,
"cpu_model": "Intel Xeon",
"memory_total_gb": 16,
"memory_used_gb": 6.4,
"memory_available_gb": 9.6,
"disk_total_gb": 500,
"disk_used_gb": 275,
"disk_free_gb": 225,
"swap_total_gb": 4,
"swap_used_gb": 0.5,
"swap_pct": 12.5,
"os_release": "Ubuntu 22.04 LTS",
"uptime_seconds": 86400,
}
)
assert sample.cpu_cores == 8
assert sample.cpu_cores_physical == 4
assert sample.cpu_model == "Intel Xeon"
assert sample.memory_used_gb == 6.4
assert sample.disk_free_gb == 225.0
assert sample.os_release == "Ubuntu 22.04 LTS"
assert sample.uptime_seconds == 86400
def test_fill_hardware_gaps_from_percent():
from server.utils.watch_metrics import fill_hardware_gaps
sample = WatchProbeSample(
probe_status=PROBE_OK,
source="redis",
mem_pct=50,
disk_pct=25,
memory_total_gb=16.0,
disk_total_gb=400.0,
)
fill_hardware_gaps(sample)
assert sample.memory_used_gb == 8.0
assert sample.memory_available_gb == 8.0
assert sample.disk_used_gb == 100.0
assert sample.disk_free_gb == 300.0
def test_merge_redis_and_ssh_prefers_ssh_io():
redis_s = WatchProbeSample(probe_status=PROBE_OK, source="redis", cpu_pct=10, mem_pct=20)
ssh_s = WatchProbeSample(
probe_status=PROBE_OK,
source="ssh",
cpu_pct=11,
cpu_cores=4,
memory_total_gb=8.0,
disk_total_gb=120.0,
net_bytes_sent=99,
net_bytes_recv=88,
disk_read_bytes=77,
disk_write_bytes=66,
)
merged = merge_redis_and_ssh(redis_s, ssh_s)
assert merged.source == "mixed"
assert merged.cpu_pct == 10
assert merged.cpu_cores == 4
assert merged.memory_total_gb == 8.0
assert merged.disk_total_gb == 120.0
assert merged.net_bytes_sent == 99
def test_ssh_watch_metrics_cmd_runs_locally():
import subprocess
from server.infrastructure.ssh.remote_probe import SSH_WATCH_METRICS_CMD, _parse_json_stdout
result = subprocess.run(
["bash", "-c", SSH_WATCH_METRICS_CMD],
capture_output=True,
text=True,
timeout=20,
)
parsed = _parse_json_stdout(result.stdout)
assert parsed is not None
assert parsed.get("status") == "ok"
assert parsed.get("cpu_cores") is not None
def test_normalize_watch_ttl_minutes():
assert normalize_watch_ttl_minutes(30) == 30
assert normalize_watch_ttl_minutes(60) == 60
assert normalize_watch_ttl_minutes(1440) == 1440
assert normalize_watch_ttl_minutes(8) == 480
assert normalize_watch_ttl_minutes(99) == WATCH_PIN_TTL_DEFAULT_MINUTES