joy-cli/tests/test_contract.py
feibisi 9d12fe24eb feat(cli): harden joy ABI — tiers, schema, dashboard package, audit
Phase 0–7 of docs/joy-cli-hardening-plan.md:
- golden fixtures + contract tests (commands/dashboard keys/whitelist)
- command tier metadata; help ≡ manifest (pipeline in core)
- _joy.schema_version on dict outputs; dashboard collect package split
- work_rebuild → shared rebuild; deprecated markers; journal vm-first compat
- web JSONL audit; lazy command import; plugins README
2026-07-10 11:42:32 +08:00

108 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""契约测试manifest/help 同源、golden 键、schema、白名单不减。"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
SRC = Path(__file__).parent.parent / "src"
FIX = Path(__file__).parent / "fixtures"
sys.path.insert(0, str(SRC))
@pytest.fixture(scope="module")
def commands_loaded():
from joy.cli import ensure_commands_loaded, COMMANDS
ensure_commands_loaded()
return COMMANDS
def test_command_count_not_below_golden(commands_loaded):
inv = json.loads((FIX / "command_inventory_v1.json").read_text())
golden = set(inv["commands"])
current = set(commands_loaded.keys())
missing = golden - current
assert not missing, f"命令从注册表消失: {sorted(missing)}"
assert len(current) >= len(golden)
def test_manifest_help_same_names(commands_loaded):
from joy.commands.help import _build_help_data
from joy.commands.tools import Tools
help_names = set()
for cat in _build_help_data()["categories"]:
for c in cat["commands"]:
help_names.add(c["name"])
reg = set(commands_loaded.keys())
assert help_names == reg, f"help/reg drift +{reg-help_names} -{help_names-reg}"
man = Tools()._manifest()
man_names = {c["name"] for c in man["commands"]}
assert man_names == reg
def test_pipeline_in_core(commands_loaded):
assert "pipeline" in commands_loaded
assert commands_loaded["pipeline"].tier == "core" or commands_loaded["pipeline"].tier in (
"core",
None,
)
from joy.core.registry_meta import CORE_COMMANDS, tier_for
assert "pipeline" in CORE_COMMANDS
assert tier_for("pipeline") == "core"
def test_dashboard_keys_golden():
golden = set(json.loads((FIX / "dashboard_keys_v1.json").read_text())["keys"])
from joy.dashboard.schema import DASHBOARD_TOP_LEVEL_KEYS
assert set(DASHBOARD_TOP_LEVEL_KEYS) >= golden
# _joy is meta, not required in golden business keys
assert "ts" in DASHBOARD_TOP_LEVEL_KEYS
assert "pulse" in DASHBOARD_TOP_LEVEL_KEYS
def test_web_whitelist_not_shrunk():
golden = json.loads((FIX / "web_action_prefixes_v1.json").read_text())
from joy.commands.web import ACTION_PREFIXES, ACTION_WHITELIST
cur_wl = set(ACTION_WHITELIST.keys())
cur_px = {p[0] for p in ACTION_PREFIXES}
missing_wl = set(golden["whitelist"]) - cur_wl
missing_px = set(golden["prefixes"]) - cur_px
assert not missing_wl, f"白名单减少: {sorted(missing_wl)}"
assert not missing_px, f"前缀减少: {sorted(missing_px)}"
def test_with_joy_meta_additive():
from joy.core.schema import with_joy_meta
d = with_joy_meta({"a": 1}, command="patrol")
assert d["a"] == 1
assert d["_joy"]["schema_version"] == 1
assert d["_joy"]["command"] == "patrol"
# list 不变
assert with_joy_meta([1, 2]) == [1, 2]
def test_deprecated_commands_marked(commands_loaded):
for name in ("prlctl", "clone_restart", "work_rebuild"):
assert name in commands_loaded
assert commands_loaded[name].tier == "deprecated"
assert commands_loaded[name].replaced_by
def test_work_rebuild_shared():
from joy.commands.work import rebuild_work_session
from joy.commands.work_rebuild import WorkRebuild
# 模块可导入SSH 不可达时返回 error 结构
r = rebuild_work_session("__no_such_vm_xyz__")
assert isinstance(r, dict)
assert r.get("vm") == "__no_such_vm_xyz__"