feat: 工资查询助手首版 v1.0.105
基于 skill-template 脚手架开发,包含: - payroll_config(费率配置,可编辑)+ payslip_snapshots(解析历史,只读)2 张业务表 - 全国默认 + 北上广 4 套费率(社保/公积金/个税起征点/7 级税率表) - 5 个业务 Action:calculate-payslip-simple / calculate-payslip-full / explain-term / view-config / edit-config - 个税累计预扣法计算,支持多月历史累加 - 20 个薪资名词库(社保/公积金/个税/全勤/加班费等) - 完整市场文档(README/TUTORIAL/DEMO/CHANGELOG) - 296 测试全部通过
This commit is contained in:
145
tests/test_scaffold_guard.py
Normal file
145
tests/test_scaffold_guard.py
Normal file
@@ -0,0 +1,145 @@
|
||||
# -*- 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_KEBAB = "your-skill-slug"
|
||||
TEMPLATE_SLUG_UNDERSCORE = "your_skill_slug"
|
||||
SCAFFOLD_TEST_SLUG = "query-demo-records"
|
||||
SCAFFOLD_TEST_SLUG_UNDERSCORE = "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_KEBAB:
|
||||
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_KEBAB:
|
||||
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_KEBAB:
|
||||
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)
|
||||
manifest_raw = json.dumps(manifest)
|
||||
self.assertNotIn(TEMPLATE_SLUG_KEBAB, manifest_raw)
|
||||
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, manifest_raw)
|
||||
|
||||
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_KEBAB, constants_text)
|
||||
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, constants_text)
|
||||
self.assertIn(f'SKILL_SLUG = "{SCAFFOLD_TEST_SLUG}"', constants_text)
|
||||
self.assertIn(
|
||||
f'LOG_LOGGER_NAME = "openclaw.skill.{SCAFFOLD_TEST_SLUG_UNDERSCORE}"',
|
||||
constants_text,
|
||||
)
|
||||
|
||||
skill_md_path = os.path.join(dest, "SKILL.md")
|
||||
with open(skill_md_path, encoding="utf-8") as f:
|
||||
skill_md = f.read()
|
||||
self.assertIn(f"slug: {SCAFFOLD_TEST_SLUG}", skill_md)
|
||||
self.assertNotIn(TEMPLATE_SLUG_KEBAB, skill_md)
|
||||
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, skill_md)
|
||||
|
||||
def test_scaffold_ps1_matches_python_placeholder_rules(self) -> None:
|
||||
slug = _read_skill_slug()
|
||||
if slug != TEMPLATE_SLUG_KEBAB:
|
||||
self.skipTest("仅在 skill-template 源仓库运行 scaffold PS1 文案测试")
|
||||
|
||||
ps1_path = os.path.join(get_skill_root(), "tools", "scaffold_skill.ps1")
|
||||
with open(ps1_path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
self.assertIn("query-payslip-default", text)
|
||||
self.assertIn("query_payslip_default", text)
|
||||
self.assertIn('.Replace("-", "_")', text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user