Rename Castle -> Wild PC across the repo

Repo-side rename only (Phases 1-3 of the migration plan); the live box
(~/.castle, systemd units, /data/castle, domains) is a separate cutover.

- Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api),
  dist names, entry point `wildpc = wildpc_cli.main:main`.
- Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*.
- Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml ->
  wildpc.yaml, /data/castle -> /data/wildpc.
- Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc.
- Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend.
- Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated;
  redundant nested uv.lock files dropped (workspace root lock is authoritative).

Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds.
Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
This commit is contained in:
2026-07-18 22:55:08 -07:00
parent 25d7a522cc
commit 05b28cb584
190 changed files with 2428 additions and 3337 deletions

View File

@@ -1,4 +1,4 @@
"""Shared fixtures for castle CLI tests."""
"""Shared fixtures for wildpc CLI tests."""
from __future__ import annotations
@@ -51,11 +51,11 @@ def _store_for(spec: dict) -> str:
]
def _write_castle_config(root: Path, config: dict) -> None:
"""Scatter a nested castle config dict into the on-disk layout: castle.yaml globals,
def _write_wildpc_config(root: Path, config: dict) -> None:
"""Scatter a nested wildpc config dict into the on-disk layout: wildpc.yaml globals,
programs/<name>.yaml, and deployments/<kind>/<name>.yaml (fields modernized)."""
globals_data = {k: v for k, v in config.items() if k in ("gateway", "repo")}
(root / "castle.yaml").write_text(yaml.dump(globals_data, default_flow_style=False))
(root / "wildpc.yaml").write_text(yaml.dump(globals_data, default_flow_style=False))
programs = config.get("programs") or {}
if programs:
(root / "programs").mkdir(parents=True, exist_ok=True)
@@ -70,8 +70,8 @@ def _write_castle_config(root: Path, config: dict) -> None:
@pytest.fixture
def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a temporary castle root with directory-per-resource config."""
def wildpc_root(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a temporary wildpc root with directory-per-resource config."""
config = {
"gateway": {"port": 18000},
"programs": {
@@ -129,7 +129,7 @@ def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
},
},
}
_write_castle_config(tmp_path, config)
_write_wildpc_config(tmp_path, config)
# Create project directories
svc_dir = tmp_path / "test-svc"
@@ -143,9 +143,9 @@ def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
@pytest.fixture
def castle_home(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a temporary ~/.castle directory."""
home = tmp_path / ".castle"
def wildpc_home(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a temporary ~/.wildpc directory."""
home = tmp_path / ".wildpc"
home.mkdir()
(home / "generated").mkdir()
(home / "secrets").mkdir()

View File

@@ -1,4 +1,4 @@
"""Tests for castle add — adopting existing repos as programs."""
"""Tests for wildpc add — adopting existing repos as programs."""
from __future__ import annotations
@@ -6,17 +6,17 @@ from argparse import Namespace
from pathlib import Path
from unittest.mock import patch
from castle_cli.config import load_config
from wildpc_cli.config import load_config
def _run_add(castle_root: Path, **kwargs: object) -> object:
def _run_add(wildpc_root: Path, **kwargs: object) -> object:
with (
patch("castle_cli.commands.add.load_config") as mock_load,
patch("castle_cli.commands.add.save_config"),
patch("wildpc_cli.commands.add.load_config") as mock_load,
patch("wildpc_cli.commands.add.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
mock_load.return_value = config
from castle_cli.commands.add import run_add
from wildpc_cli.commands.add import run_add
args = Namespace(name=None, description="", **kwargs)
rc = run_add(args)
@@ -24,49 +24,49 @@ def _run_add(castle_root: Path, **kwargs: object) -> object:
class TestAdd:
def test_adopt_python_path_assigns_stack(self, castle_root: Path, tmp_path: Path) -> None:
def test_adopt_python_path_assigns_stack(self, wildpc_root: Path, tmp_path: Path) -> None:
repo = tmp_path / "mytool"
repo.mkdir()
(repo / "pyproject.toml").write_text('[project]\nname = "mytool"\ndependencies = []\n')
rc, config = _run_add(castle_root, target=str(repo))
rc, config = _run_add(wildpc_root, target=str(repo))
assert rc == 0
prog = config.programs["mytool"]
assert prog.stack == "python-cli" # detected
assert prog.source == str(repo.resolve())
def test_adopt_fastapi_detects_daemon(self, castle_root: Path, tmp_path: Path) -> None:
def test_adopt_fastapi_detects_daemon(self, wildpc_root: Path, tmp_path: Path) -> None:
repo = tmp_path / "svc"
repo.mkdir()
(repo / "pyproject.toml").write_text(
'[project]\nname = "svc"\ndependencies = ["fastapi>=0.1"]\n'
)
rc, config = _run_add(castle_root, target=str(repo))
rc, config = _run_add(wildpc_root, target=str(repo))
assert rc == 0
# `add` adopts source only — no deployment yet (kind is a deployment
# property); a fastapi project is detected as the python-fastapi stack.
assert config.programs["svc"].stack == "python-fastapi"
assert config.deployments_of("svc") == []
def test_adopt_rust_declares_commands(self, castle_root: Path, tmp_path: Path) -> None:
def test_adopt_rust_declares_commands(self, wildpc_root: Path, tmp_path: Path) -> None:
repo = tmp_path / "rusty"
repo.mkdir()
(repo / "Cargo.toml").write_text('[package]\nname = "rusty"\n')
rc, config = _run_add(castle_root, target=str(repo))
rc, config = _run_add(wildpc_root, target=str(repo))
assert rc == 0
prog = config.programs["rusty"]
assert prog.stack is None # no castle stack for rust
assert prog.stack is None # no wildpc stack for rust
# build lands in BuildSpec; other verbs in CommandsSpec
assert prog.build is not None
assert prog.build.commands == [["cargo", "build", "--release"]]
assert prog.commands is not None
assert prog.commands.run == [["cargo", "run"]]
def test_adopt_git_url_records_repo(self, castle_root: Path) -> None:
rc, config = _run_add(castle_root, target="https://github.com/someone/widget.git")
def test_adopt_git_url_records_repo(self, wildpc_root: Path) -> None:
rc, config = _run_add(wildpc_root, target="https://github.com/someone/widget.git")
assert rc == 0
prog = config.programs["widget"]
assert prog.repo == "https://github.com/someone/widget.git"
def test_missing_path_fails(self, castle_root: Path, tmp_path: Path) -> None:
rc, _ = _run_add(castle_root, target=str(tmp_path / "nope"))
def test_missing_path_fails(self, wildpc_root: Path, tmp_path: Path) -> None:
rc, _ = _run_add(wildpc_root, target=str(tmp_path / "nope"))
assert rc == 1

View File

@@ -1,4 +1,4 @@
"""Tests for castle create command."""
"""Tests for wildpc create command."""
from __future__ import annotations
@@ -6,24 +6,24 @@ from argparse import Namespace
from pathlib import Path
from unittest.mock import patch
from castle_cli.config import load_config
from wildpc_cli.config import load_config
class TestCreateCommand:
"""Tests for the create command."""
def test_create_service(self, castle_root: Path, tmp_path: Path) -> None:
def test_create_service(self, wildpc_root: Path, tmp_path: Path) -> None:
"""Create a new service project."""
repos = tmp_path / "repos"
with (
patch("castle_cli.commands.create.load_config") as mock_load,
patch("castle_cli.commands.create.save_config") as mock_save,
patch("wildpc_cli.commands.create.load_config") as mock_load,
patch("wildpc_cli.commands.create.save_config") as mock_save,
):
config = load_config(castle_root)
config = load_config(wildpc_root)
config.repos_dir = repos
mock_load.return_value = config
from castle_cli.commands.create import run_create
from wildpc_cli.commands.create import run_create
args = Namespace(
name="my-api",
@@ -51,18 +51,18 @@ class TestCreateCommand:
assert svc.program == "my-api"
mock_save.assert_called_once()
def test_create_tool(self, castle_root: Path, tmp_path: Path) -> None:
def test_create_tool(self, wildpc_root: Path, tmp_path: Path) -> None:
"""Create a new tool project."""
repos = tmp_path / "repos"
with (
patch("castle_cli.commands.create.load_config") as mock_load,
patch("castle_cli.commands.create.save_config"),
patch("wildpc_cli.commands.create.load_config") as mock_load,
patch("wildpc_cli.commands.create.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
config.repos_dir = repos
mock_load.return_value = config
from castle_cli.commands.create import run_create
from wildpc_cli.commands.create import run_create
args = Namespace(name="my-tool2", stack="python-cli", description="My tool", port=None)
result = run_create(args)
@@ -77,19 +77,19 @@ class TestCreateCommand:
assert config.tools["my-tool2"].manager == "path"
assert config.deployments_of("my-tool2") == [("my-tool2", "tool")]
def test_create_supabase_app(self, castle_root: Path, tmp_path: Path) -> None:
def test_create_supabase_app(self, wildpc_root: Path, tmp_path: Path) -> None:
"""A supabase app scaffolds a Patch-shaped project registered as a static
frontend (build.outputs=[public]) with no service."""
repos = tmp_path / "repos"
with (
patch("castle_cli.commands.create.load_config") as mock_load,
patch("castle_cli.commands.create.save_config"),
patch("wildpc_cli.commands.create.load_config") as mock_load,
patch("wildpc_cli.commands.create.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
config.repos_dir = repos
mock_load.return_value = config
from castle_cli.commands.create import run_create
from wildpc_cli.commands.create import run_create
args = Namespace(name="guestbook", stack="supabase", description="Guestbook", port=None)
result = run_create(args)
@@ -114,13 +114,13 @@ class TestCreateCommand:
assert [(r.kind, r.ref) for r in dep.requires] == [("deployment", "supabase")]
assert config.deployments_of("guestbook") == [("guestbook", "static")]
def test_create_duplicate_fails(self, castle_root: Path, capsys: object) -> None:
def test_create_duplicate_fails(self, wildpc_root: Path, capsys: object) -> None:
"""Creating a project with existing name fails."""
with patch("castle_cli.commands.create.load_config") as mock_load:
config = load_config(castle_root)
with patch("wildpc_cli.commands.create.load_config") as mock_load:
config = load_config(wildpc_root)
mock_load.return_value = config
from castle_cli.commands.create import run_create
from wildpc_cli.commands.create import run_create
# test-svc exists in the services section
args = Namespace(
@@ -133,17 +133,17 @@ class TestCreateCommand:
assert result == 1
def test_create_auto_port(self, castle_root: Path, tmp_path: Path) -> None:
def test_create_auto_port(self, wildpc_root: Path, tmp_path: Path) -> None:
"""Service creation auto-assigns next available port."""
with (
patch("castle_cli.commands.create.load_config") as mock_load,
patch("castle_cli.commands.create.save_config"),
patch("wildpc_cli.commands.create.load_config") as mock_load,
patch("wildpc_cli.commands.create.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
config.repos_dir = tmp_path / "repos"
mock_load.return_value = config
from castle_cli.commands.create import run_create
from wildpc_cli.commands.create import run_create
args = Namespace(
name="auto-port-svc",

View File

@@ -1,4 +1,4 @@
"""Tests for castle delete."""
"""Tests for wildpc delete."""
from __future__ import annotations
@@ -6,17 +6,17 @@ from argparse import Namespace
from pathlib import Path
from unittest.mock import patch
from castle_cli.config import load_config
from wildpc_cli.config import load_config
def _run_delete(castle_root: Path, **kwargs: object) -> object:
def _run_delete(wildpc_root: Path, **kwargs: object) -> object:
with (
patch("castle_cli.commands.delete.load_config") as mock_load,
patch("castle_cli.commands.delete.save_config"),
patch("wildpc_cli.commands.delete.load_config") as mock_load,
patch("wildpc_cli.commands.delete.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
mock_load.return_value = config
from castle_cli.commands.delete import run_delete
from wildpc_cli.commands.delete import run_delete
args = Namespace(source=False, yes=True, **kwargs)
rc = run_delete(args)
@@ -24,43 +24,43 @@ def _run_delete(castle_root: Path, **kwargs: object) -> object:
class TestDelete:
def test_delete_program(self, castle_root: Path) -> None:
rc, config = _run_delete(castle_root, name="test-tool")
def test_delete_program(self, wildpc_root: Path) -> None:
rc, config = _run_delete(wildpc_root, name="test-tool")
assert rc == 0
assert "test-tool" not in config.programs
def test_delete_unknown_fails(self, castle_root: Path) -> None:
rc, _ = _run_delete(castle_root, name="does-not-exist")
def test_delete_unknown_fails(self, wildpc_root: Path) -> None:
rc, _ = _run_delete(wildpc_root, name="does-not-exist")
assert rc == 1
def test_delete_source_removes_dir(self, castle_root: Path, tmp_path: Path) -> None:
def test_delete_source_removes_dir(self, wildpc_root: Path, tmp_path: Path) -> None:
# Point a program's source at a real temp dir, then delete with --source.
src = tmp_path / "victim"
src.mkdir()
(src / "file.txt").write_text("x")
with (
patch("castle_cli.commands.delete.load_config") as mock_load,
patch("castle_cli.commands.delete.save_config"),
patch("wildpc_cli.commands.delete.load_config") as mock_load,
patch("wildpc_cli.commands.delete.save_config"),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
config.programs["test-tool"].source = str(src)
mock_load.return_value = config
from castle_cli.commands.delete import run_delete
from wildpc_cli.commands.delete import run_delete
rc = run_delete(Namespace(name="test-tool", source=True, yes=True))
assert rc == 0
assert not src.exists()
def test_abort_without_yes_and_no_input(self, castle_root: Path) -> None:
def test_abort_without_yes_and_no_input(self, wildpc_root: Path) -> None:
# No --yes and no stdin → aborts safely (returns 1, leaves entry).
with (
patch("castle_cli.commands.delete.load_config") as mock_load,
patch("castle_cli.commands.delete.save_config"),
patch("wildpc_cli.commands.delete.load_config") as mock_load,
patch("wildpc_cli.commands.delete.save_config"),
patch("builtins.input", side_effect=EOFError),
):
config = load_config(castle_root)
config = load_config(wildpc_root)
mock_load.return_value = config
from castle_cli.commands.delete import run_delete
from wildpc_cli.commands.delete import run_delete
rc = run_delete(Namespace(name="test-tool", source=False, yes=False))
assert rc == 1

View File

@@ -1,4 +1,4 @@
"""Tests for castle doctor."""
"""Tests for wildpc doctor."""
from __future__ import annotations
@@ -7,7 +7,7 @@ from pathlib import Path
from unittest.mock import patch
import pytest
from castle_cli.commands.doctor import (
from wildpc_cli.commands.doctor import (
FAIL,
OK,
WARN,
@@ -20,13 +20,13 @@ from castle_cli.commands.doctor import (
class TestDoctor:
"""The diagnosis path — a bare, unconfigured node should fail loudly."""
def test_bare_node_reports_problems(self, castle_root: Path, capsys: object) -> None:
def test_bare_node_reports_problems(self, wildpc_root: Path, capsys: object) -> None:
"""No repo:, no control plane, nothing running → exit 1 with fix hints."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
# The shared fixture has no repo: and no castle-gateway/api/dashboard, so the
# The shared fixture has no repo: and no wildpc-gateway/api/dashboard, so the
# Configuration and Runtime sections must FAIL. Patch where doctor imports it.
with patch("castle_core.config.load_config", return_value=load_config(castle_root)):
with patch("wildpc_core.config.load_config", return_value=load_config(wildpc_root)):
result = run_doctor(Namespace())
assert result == 1
@@ -34,11 +34,11 @@ class TestDoctor:
assert "repo: not set" in out
assert "control plane missing" in out
# Every failing check offers a concrete next command.
assert "castle apply" in out
assert "wildpc apply" in out
def test_load_failure_is_first_fail(self, capsys: object) -> None:
"""A castle.yaml that won't load is surfaced as a FAIL, not a traceback."""
with patch("castle_core.config.load_config", side_effect=ValueError("bad yaml")):
"""A wildpc.yaml that won't load is surfaced as a FAIL, not a traceback."""
with patch("wildpc_core.config.load_config", side_effect=ValueError("bad yaml")):
result = run_doctor(Namespace())
assert result == 1
@@ -48,31 +48,31 @@ class TestDoctor:
class TestDataDirChecks:
"""The drift-prevention checks: data_dir must be writable, and a CASTLE_DATA_DIR env
"""The drift-prevention checks: data_dir must be writable, and a WILDPC_DATA_DIR env
override (the one way the CLI and api can still diverge) must be surfaced."""
def _config(self, castle_root: Path):
from castle_cli.config import load_config
def _config(self, wildpc_root: Path):
from wildpc_cli.config import load_config
return load_config(castle_root)
return load_config(wildpc_root)
def test_writable_dir_ok_no_warn(
self, castle_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
self, wildpc_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("CASTLE_DATA_DIR", raising=False)
monkeypatch.delenv("CASTLE_REPOS_DIR", raising=False)
cfg = self._config(castle_root)
monkeypatch.delenv("WILDPC_DATA_DIR", raising=False)
monkeypatch.delenv("WILDPC_REPOS_DIR", raising=False)
cfg = self._config(wildpc_root)
cfg.data_dir = tmp_path # exists + writable
checks = _check_configuration(cfg)
by_label = {c.label: c for c in checks}
assert by_label["data dir writable"].status == OK
assert not any("overrides castle.yaml" in c.label for c in checks)
assert not any("overrides wildpc.yaml" in c.label for c in checks)
def test_missing_dir_fails_with_hint(
self, castle_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
self, wildpc_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("CASTLE_DATA_DIR", raising=False)
cfg = self._config(castle_root)
monkeypatch.delenv("WILDPC_DATA_DIR", raising=False)
cfg = self._config(wildpc_root)
missing = tmp_path / "nope"
cfg.data_dir = missing
fail = next(
@@ -82,16 +82,16 @@ class TestDataDirChecks:
assert fail.hint # offers a concrete fix
def test_env_override_warns(
self, castle_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
self, wildpc_root: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A CASTLE_DATA_DIR env var overrides the single-source-of-truth file — the
"""A WILDPC_DATA_DIR env var overrides the single-source-of-truth file — the
exact CLI/api divergence we fixed. Doctor must WARN."""
monkeypatch.setenv("CASTLE_DATA_DIR", str(tmp_path))
cfg = self._config(castle_root)
monkeypatch.setenv("WILDPC_DATA_DIR", str(tmp_path))
cfg = self._config(wildpc_root)
cfg.data_dir = tmp_path
warn = next(c for c in _check_configuration(cfg) if "overrides castle.yaml" in c.label)
warn = next(c for c in _check_configuration(cfg) if "overrides wildpc.yaml" in c.label)
assert warn.status == WARN
assert "CASTLE_DATA_DIR" in warn.detail
assert "WILDPC_DATA_DIR" in warn.detail
class TestStackChecks:
@@ -99,8 +99,8 @@ class TestStackChecks:
run. Missing tooling for an enabled deployment is a FAIL with a copyable fix."""
def _fastapi_cfg(self):
import castle_core.config as C
from castle_core.manifest import ProgramSpec, SystemdDeployment
import wildpc_core.config as C
from wildpc_core.manifest import ProgramSpec, SystemdDeployment
prog = ProgramSpec(id="svc", stack="python-fastapi")
dep = SystemdDeployment.model_validate(
@@ -110,7 +110,7 @@ class TestStackChecks:
"run": {"launcher": "command", "argv": ["svc"]},
}
)
return C.CastleConfig(
return C.WildpcConfig(
root=None,
gateway=C.GatewayConfig(port=9000),
repo=None,
@@ -119,7 +119,7 @@ class TestStackChecks:
)
def test_present_tool_is_ok(self, monkeypatch: pytest.MonkeyPatch) -> None:
import castle_core.stack_status as SS
import wildpc_core.stack_status as SS
monkeypatch.setattr(SS, "_tool_available", lambda dep, tool: True)
fa = next(
@@ -130,7 +130,7 @@ class TestStackChecks:
def test_missing_tool_for_enabled_deployment_fails_with_hint(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
import castle_core.stack_status as SS
import wildpc_core.stack_status as SS
monkeypatch.setattr(SS, "_tool_available", lambda dep, tool: False)
fa = next(
@@ -141,7 +141,7 @@ class TestStackChecks:
def test_unused_stacks_are_skipped(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""No react-vite program → no pnpm nag."""
import castle_core.stack_status as SS
import wildpc_core.stack_status as SS
monkeypatch.setattr(SS, "_tool_available", lambda dep, tool: True)
labels = [c.label for c in _check_stacks(self._fastapi_cfg())]

View File

@@ -1,4 +1,4 @@
"""Tests for castle info command."""
"""Tests for wildpc info command."""
from __future__ import annotations
@@ -11,14 +11,14 @@ from unittest.mock import patch
class TestInfoCommand:
"""Tests for the info command."""
def test_info_service(self, castle_root: Path, capsys) -> None:
def test_info_service(self, wildpc_root: Path, capsys) -> None:
"""Show info for a service."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="test-svc", json=False))
@@ -28,14 +28,14 @@ class TestInfoCommand:
assert "service" in output
assert "19000" in output
def test_info_tool(self, castle_root: Path, capsys) -> None:
def test_info_tool(self, wildpc_root: Path, capsys) -> None:
"""Show info for a tool."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="test-tool", json=False))
@@ -44,14 +44,14 @@ class TestInfoCommand:
assert "test-tool" in output
assert "tool" in output
def test_info_not_found(self, castle_root: Path, capsys) -> None:
def test_info_not_found(self, wildpc_root: Path, capsys) -> None:
"""Info for nonexistent component returns error."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="nope", json=False))
@@ -59,14 +59,14 @@ class TestInfoCommand:
output = capsys.readouterr().out
assert "nope" in output
def test_info_json_service(self, castle_root: Path, capsys) -> None:
def test_info_json_service(self, wildpc_root: Path, capsys) -> None:
"""--json produces valid JSON with service fields."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="test-svc", json=True))
@@ -76,14 +76,14 @@ class TestInfoCommand:
assert data["kind"] == "service"
assert data["service"]["expose"]["http"]["internal"]["port"] == 19000
def test_info_shows_env(self, castle_root: Path, capsys) -> None:
def test_info_shows_env(self, wildpc_root: Path, capsys) -> None:
"""Info displays environment variables."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="test-svc", json=False))
@@ -91,14 +91,14 @@ class TestInfoCommand:
output = capsys.readouterr().out
assert "TEST_SVC_DATA_DIR" in output
def test_info_shows_kind(self, castle_root: Path, capsys) -> None:
def test_info_shows_kind(self, wildpc_root: Path, capsys) -> None:
"""Info displays the derived kind."""
from castle_cli.config import load_config
from wildpc_cli.config import load_config
with patch("castle_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(castle_root)
with patch("wildpc_cli.commands.info.load_config") as mock_load:
mock_load.return_value = load_config(wildpc_root)
from castle_cli.commands.info import run_info
from wildpc_cli.commands.info import run_info
result = run_info(Namespace(name="test-svc", json=False))

View File

@@ -1,4 +1,4 @@
"""Tests for castle list command."""
"""Tests for wildpc list command."""
from __future__ import annotations
@@ -7,18 +7,18 @@ from argparse import Namespace
from pathlib import Path
from unittest.mock import patch
from castle_cli.commands.list_cmd import run_list
from wildpc_cli.commands.list_cmd import run_list
class TestListCommand:
"""Tests for the list command."""
def test_list_all(self, castle_root: Path, capsys: object) -> None:
def test_list_all(self, wildpc_root: Path, capsys: object) -> None:
"""List all components."""
with patch("castle_cli.commands.list_cmd.load_config") as mock_load:
from castle_cli.config import load_config
with patch("wildpc_cli.commands.list_cmd.load_config") as mock_load:
from wildpc_cli.config import load_config
mock_load.return_value = load_config(castle_root)
mock_load.return_value = load_config(wildpc_root)
args = Namespace(kind=None, stack=None, json=False)
result = run_list(args)
@@ -27,12 +27,12 @@ class TestListCommand:
assert "test-svc" in captured.out
assert "test-tool" in captured.out
def test_list_filter_daemon(self, castle_root: Path, capsys: object) -> None:
def test_list_filter_daemon(self, wildpc_root: Path, capsys: object) -> None:
"""--behavior filters the program catalog by its real behavior field."""
with patch("castle_cli.commands.list_cmd.load_config") as mock_load:
from castle_cli.config import load_config
with patch("wildpc_cli.commands.list_cmd.load_config") as mock_load:
from wildpc_cli.config import load_config
mock_load.return_value = load_config(castle_root)
mock_load.return_value = load_config(wildpc_root)
args = Namespace(kind="service", stack=None, json=False)
result = run_list(args)
@@ -43,12 +43,12 @@ class TestListCommand:
# Services/jobs are deployment views, not behaviors — hidden under a filter.
assert "test-svc" not in captured.out
def test_list_filter_tool(self, castle_root: Path, capsys: object) -> None:
def test_list_filter_tool(self, wildpc_root: Path, capsys: object) -> None:
"""List filtered to tools."""
with patch("castle_cli.commands.list_cmd.load_config") as mock_load:
from castle_cli.config import load_config
with patch("wildpc_cli.commands.list_cmd.load_config") as mock_load:
from wildpc_cli.config import load_config
mock_load.return_value = load_config(castle_root)
mock_load.return_value = load_config(wildpc_root)
args = Namespace(kind="tool", stack=None, json=False)
result = run_list(args)
@@ -57,12 +57,12 @@ class TestListCommand:
assert "test-tool" in captured.out
assert "test-svc" not in captured.out
def test_list_jobs_are_deployments(self, castle_root: Path, capsys: object) -> None:
def test_list_jobs_are_deployments(self, wildpc_root: Path, capsys: object) -> None:
"""Jobs are a deployment view: shown unfiltered, hidden under a behavior filter."""
with patch("castle_cli.commands.list_cmd.load_config") as mock_load:
from castle_cli.config import load_config
with patch("wildpc_cli.commands.list_cmd.load_config") as mock_load:
from wildpc_cli.config import load_config
mock_load.return_value = load_config(castle_root)
mock_load.return_value = load_config(wildpc_root)
# Unfiltered: the job appears.
run_list(Namespace(kind=None, stack=None, json=False))
assert "test-job" in capsys.readouterr().out # type: ignore[attr-defined]
@@ -73,12 +73,12 @@ class TestListCommand:
assert "test-svc" not in out
assert "test-tool" in out
def test_list_json(self, castle_root: Path, capsys: object) -> None:
def test_list_json(self, wildpc_root: Path, capsys: object) -> None:
"""JSON output tags each entry with its derived kind."""
with patch("castle_cli.commands.list_cmd.load_config") as mock_load:
from castle_cli.config import load_config
with patch("wildpc_cli.commands.list_cmd.load_config") as mock_load:
from wildpc_cli.config import load_config
mock_load.return_value = load_config(castle_root)
mock_load.return_value = load_config(wildpc_root)
args = Namespace(kind=None, stack=None, json=True)
result = run_list(args)

View File

@@ -1,4 +1,4 @@
"""Tests for the castle mesh command (API calls mocked)."""
"""Tests for the wildpc mesh command (API calls mocked)."""
from __future__ import annotations
@@ -6,7 +6,7 @@ import urllib.error
from argparse import Namespace
from unittest.mock import patch
from castle_cli.commands.mesh import run_mesh
from wildpc_cli.commands.mesh import run_mesh
class TestMeshCommand:
@@ -15,7 +15,7 @@ class TestMeshCommand:
"enabled": True, "connected": True, "nats_url": "tls://x:4222",
"peer_count": 1, "peers": ["primer"],
}
with patch("castle_cli.commands.mesh._get", return_value=data):
with patch("wildpc_cli.commands.mesh._get", return_value=data):
rc = run_mesh(Namespace(mesh_command="status"))
assert rc == 0
out = capsys.readouterr().out # type: ignore[attr-defined]
@@ -28,7 +28,7 @@ class TestMeshCommand:
{"hostname": "primer", "online": True, "is_stale": False,
"deployed_count": 3, "is_local": False},
]
with patch("castle_cli.commands.mesh._get", return_value=data):
with patch("wildpc_cli.commands.mesh._get", return_value=data):
rc = run_mesh(Namespace(mesh_command="nodes"))
assert rc == 0
out = capsys.readouterr().out # type: ignore[attr-defined]
@@ -36,7 +36,7 @@ class TestMeshCommand:
def test_config_list(self, capsys: object) -> None:
data = {"role": "authority", "keys": ["fleet/motd"]}
with patch("castle_cli.commands.mesh._get", return_value=data):
with patch("wildpc_cli.commands.mesh._get", return_value=data):
rc = run_mesh(
Namespace(mesh_command="config", mesh_config_command="list")
)
@@ -45,7 +45,7 @@ class TestMeshCommand:
def test_config_set_calls_put(self) -> None:
with patch(
"castle_cli.commands.mesh._put", return_value={"ok": True}
"wildpc_cli.commands.mesh._put", return_value={"ok": True}
) as put:
rc = run_mesh(
Namespace(
@@ -60,7 +60,7 @@ class TestMeshCommand:
def test_api_unreachable_returns_1(self, capsys: object) -> None:
with patch(
"castle_cli.commands.mesh._get",
"wildpc_cli.commands.mesh._get",
side_effect=urllib.error.URLError("refused"),
):
rc = run_mesh(Namespace(mesh_command="status"))

View File

@@ -1,4 +1,4 @@
"""Tests for the `castle secret` command (reads/writes the active backend)."""
"""Tests for the `wildpc secret` command (reads/writes the active backend)."""
from __future__ import annotations
@@ -13,13 +13,13 @@ def file_secrets(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Force the active backend to a temp file store."""
secrets = tmp_path / "secrets"
secrets.mkdir()
monkeypatch.setenv("CASTLE_SECRET_BACKEND", "file")
monkeypatch.setattr("castle_core.config.SECRETS_DIR", secrets)
monkeypatch.setenv("WILDPC_SECRET_BACKEND", "file")
monkeypatch.setattr("wildpc_core.config.SECRETS_DIR", secrets)
return secrets
def _run(**kw: object) -> int:
from castle_cli.commands.secret import run_secret
from wildpc_cli.commands.secret import run_secret
return run_secret(Namespace(**kw))

View File

@@ -1,4 +1,4 @@
"""Tests for the `castle tool` lens."""
"""Tests for the `wildpc tool` lens."""
from __future__ import annotations
@@ -8,16 +8,16 @@ from pathlib import Path
from unittest.mock import patch
def _config(castle_root: Path):
from castle_cli.config import load_config
def _config(wildpc_root: Path):
from wildpc_cli.config import load_config
return load_config(castle_root)
return load_config(wildpc_root)
class TestToolList:
def test_list_includes_tool(self, castle_root: Path, capsys: object) -> None:
with patch("castle_cli.commands.tool.load_config", return_value=_config(castle_root)):
from castle_cli.commands.tool import run_tool_list
def test_list_includes_tool(self, wildpc_root: Path, capsys: object) -> None:
with patch("wildpc_cli.commands.tool.load_config", return_value=_config(wildpc_root)):
from wildpc_cli.commands.tool import run_tool_list
rc = run_tool_list(Namespace(json=False))
assert rc == 0
@@ -26,9 +26,9 @@ class TestToolList:
# test-svc is a service (systemd), not a tool — must not appear.
assert "test-svc" not in out
def test_list_json_payload(self, castle_root: Path, capsys: object) -> None:
with patch("castle_cli.commands.tool.load_config", return_value=_config(castle_root)):
from castle_cli.commands.tool import run_tool_list
def test_list_json_payload(self, wildpc_root: Path, capsys: object) -> None:
with patch("wildpc_cli.commands.tool.load_config", return_value=_config(wildpc_root)):
from wildpc_cli.commands.tool import run_tool_list
rc = run_tool_list(Namespace(json=True))
assert rc == 0
@@ -41,9 +41,9 @@ class TestToolList:
class TestToolInfo:
def test_info_json(self, castle_root: Path, capsys: object) -> None:
with patch("castle_cli.commands.tool.load_config", return_value=_config(castle_root)):
from castle_cli.commands.tool import run_tool_info
def test_info_json(self, wildpc_root: Path, capsys: object) -> None:
with patch("wildpc_cli.commands.tool.load_config", return_value=_config(wildpc_root)):
from wildpc_cli.commands.tool import run_tool_info
rc = run_tool_info(Namespace(name="test-tool", json=True))
assert rc == 0
@@ -51,10 +51,10 @@ class TestToolInfo:
assert data["name"] == "test-tool"
assert data["executables"]
def test_info_rejects_non_tool(self, castle_root: Path, capsys: object) -> None:
def test_info_rejects_non_tool(self, wildpc_root: Path, capsys: object) -> None:
# test-svc is a service deployment, not a tool.
with patch("castle_cli.commands.tool.load_config", return_value=_config(castle_root)):
from castle_cli.commands.tool import run_tool_info
with patch("wildpc_cli.commands.tool.load_config", return_value=_config(wildpc_root)):
from wildpc_cli.commands.tool import run_tool_info
rc = run_tool_info(Namespace(name="test-svc", json=False))
assert rc == 1