Files
wild-pc/castle-api/tests/test_mesh.py
Paul Payne 3f3b88f17b refactor: Align vocabulary — component → program / deployment
Canonical terms (see docs/registry.md glossary):
- program: any catalog entry (tool/daemon/frontend) — the software
- service / job: how a program is deployed (systemd .service / .timer)
- deployment: umbrella for the unified service+job+program view

Changes:
- core: ServiceSpec/JobSpec component: → program: (component accepted as
  back-compat validation_alias); DeployedComponent → Deployment.
- api: ComponentSummary/Detail → DeploymentSummary/Detail; GET /components
  → /deployments; GatewayRoute.component → program; GatewayInfo
  .component_count → deployment_count; ServiceActionResponse/action keys
  component → program.
- app: matching type renames, /components → /deployments hook, route
  /component/:name → /deployment/:name, GatewayRoute.program.
- docs: component-registry.md → registry.md (+ canonical glossary);
  CLAUDE.md endpoint list refreshed (drop removed /tools, add typed
  program/service/job + config routes).

Tests: core 92, cli 24, api 52 green; app build clean; ruff clean.
2026-06-14 11:05:22 -07:00

105 lines
3.4 KiB
Python

"""Tests for MeshStateManager."""
import time
from castle_core.registry import Deployment, NodeConfig, NodeRegistry
from castle_api.mesh import STALE_TTL_SECONDS, MeshStateManager, RemoteNode
def _make_registry(hostname: str, deployed: dict | None = None) -> NodeRegistry:
return NodeRegistry(
node=NodeConfig(hostname=hostname, gateway_port=9000),
deployed=deployed or {},
)
class TestRemoteNode:
"""RemoteNode staleness tracking."""
def test_fresh_node_not_stale(self) -> None:
node = RemoteNode(registry=_make_registry("a"))
assert not node.is_stale
def test_old_node_is_stale(self) -> None:
node = RemoteNode(
registry=_make_registry("a"),
last_seen=time.time() - STALE_TTL_SECONDS - 1,
)
assert node.is_stale
class TestMeshStateManager:
"""MeshStateManager add/remove/stale operations."""
def test_update_and_get(self) -> None:
mgr = MeshStateManager()
reg = _make_registry("devbox")
mgr.update_node("devbox", reg)
node = mgr.get_node("devbox")
assert node is not None
assert node.registry.node.hostname == "devbox"
assert node.online is True
def test_set_offline(self) -> None:
mgr = MeshStateManager()
mgr.update_node("devbox", _make_registry("devbox"))
mgr.set_offline("devbox")
node = mgr.get_node("devbox")
assert node is not None
assert node.online is False
def test_remove_node(self) -> None:
mgr = MeshStateManager()
mgr.update_node("devbox", _make_registry("devbox"))
mgr.remove_node("devbox")
assert mgr.get_node("devbox") is None
def test_remove_nonexistent_is_safe(self) -> None:
mgr = MeshStateManager()
mgr.remove_node("nope") # should not raise
def test_all_nodes_excludes_stale(self) -> None:
mgr = MeshStateManager()
mgr.update_node("fresh", _make_registry("fresh"))
mgr._nodes["stale"] = RemoteNode(
registry=_make_registry("stale"),
last_seen=time.time() - STALE_TTL_SECONDS - 1,
)
result = mgr.all_nodes()
assert "fresh" in result
assert "stale" not in result
def test_all_nodes_includes_stale_when_requested(self) -> None:
mgr = MeshStateManager()
mgr._nodes["stale"] = RemoteNode(
registry=_make_registry("stale"),
last_seen=time.time() - STALE_TTL_SECONDS - 1,
)
result = mgr.all_nodes(include_stale=True)
assert "stale" in result
def test_prune_stale(self) -> None:
mgr = MeshStateManager()
mgr.update_node("fresh", _make_registry("fresh"))
mgr._nodes["stale"] = RemoteNode(
registry=_make_registry("stale"),
last_seen=time.time() - STALE_TTL_SECONDS - 1,
)
pruned = mgr.prune_stale()
assert pruned == ["stale"]
assert mgr.get_node("stale") is None
assert mgr.get_node("fresh") is not None
def test_update_replaces_existing(self) -> None:
mgr = MeshStateManager()
mgr.update_node("devbox", _make_registry("devbox"))
new_reg = _make_registry(
"devbox",
{"svc": Deployment(runner="python", run_cmd=["svc"])},
)
mgr.update_node("devbox", new_reg)
node = mgr.get_node("devbox")
assert node is not None
assert "svc" in node.registry.deployed