117 lines
4.5 KiB
Python
117 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""skill-template 脚手架与 Git 防串库守护。"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import tempfile
|
||
import unittest
|
||
|
||
from _support import get_skill_root
|
||
|
||
TEMPLATE_MARKER = ".openclaw-skill-template"
|
||
TEMPLATE_SLUG = "your-skill-slug"
|
||
SCAFFOLD_TEST_SLUG = "query-demo-records"
|
||
|
||
|
||
def _read_skill_slug() -> str:
|
||
constants_path = os.path.join(get_skill_root(), "scripts", "util", "constants.py")
|
||
with open(constants_path, encoding="utf-8") as f:
|
||
for line in f:
|
||
if line.strip().startswith("SKILL_SLUG"):
|
||
_, _, value = line.partition("=")
|
||
return value.strip().strip('"').strip("'")
|
||
return ""
|
||
|
||
|
||
class TestScaffoldGuard(unittest.TestCase):
|
||
def test_template_repo_has_marker(self) -> None:
|
||
slug = _read_skill_slug()
|
||
if slug != TEMPLATE_SLUG:
|
||
self.skipTest(f"当前 SKILL_SLUG={slug!r},非模板占位,跳过 template marker 断言")
|
||
marker = os.path.join(get_skill_root(), TEMPLATE_MARKER)
|
||
self.assertTrue(
|
||
os.path.isfile(marker),
|
||
msg=f"skill-template 源仓库根目录必须存在 {TEMPLATE_MARKER}",
|
||
)
|
||
|
||
def test_business_skill_must_not_keep_template_marker(self) -> None:
|
||
slug = _read_skill_slug()
|
||
if slug == TEMPLATE_SLUG:
|
||
self.skipTest("当前仍为模板占位 slug,由 test_template_repo_has_marker 守护")
|
||
marker = os.path.join(get_skill_root(), TEMPLATE_MARKER)
|
||
self.assertFalse(
|
||
os.path.isfile(marker),
|
||
msg=(
|
||
"业务技能不得保留 .openclaw-skill-template;"
|
||
"请使用 tools/scaffold_skill 或删除标记文件并检查是否误复制了模板 .git"
|
||
),
|
||
)
|
||
|
||
def test_development_md_documents_git_cleanup(self) -> None:
|
||
path = os.path.join(get_skill_root(), "development", "DEVELOPMENT.md")
|
||
with open(path, encoding="utf-8") as f:
|
||
text = f.read()
|
||
self.assertIn(".git", text)
|
||
self.assertTrue(
|
||
"scaffold_skill" in text or "scaffold" in text.lower(),
|
||
msg="DEVELOPMENT.md must document scaffold_skill",
|
||
)
|
||
self.assertTrue(
|
||
"remote" in text.lower() or "git remote" in text.lower(),
|
||
msg="DEVELOPMENT.md must mention git remote self-check",
|
||
)
|
||
|
||
def test_scaffold_replaces_slug_and_copies_actions_manifest(self) -> None:
|
||
slug = _read_skill_slug()
|
||
if slug != TEMPLATE_SLUG:
|
||
self.skipTest("仅在 skill-template 源仓库运行 scaffold 集成测试")
|
||
|
||
import subprocess
|
||
import sys
|
||
|
||
repo_root = get_skill_root()
|
||
scaffold_py = os.path.join(repo_root, "tools", "scaffold_skill.py")
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
dest = os.path.join(tmp, SCAFFOLD_TEST_SLUG)
|
||
proc = subprocess.run(
|
||
[
|
||
sys.executable,
|
||
scaffold_py,
|
||
"--slug",
|
||
SCAFFOLD_TEST_SLUG,
|
||
"--destination",
|
||
dest,
|
||
],
|
||
cwd=repo_root,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
self.assertEqual(proc.returncode, 0, msg=proc.stderr or proc.stdout)
|
||
|
||
actions_path = os.path.join(dest, "assets", "actions.json")
|
||
self.assertTrue(os.path.isfile(actions_path), msg="scaffold must copy assets/actions.json")
|
||
with open(actions_path, encoding="utf-8") as f:
|
||
manifest = json.load(f)
|
||
self.assertEqual(manifest.get("skill"), SCAFFOLD_TEST_SLUG)
|
||
self.assertNotIn(TEMPLATE_SLUG, json.dumps(manifest))
|
||
|
||
actions_md = os.path.join(dest, "references", "ACTIONS.md")
|
||
self.assertTrue(os.path.isfile(actions_md), msg="scaffold must copy references/ACTIONS.md")
|
||
|
||
schema_path = os.path.join(dest, "assets", "schemas", "skill-actions.schema.json")
|
||
self.assertTrue(os.path.isfile(schema_path))
|
||
|
||
marker = os.path.join(dest, TEMPLATE_MARKER)
|
||
self.assertFalse(os.path.isfile(marker))
|
||
self.assertFalse(os.path.isdir(os.path.join(dest, ".git")))
|
||
|
||
constants_path = os.path.join(dest, "scripts", "util", "constants.py")
|
||
with open(constants_path, encoding="utf-8") as f:
|
||
constants_text = f.read()
|
||
self.assertNotIn(TEMPLATE_SLUG, constants_text)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|