"""Unit tests for the shared liveness helper (thresholds + tier state machine).""" from __future__ import annotations import sys import time from datetime import datetime, timezone from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) import liveness from liveness import ( compute_liveness, ttls_for, liveness_to_status, node_health, degrade_for_node, parse_ts, FRESH, STALE, DEAD, UNKNOWN, ) # --- parse_ts --------------------------------------------------------------- def test_parse_ts_int_float_iso_none(): assert abs(parse_ts(1000) - 1000.0) < 0.001 assert abs(parse_ts(1000.5) - 1000.5) < 0.001 iso = "2026-06-01T00:00:00Z" assert abs(parse_ts(iso) - datetime(2026, 6, 1, tzinfo=timezone.utc).timestamp()) < 1 assert parse_ts(None) == 0.0 assert parse_ts("garbage") == 0.0 # --- compute_liveness (default TTLs: fresh<=180, dead>600) ------------------ def test_compute_liveness_fresh(): assert compute_liveness(time.time() - 60) == FRESH def test_compute_liveness_stale(): assert compute_liveness(time.time() - 300) == STALE def test_compute_liveness_dead(): assert compute_liveness(time.time() - 700) == DEAD def test_compute_liveness_boundaries(): now = 1_000_000 assert compute_liveness(now - 180, now=now) == FRESH # exactly fresh ceiling assert compute_liveness(now - 181, now=now) == STALE assert compute_liveness(now - 600, now=now) == STALE # exactly dead floor assert compute_liveness(now - 601, now=now) == DEAD def test_compute_liveness_unknown_on_missing(): assert compute_liveness(None) == UNKNOWN assert compute_liveness("garbage") == UNKNOWN assert compute_liveness(0) == UNKNOWN def test_compute_liveness_iso_string(): iso = datetime.fromtimestamp(time.time() - 700, tz=timezone.utc).isoformat() assert compute_liveness(iso) == DEAD # --- ttls_for --------------------------------------------------------------- def test_ttls_for_default_vs_remote(): assert ttls_for("vps", ["control-plane"]) == liveness.DEFAULT_TTLS assert ttls_for("chelsty-infra", []) == liveness.REMOTE_TTLS # by name assert ttls_for("anything", ["remote"]) == liveness.REMOTE_TTLS # by role def test_remote_node_stale_where_default_would_be_dead(): now = 1_000_000 ts = now - 1200 # > default dead (600) and > remote fresh (900), < remote dead (3600) assert compute_liveness(ts, now=now, ttls=ttls_for("chelsty-infra")) == STALE assert compute_liveness(ts, now=now, ttls=ttls_for("vps")) == DEAD # --- liveness_to_status ----------------------------------------------------- def test_liveness_to_status(): assert liveness_to_status(FRESH) == "online" assert liveness_to_status(STALE) == "stale" assert liveness_to_status(DEAD) == "offline" assert liveness_to_status(UNKNOWN) is None # --- node_health (read-time safety net) ------------------------------------- def test_node_health_fresh_online_is_nominal(): info = {"status": "online", "last_seen": time.time() - 30, "roles": []} assert node_health(info, "vps") == "nominal" def test_node_health_frozen_online_but_dead_is_error(): """Observer dead → status frozen 'online' but last_seen old → error (the net).""" info = {"status": "online", "last_seen": time.time() - 700, "roles": []} assert node_health(info, "vps") == "error" def test_node_health_stale_is_degraded(): info = {"status": "online", "last_seen": time.time() - 300, "roles": []} assert node_health(info, "vps") == "degraded" def test_node_health_unknown_freshness_does_not_downgrade(): info = {"status": "online", "last_seen": None, "roles": []} assert node_health(info, "vps") == "nominal" def test_node_health_disk_pressure_degrades_fresh_node(): info = {"status": "online", "last_seen": time.time() - 30, "roles": [], "disk_pressure": "high"} assert node_health(info, "vps") == "degraded" def test_node_health_takes_worse_of_status_and_freshness(): # status already offline, freshness fresh → still error (worse wins) info = {"status": "offline", "last_seen": time.time() - 5, "roles": []} assert node_health(info, "vps") == "error" # --- degrade_for_node (service cascade) ------------------------------------- def test_degrade_for_node(): assert degrade_for_node("nominal", DEAD) == "error" assert degrade_for_node("nominal", STALE) == "degraded" assert degrade_for_node("nominal", FRESH) == "nominal" # never upgrades a service that is already worse assert degrade_for_node("error", STALE) == "error" assert degrade_for_node("error", FRESH) == "error"