Files
FabledSteward/tests/plugins/host_agent/test_agent_build_payload.py
T
bvandeusen af60ca446d
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m8s
fix(plugins): complete steward rename across bundled plugins; clear lint debt
The fabledscryer->steward rename had only ever reached host_agent. The other
five bundled plugins (http, snmp, traefik, unifi, docker) still imported
`from fabledscryer.*` (package no longer exists) and read FABLEDSCRYER_* env
vars — so every one of them was broken at import since the original rebrand.
CI stayed green only because none are enabled by default and migrations don't
import plugin modules. Now that they version in-tree, complete the rename:
- fabledscryer.* -> steward.* imports across all five plugins
- FABLEDSCRYER_* -> STEWARD_* in plugin migration env.py files
- author/repository/homepage + user-facing 'Fabled Scryer' strings -> Steward
- snmp/scheduler.py: also drop dead `now`/datetime; record_metric from steward

Adds tests/test_no_legacy_names.py — fails if 'scryer'/'roundtable' ever
reappear in shipped code (the drift bit twice; this stops a third time).

Also clears pre-existing ruff lint debt (unused imports, semicolon statements,
mid-file import) surfaced by the new lint lane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:22:20 -04:00

115 lines
4.1 KiB
Python

import urllib.error
from unittest.mock import patch
from plugins.host_agent import agent as a
def test_build_sample_shape():
fake_mem = {"total_bytes": 100, "used_bytes": 40,
"available_bytes": 60, "swap_used_bytes": 0}
fake_load = {"1m": 0.1, "5m": 0.2, "15m": 0.3}
fake_storage = [{"mount": "/", "total_bytes": 1000, "used_bytes": 400}]
with patch.object(a, "collect_cpu", return_value=12.5), \
patch.object(a, "collect_memory", return_value=fake_mem), \
patch.object(a, "collect_load", return_value=fake_load), \
patch.object(a, "collect_uptime", return_value=1234), \
patch.object(a, "collect_storage", return_value=fake_storage):
sample = a.build_sample(mounts=["/"])
assert sample["cpu_pct"] == 12.5
assert sample["mem"]["used_bytes"] == 40
assert sample["load"]["1m"] == 0.1
assert sample["uptime_secs"] == 1234
assert sample["storage"][0]["mount"] == "/"
assert "ts" in sample
# ISO-8601 UTC string
assert "T" in sample["ts"]
def test_build_sample_tolerates_collector_failure():
# If a collector raises, the sample should still be built with None for that field.
def _boom():
raise RuntimeError("no /proc")
with patch.object(a, "collect_cpu", side_effect=_boom), \
patch.object(a, "collect_memory", return_value={"total_bytes": 1, "used_bytes": 0,
"available_bytes": 1, "swap_used_bytes": 0}), \
patch.object(a, "collect_load", return_value={"1m": 0.0, "5m": 0.0, "15m": 0.0}), \
patch.object(a, "collect_uptime", return_value=0), \
patch.object(a, "collect_storage", return_value=[]):
sample = a.build_sample(mounts=["/"])
assert sample["cpu_pct"] is None
assert sample["mem"]["total_bytes"] == 1
def test_build_payload_wraps_samples():
metadata = {"kernel": "6.8", "distro": "Ubuntu", "arch": "x86_64"}
payload = a.build_payload(
samples=[{"ts": "2026-04-14T00:00:00+00:00", "cpu_pct": 5.0}],
hostname="myhost",
metadata=metadata,
)
assert payload["agent_version"] == a.AGENT_VERSION
assert payload["hostname"] == "myhost"
assert payload["metadata"] == metadata
assert len(payload["samples"]) == 1
assert payload["samples"][0]["cpu_pct"] == 5.0
def test_post_payload_success():
from plugins.host_agent import agent as a
captured = {}
class FakeResp:
status = 200
def __enter__(self): return self
def __exit__(self, *a): return False
def read(self): return b'{"ok":true}'
def fake_urlopen(req, timeout=10):
captured["url"] = req.full_url
captured["headers"] = dict(req.header_items())
captured["body"] = req.data
return FakeResp()
with patch.object(a.urllib.request, "urlopen", side_effect=fake_urlopen):
ok, status = a.post_payload("https://x", "tok", {"hello": "world"})
assert ok is True
assert status == 200
assert captured["url"] == "https://x/plugins/host_agent/ingest"
assert captured["headers"]["Authorization"] == "Bearer tok"
def test_post_payload_http_error_returns_status():
from plugins.host_agent import agent as a
def raise_401(req, timeout=10):
raise urllib.error.HTTPError(req.full_url, 401, "unauthorized", {}, None)
with patch.object(a.urllib.request, "urlopen", side_effect=raise_401):
ok, status = a.post_payload("https://x", "tok", {})
assert ok is False
assert status == 401
def test_post_payload_network_error_returns_none_status():
from plugins.host_agent import agent as a
def raise_url(req, timeout=10):
raise urllib.error.URLError("conn refused")
with patch.object(a.urllib.request, "urlopen", side_effect=raise_url):
ok, status = a.post_payload("https://x", "tok", {})
assert ok is False
assert status is None
def test_backoff_schedule():
from plugins.host_agent.agent import next_backoff
assert next_backoff(0) == 30
assert next_backoff(30) == 60
assert next_backoff(60) == 120
assert next_backoff(120) == 240
assert next_backoff(240) == 300 # capped
assert next_backoff(300) == 300