feat(cli): castle mesh command + shared-config API + NATS integration tests
- castle mesh status|nodes|config (list/get/set) — hits the local castle-api;
surfaces the shared-config write path (authority-gated)
- API: GET/PUT /mesh/config/{key:path}, GET /mesh/config; nats_client gains
list_shared_config
- tests: NATS integration suite against a real broker via a docker fixture
(peer discovery, presence, graceful-offline, shared config, secrets-off-wire);
plus HTTP-layer /mesh/config tests. Closes the runtime-coverage gap.
- AGENTS.md §8 updated for NATS + the mesh CLI
100 api + 211 core tests pass.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
"""Test fixtures for castle-api."""
|
||||
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
@@ -17,6 +21,48 @@ from castle_core.registry import (
|
||||
)
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
s = socket.socket()
|
||||
s.bind(("", 0))
|
||||
port = s.getsockname()[1]
|
||||
s.close()
|
||||
return port
|
||||
|
||||
|
||||
def _docker_available() -> bool:
|
||||
try:
|
||||
return subprocess.run(
|
||||
["docker", "info"], capture_output=True, timeout=5
|
||||
).returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nats_url() -> Generator[str, None, None]:
|
||||
"""A throwaway NATS+JetStream broker in docker (fresh per test for clean
|
||||
buckets). Skips if docker is unavailable."""
|
||||
if not _docker_available():
|
||||
pytest.skip("docker unavailable — skipping NATS integration tests")
|
||||
cport, mport = _free_port(), _free_port()
|
||||
name = f"castle-test-nats-{cport}"
|
||||
subprocess.run(
|
||||
["docker", "run", "-d", "--rm", "--name", name,
|
||||
"-p", f"{cport}:4222", "-p", f"{mport}:8222", "nats:2", "-js", "-m", "8222"],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
try:
|
||||
for _ in range(50): # wait for readiness
|
||||
try:
|
||||
urllib.request.urlopen(f"http://127.0.0.1:{mport}/healthz", timeout=1)
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
yield f"nats://127.0.0.1:{cport}"
|
||||
finally:
|
||||
subprocess.run(["docker", "rm", "-f", name], capture_output=True)
|
||||
|
||||
|
||||
def _write_castle_config(root: Path, config: dict) -> None:
|
||||
"""Scatter a nested castle config dict into the directory-per-resource layout."""
|
||||
globals_data = {k: v for k, v in config.items() if k in ("gateway", "repo")}
|
||||
|
||||
Reference in New Issue
Block a user