From 5e0bfcc2b0f6059cd7bbf3ad7230208c4615c8bf Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 14 Apr 2026 22:27:08 -0400 Subject: [PATCH] test(host_agent): pure-function tests for metric expansion Co-Authored-By: Claude Sonnet 4.6 --- .../host_agent/test_routes_expand_metrics.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/plugins/host_agent/test_routes_expand_metrics.py diff --git a/tests/plugins/host_agent/test_routes_expand_metrics.py b/tests/plugins/host_agent/test_routes_expand_metrics.py new file mode 100644 index 0000000..92bd300 --- /dev/null +++ b/tests/plugins/host_agent/test_routes_expand_metrics.py @@ -0,0 +1,98 @@ +"""Pure-function tests for host_agent metric expansion.""" +from datetime import datetime, timezone + +from plugins.host_agent.routes import _expand_sample_to_metrics + + +NOW = datetime(2026, 4, 14, 12, 0, 0, tzinfo=timezone.utc) + + +def _sample() -> dict: + return { + "ts": NOW.isoformat(), + "cpu_pct": 12.5, + "mem": { + "total_bytes": 16_000_000_000, + "used_bytes": 8_000_000_000, + "available_bytes": 8_000_000_000, + "swap_used_bytes": 0, + }, + "load": {"1m": 0.1, "5m": 0.2, "15m": 0.3}, + "uptime_secs": 1234, + "storage": [ + {"mount": "/", "total_bytes": 1000, "used_bytes": 400}, + {"mount": "/mnt/data", "total_bytes": 2000, "used_bytes": 1800}, + ], + } + + +def _by_metric(rows, name): + return [r for r in rows if r.metric_name == name] + + +def test_expand_emits_cpu_mem_load_uptime(): + rows = _expand_sample_to_metrics(_sample(), "testhost", NOW) + names = {r.metric_name for r in rows} + assert "cpu_pct" in names + assert "mem_used_pct" in names + assert "mem_available_bytes" in names + assert "swap_used_bytes" in names + assert "load_1m" in names and "load_5m" in names and "load_15m" in names + assert "uptime_secs" in names + + +def test_expand_source_module_and_recorded_at(): + rows = _expand_sample_to_metrics(_sample(), "testhost", NOW) + assert rows, "expected non-empty rows" + for r in rows: + assert r.source_module == "host_agent" + assert r.recorded_at == NOW + + +def test_expand_cpu_value_and_resource(): + cpu = _by_metric(_expand_sample_to_metrics(_sample(), "testhost", NOW), "cpu_pct") + assert len(cpu) == 1 + assert cpu[0].value == 12.5 + assert cpu[0].resource_name == "testhost" + + +def test_expand_mem_used_pct_math(): + # used = total - available = 50% + used = _by_metric(_expand_sample_to_metrics(_sample(), "testhost", NOW), "mem_used_pct") + assert len(used) == 1 + assert abs(used[0].value - 50.0) < 0.01 + + +def test_expand_per_mount_disk_metrics(): + rows = _expand_sample_to_metrics(_sample(), "testhost", NOW) + per_mount = _by_metric(rows, "disk_used_pct") + assert {r.resource_name for r in per_mount} == {"testhost:/", "testhost:/mnt/data"} + # Values: / is 40%, /mnt/data is 90% + by_res = {r.resource_name: r.value for r in per_mount} + assert abs(by_res["testhost:/"] - 40.0) < 0.01 + assert abs(by_res["testhost:/mnt/data"] - 90.0) < 0.01 + + +def test_expand_disk_worst_is_max(): + worst = _by_metric(_expand_sample_to_metrics(_sample(), "testhost", NOW), "disk_used_pct_worst") + assert len(worst) == 1 + assert worst[0].resource_name == "testhost" + assert abs(worst[0].value - 90.0) < 0.01 + + +def test_expand_skips_missing_cpu_and_load(): + sample = {"mem": {"total_bytes": 100, "available_bytes": 50}} + rows = _expand_sample_to_metrics(sample, "h", NOW) + names = {r.metric_name for r in rows} + assert "cpu_pct" not in names + assert "load_1m" not in names + assert "uptime_secs" not in names + assert "mem_used_pct" in names + + +def test_expand_empty_storage_emits_no_disk_worst(): + sample = {"cpu_pct": 1.0, "storage": []} + rows = _expand_sample_to_metrics(sample, "h", NOW) + names = {r.metric_name for r in rows} + assert "disk_used_pct_worst" not in names + assert "cpu_pct" in names