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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())]
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user