feat: Implement multi-node support with MQTT and mDNS for service discovery and coordination

This commit is contained in:
2026-02-23 02:30:12 -08:00
parent eeaa5045d0
commit 3343e955fd
29 changed files with 1878 additions and 35 deletions

View File

@@ -0,0 +1,91 @@
"""Tests for nodes endpoints."""
from pathlib import Path
from fastapi.testclient import TestClient
from castle_core.registry import DeployedComponent, NodeConfig, NodeRegistry
from castle_api.mesh import MeshStateManager
class TestNodesList:
"""GET /nodes endpoint tests."""
def test_returns_local_node(self, client: TestClient) -> None:
"""Always returns the local node."""
response = client.get("/nodes")
assert response.status_code == 200
data = response.json()
assert len(data) >= 1
local = data[0]
assert local["hostname"] == "test-node"
assert local["is_local"] is True
assert local["online"] is True
def test_local_node_counts(self, client: TestClient) -> None:
"""Local node has correct deployment counts."""
response = client.get("/nodes")
data = response.json()
local = data[0]
assert local["deployed_count"] == 1 # test-svc
assert local["service_count"] == 1
def test_includes_remote_nodes(self, client: TestClient, registry_path: Path) -> None:
"""Remote nodes from mesh state are included."""
import castle_api.mesh as mesh_mod
original = mesh_mod.mesh_state
try:
mgr = MeshStateManager()
remote_reg = NodeRegistry(
node=NodeConfig(hostname="devbox", gateway_port=9000),
deployed={
"remote-svc": DeployedComponent(
runner="python",
run_cmd=["svc"],
port=9050,
category="service",
),
},
)
mgr.update_node("devbox", remote_reg)
mesh_mod.mesh_state = mgr
# Also patch the reference in the nodes module
import castle_api.nodes as nodes_mod
nodes_mod.mesh_state = mgr
response = client.get("/nodes")
data = response.json()
hostnames = [n["hostname"] for n in data]
assert "devbox" in hostnames
devbox = next(n for n in data if n["hostname"] == "devbox")
assert devbox["is_local"] is False
assert devbox["deployed_count"] == 1
finally:
mesh_mod.mesh_state = original
import castle_api.nodes as nodes_mod2
nodes_mod2.mesh_state = original
class TestNodeDetail:
"""GET /nodes/{hostname} endpoint tests."""
def test_local_node_detail(self, client: TestClient) -> None:
"""Returns local node detail with deployed components."""
response = client.get("/nodes/test-node")
assert response.status_code == 200
data = response.json()
assert data["hostname"] == "test-node"
assert data["is_local"] is True
assert len(data["deployed"]) == 1
assert data["deployed"][0]["id"] == "test-svc"
assert data["deployed"][0]["node"] == "test-node"
def test_unknown_node_returns_404(self, client: TestClient) -> None:
"""Returns 404 for unknown hostname."""
response = client.get("/nodes/nonexistent")
assert response.status_code == 404