Files
.pouch/tests/test_pouch_catalog.py
T
laily a15a046e4e feat(ship): merge short-ship and long-ship into one entry
Promote ship to the user-facing delivery skill. Kind is short or long
from an explicit mode, then self-judgment, then a question, defaulting
to short. Old names remain deprecation stubs.
2026-10-24 14:27:19 +08:00

189 lines
8.1 KiB
Python
Raw 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.
from __future__ import annotations
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
CATALOG = REPO_ROOT / "skills" / "pouch" / "scripts" / "catalog.py"
SUGGEST = REPO_ROOT / "skills" / "pouch" / "scripts" / "suggest_skills.py"
def _run(script: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(script), *args],
check=False,
capture_output=True,
text=True,
)
class CatalogTests(unittest.TestCase):
def test_repo_skills_have_lede_and_example(self) -> None:
result = _run(CATALOG, "--skills-dir", str(REPO_ROOT / "skills"))
self.assertEqual(result.returncode, 0, result.stderr)
for name in (
"ack",
"builder",
"deployer",
"host-sec-check",
"releaser",
"note-style",
"pouch",
"short-ship",
"long-ship",
"ship",
"pr-ship",
# 旧名兼容占位,仍须可列出(见 test_manage_release_is_deprecation_stub)
"manage-release",
"bugfix-ship",
"requirement-flow",
):
self.assertIn(f"| {name} |", result.stdout)
self.assertIn("/ack 初始化", result.stdout)
self.assertIn("用 builder 初始化这个项目", result.stdout)
self.assertNotIn("触发词", result.stdout)
def test_manage_release_is_deprecation_stub(self) -> None:
"""旧名只保留指向 releaser 的占位,不得再携带流程或参考资料。"""
stub = REPO_ROOT / "skills" / "manage-release"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: manage-release", skill)
self.assertIn("已弃用", skill)
self.assertIn("../releaser/SKILL.md", skill)
self.assertTrue((REPO_ROOT / "skills" / "releaser" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
def test_bugfix_ship_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "bugfix-ship"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: bugfix-ship", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_requirement_flow_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "requirement-flow"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: requirement-flow", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_short_ship_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "short-ship"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: short-ship", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertIn("short 模式", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_long_ship_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "long-ship"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: long-ship", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertIn("long 模式", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_missing_readme_fails(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
skill = Path(tmp) / "demo"
skill.mkdir()
(skill / "SKILL.md").write_text(
"---\nname: demo\ndescription: d\n---\n# demo\n",
encoding="utf-8",
)
result = _run(CATALOG, "--skills-dir", tmp)
self.assertEqual(result.returncode, 1)
self.assertIn("缺少 README.md", result.stderr)
def test_extracts_first_sentence_and_skips_hash_comments(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
skill = Path(tmp) / "demo"
skill.mkdir()
(skill / "SKILL.md").write_text(
"---\nname: demo\ndescription: d\n---\n# demo\n",
encoding="utf-8",
)
(skill / "README.md").write_text(
"# demo\n\n"
"这是一句话说明。后面还有一句不该进短清单。\n\n"
"## 使用示例\n\n"
"```text\n"
"# 注释\n"
"用 demo 做这件事。\n"
"```\n",
encoding="utf-8",
)
result = _run(CATALOG, "--skills-dir", tmp)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("这是一句话说明。", result.stdout)
self.assertNotIn("不该进短清单", result.stdout)
self.assertIn("用 demo 做这件事。", result.stdout)
class SuggestSkillsTests(unittest.TestCase):
def test_pouch_repo_is_not_an_app(self) -> None:
result = _run(SUGGEST, str(REPO_ROOT))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("this_is_pouch_repo: true", result.stdout)
self.assertNotIn("suggested:", result.stdout)
def test_dockerfile_suggests_builder(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
result = _run(SUGGEST, str(root))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("this_is_pouch_repo: false", result.stdout)
self.assertIn("builder", result.stdout)
self.assertIn("Dockerfile", result.stdout)
self.assertIn("用 builder 初始化", result.stdout)
self.assertIn("不要 `pouch init builder`", result.stdout)
def test_existing_makefile_builder_is_already(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "makefile.builder").write_text("build:\n", encoding="utf-8")
result = _run(SUGGEST, str(root))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("already:", result.stdout)
self.assertIn("builder", result.stdout)
self.assertIn("init: (none)", result.stdout)
def test_compose_and_ack_dir(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "compose.yaml").write_text("services: {}\n", encoding="utf-8")
(root / ".pouch" / "ack").mkdir(parents=True)
result = _run(SUGGEST, str(root))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("deployer", result.stdout)
self.assertIn("compose.yaml", result.stdout)
self.assertIn("ack", result.stdout)
self.assertIn(".pouch/ack", result.stdout)
self.assertIn("用 deployer 初始化", result.stdout)
self.assertNotIn("/ack 初始化", result.stdout)
if __name__ == "__main__":
unittest.main()