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.
136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""Tests for per-program verb resolution (declared commands → stack → none)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from wildpc_core.manifest import ProgramSpec
|
|
from wildpc_core.stacks import (
|
|
_declared_commands,
|
|
_migration_version,
|
|
available_actions,
|
|
is_available,
|
|
plan_migrations,
|
|
)
|
|
|
|
|
|
class TestMigrationPlanner:
|
|
"""The supabase stack's forward-only, idempotent migration runner (pure part)."""
|
|
|
|
def test_version_is_leading_token(self) -> None:
|
|
assert _migration_version(Path("0007_add_users.sql")) == "0007"
|
|
|
|
def test_orders_by_filename_and_skips_applied(self) -> None:
|
|
files = [Path("0003_c.sql"), Path("0001_a.sql"), Path("0002_b.sql")]
|
|
pending = plan_migrations(files, {"0001"})
|
|
assert [p.name for p in pending] == ["0002_b.sql", "0003_c.sql"]
|
|
|
|
def test_none_pending_when_all_applied(self) -> None:
|
|
files = [Path("0001_a.sql"), Path("0002_b.sql")]
|
|
assert plan_migrations(files, {"0001", "0002"}) == []
|
|
|
|
def test_all_pending_on_fresh_db(self) -> None:
|
|
files = [Path("0002_b.sql"), Path("0001_a.sql")]
|
|
assert [p.name for p in plan_migrations(files, set())] == [
|
|
"0001_a.sql",
|
|
"0002_b.sql",
|
|
]
|
|
|
|
|
|
class TestSupabaseStackResolution:
|
|
def test_supabase_stack_resolves_verbs(self) -> None:
|
|
"""A supabase program resolves build (migrations) + deno verbs."""
|
|
p = ProgramSpec.model_validate({"source": "/tmp/x", "stack": "supabase"})
|
|
actions = available_actions(p)
|
|
assert "build" in actions and "test" in actions
|
|
assert "install" in actions and "uninstall" in actions
|
|
|
|
|
|
class TestHugoStackResolution:
|
|
def test_hugo_provides_only_build_verbs(self) -> None:
|
|
"""Hugo is build-only: it resolves build/install/uninstall but NOT the
|
|
lint/test/type-check/check verbs it has no native tooling for."""
|
|
p = ProgramSpec.model_validate({"source": "/tmp/x", "stack": "hugo"})
|
|
actions = available_actions(p)
|
|
assert "build" in actions
|
|
assert "install" in actions and "uninstall" in actions
|
|
for absent in ("lint", "test", "type-check", "check"):
|
|
assert absent not in actions
|
|
|
|
def test_declared_command_still_overrides_hugo(self) -> None:
|
|
"""A hugo program can still declare a verb the stack doesn't provide —
|
|
declared commands are resolved regardless of the handler's `provides`."""
|
|
p = ProgramSpec.model_validate(
|
|
{
|
|
"source": "/tmp/x",
|
|
"stack": "hugo",
|
|
"commands": {"test": [["htmltest"]]},
|
|
}
|
|
)
|
|
actions = available_actions(p)
|
|
assert "test" in actions and "build" in actions
|
|
|
|
|
|
class TestResolution:
|
|
def test_stack_only_program_unchanged(self) -> None:
|
|
"""A program with a stack and no commands resolves all stack verbs."""
|
|
p = ProgramSpec.model_validate({"source": "/tmp/x", "stack": "python-cli"})
|
|
actions = available_actions(p)
|
|
assert "build" in actions and "test" in actions and "lint" in actions
|
|
assert "install" in actions and "uninstall" in actions
|
|
# `run` is declared-only — a bare stack program does not expose it.
|
|
assert "run" not in actions
|
|
|
|
def test_wired_in_program_no_stack(self) -> None:
|
|
"""A program with no stack but declared commands resolves those verbs."""
|
|
p = ProgramSpec.model_validate(
|
|
{
|
|
"source": "/tmp/y",
|
|
"commands": {
|
|
"lint": [["make", "lint"]],
|
|
"test": [["make", "test"]],
|
|
"run": [["./bin/y"]],
|
|
},
|
|
}
|
|
)
|
|
actions = available_actions(p)
|
|
assert set(actions) >= {"lint", "test", "run"}
|
|
assert "build" not in actions # not declared, no stack
|
|
assert _declared_commands(p, "lint") == [["make", "lint"]]
|
|
|
|
def test_check_available_when_subverbs_are(self) -> None:
|
|
"""`check` is a composite; available when any of lint/type-check/test is."""
|
|
p = ProgramSpec.model_validate(
|
|
{"source": "/tmp/y", "commands": {"lint": [["ruff", "check", "."]]}}
|
|
)
|
|
assert is_available(p, "check")
|
|
|
|
def test_hybrid_override_one_verb(self) -> None:
|
|
"""A stack program can override a single verb; the rest fall back to stack."""
|
|
p = ProgramSpec.model_validate(
|
|
{
|
|
"source": "/tmp/z",
|
|
"stack": "python-cli",
|
|
"commands": {"test": [["pytest", "-x"]]},
|
|
}
|
|
)
|
|
assert _declared_commands(p, "test") == [["pytest", "-x"]]
|
|
assert (
|
|
_declared_commands(p, "build") is None
|
|
) # build still comes from the stack
|
|
|
|
def test_build_declared_via_buildspec(self) -> None:
|
|
"""`build` is declared through BuildSpec.commands, not CommandsSpec."""
|
|
p = ProgramSpec.model_validate(
|
|
{
|
|
"source": "/tmp/w",
|
|
"build": {"commands": [["make"]], "outputs": ["dist/"]},
|
|
}
|
|
)
|
|
assert _declared_commands(p, "build") == [["make"]]
|
|
assert "build" in available_actions(p)
|
|
|
|
def test_no_source_no_actions(self) -> None:
|
|
p = ProgramSpec.model_validate({"description": "a library"})
|
|
assert available_actions(p) == []
|