diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 39d8143..8361ff2 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -137,6 +137,7 @@ jobs: } run_bats "sync_standards" "test_sync_standards.bats" + run_bats "sync_templates" "test_sync_templates.bats" run_bats "vendor_playbook" "test_vendor_playbook.bats" run_bats "install_codex_skills" "test_install_codex_skills.bats" @@ -171,6 +172,7 @@ jobs: run_validator "python" "validate_python_templates.sh" run_validator "cpp" "validate_cpp_templates.sh" run_validator "ci" "validate_ci_templates.sh" + run_validator "project_templates" "validate_project_templates.sh" echo "========================================" echo "🔗 集成测试" diff --git a/tests/README.md b/tests/README.md index ea0ac56..2284263 100644 --- a/tests/README.md +++ b/tests/README.md @@ -9,12 +9,14 @@ tests/ ├── README.md # 本文件:测试文档 ├── scripts/ # Shell 脚本测试(bats) │ ├── test_sync_standards.bats # sync_standards.sh 测试 +│ ├── test_sync_templates.bats # sync_templates.sh 测试 │ ├── test_vendor_playbook.bats # vendor_playbook.sh 测试 │ └── test_install_codex_skills.bats # install_codex_skills.sh 测试 ├── templates/ # 模板验证测试 │ ├── validate_python_templates.sh # Python 模板验证 │ ├── validate_cpp_templates.sh # C++ 模板验证 -│ └── validate_ci_templates.sh # CI 模板验证 +│ ├── validate_ci_templates.sh # CI 模板验证 +│ └── validate_project_templates.sh # 项目模板验证 └── integration/ # 集成测试 └── check_doc_links.sh # 文档链接有效性检查 ``` @@ -31,6 +33,7 @@ cd /path/to/playbook sudo apt-get install bats # Ubuntu/Debian cd tests/scripts bats test_sync_standards.bats +bats test_sync_templates.bats bats test_vendor_playbook.bats bats test_install_codex_skills.bats @@ -39,6 +42,7 @@ cd tests/templates sh validate_python_templates.sh sh validate_cpp_templates.sh sh validate_ci_templates.sh +sh validate_project_templates.sh # 3. 运行集成测试 cd tests/integration @@ -88,6 +92,21 @@ sh check_doc_links.sh - **幂等性**: - 多次执行结果一致 +#### test_sync_templates.bats + +测试 `scripts/sync_templates.sh` 脚本的功能: + +- **基础同步**: + - 同步 memory-bank/ 与 docs/prompts/ + - 创建 AGENTS.md / AGENT_RULES.md / TODO.md / CONFIRM.md +- **占位符替换**: + - `{{PROJECT_NAME}}` 与 `{{DATE}}` +- **目录覆盖策略**: + - 无 `--force` 时不覆盖已有目录 + - `--force` 时覆盖并备份 +- **AGENTS.md 更新**: + - `--full` 更新 framework 区块 + #### test_vendor_playbook.bats 测试 `scripts/vendor_playbook.sh` 脚本的功能: @@ -227,6 +246,22 @@ sh check_doc_links.sh - 包含 README 说明文档 - 包含使用说明 +#### validate_project_templates.sh + +验证项目通用模板: + +- **核心模板**: + - `templates/AGENTS.template.md` + - `templates/AGENT_RULES.template.md` + - `templates/README.md` +- **memory-bank 模板**: + - 项目定位/技术栈/架构/进度/决策/实施计划 +- **prompts 模板**: + - `prompts/README.md` + - `prompts/system/agent-behavior.template.md` + - `prompts/coding/clarify.template.md` + - `prompts/coding/verify.template.md` + ### 3. 集成测试 (integration/) 端到端测试,验证整体功能。 @@ -236,7 +271,7 @@ sh check_doc_links.sh 检查所有 Markdown 文档中的链接有效性: - **扫描范围**: - - 所有 `*.md` 文件 + - 所有 `*.md` 文件(排除 `*.template.md`) - 排除 node_modules, .git, build, dist 等目录 - **链接类型**: - Markdown 链接:`[text](link)` @@ -296,6 +331,7 @@ pip3 install yamllint # Shell 脚本测试 cd tests/scripts bats test_sync_standards.bats --tap # TAP 格式输出 +bats test_sync_templates.bats bats test_vendor_playbook.bats --formatter junit # JUnit 格式 # 模板验证测试 @@ -303,6 +339,7 @@ cd tests/templates sh validate_python_templates.sh sh validate_cpp_templates.sh sh validate_ci_templates.sh +sh validate_project_templates.sh # 集成测试 cd tests/integration diff --git a/tests/integration/check_doc_links.sh b/tests/integration/check_doc_links.sh index d16c605..57cfc8d 100644 --- a/tests/integration/check_doc_links.sh +++ b/tests/integration/check_doc_links.sh @@ -127,6 +127,7 @@ echo "🔍 扫描 Markdown 文件..." cd "$PLAYBOOK_ROOT" MD_FILES=$(find . -name "*.md" \ + -not -name "*.template.md" \ -not -path "*/node_modules/*" \ -not -path "*/.git/*" \ -not -path "*/build/*" \ diff --git a/tests/scripts/test_sync_templates.bats b/tests/scripts/test_sync_templates.bats new file mode 100644 index 0000000..a50b47d --- /dev/null +++ b/tests/scripts/test_sync_templates.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats +# sync_templates.sh 测试套件 + +setup() { + export PLAYBOOK_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)" + export SCRIPT_PATH="$PLAYBOOK_ROOT/scripts/sync_templates.sh" + export TARGET_DIR="$(mktemp -d)" +} + +teardown() { + if [ -n "$TARGET_DIR" ] && [ -d "$TARGET_DIR" ]; then + chmod -R u+w "$TARGET_DIR" 2>/dev/null || true + rm -rf "$TARGET_DIR" + fi +} + +# ============================================== +# 基础功能测试 +# ============================================== + +@test "sync_templates.sh 脚本存在且可执行" { + [ -f "$SCRIPT_PATH" ] +} + +@test "sync_templates.sh - 基础同步与占位符替换" { + sh "$SCRIPT_PATH" --project-name "DemoProject" --date "2026-02-03" "$TARGET_DIR" + + [ -d "$TARGET_DIR/memory-bank" ] + [ -f "$TARGET_DIR/memory-bank/project-brief.md" ] + [ -f "$TARGET_DIR/docs/prompts/coding/clarify.md" ] + [ -f "$TARGET_DIR/AGENTS.md" ] + [ -f "$TARGET_DIR/AGENT_RULES.md" ] + [ -f "$TARGET_DIR/TODO.md" ] + [ -f "$TARGET_DIR/CONFIRM.md" ] + + grep -q "DemoProject" "$TARGET_DIR/memory-bank/project-brief.md" + ! grep -q "{{DATE}}" "$TARGET_DIR/TODO.md" + + [ -z "$(find "$TARGET_DIR" -name '*.template.md' -print -quit)" ] +} + +@test "sync_templates.sh - 已存在目录不覆盖 (无 --force)" { + mkdir -p "$TARGET_DIR/memory-bank" + mkdir -p "$TARGET_DIR/docs/prompts" + echo "keep" > "$TARGET_DIR/memory-bank/keep.md" + echo "keep" > "$TARGET_DIR/docs/prompts/keep.md" + + sh "$SCRIPT_PATH" "$TARGET_DIR" + + [ -f "$TARGET_DIR/memory-bank/keep.md" ] + [ ! -f "$TARGET_DIR/memory-bank/project-brief.md" ] + [ -f "$TARGET_DIR/docs/prompts/keep.md" ] + [ ! -f "$TARGET_DIR/docs/prompts/README.md" ] +} + +@test "sync_templates.sh - --force 覆盖并备份" { + mkdir -p "$TARGET_DIR/memory-bank" + echo "marker" > "$TARGET_DIR/memory-bank/marker.txt" + + sh "$SCRIPT_PATH" --force "$TARGET_DIR" + + [ -f "$TARGET_DIR/memory-bank/project-brief.md" ] + [ ! -f "$TARGET_DIR/memory-bank/marker.txt" ] + + backup_dir="$(ls -d "$TARGET_DIR"/memory-bank.bak.* 2>/dev/null | head -n 1)" + [ -n "$backup_dir" ] + [ -f "$backup_dir/marker.txt" ] +} + +@test "sync_templates.sh - --full 更新 framework 区块" { + cat > "$TARGET_DIR/AGENTS.md" << 'EOF' +# Agent Instructions + + +OLD_FRAMEWORK + + +Footer +EOF + + sh "$SCRIPT_PATH" --full "$TARGET_DIR" + + ! grep -q "OLD_FRAMEWORK" "$TARGET_DIR/AGENTS.md" + grep -q "" "$TARGET_DIR/AGENTS.md" + grep -q "Footer" "$TARGET_DIR/AGENTS.md" +} diff --git a/tests/scripts/test_vendor_playbook.bats b/tests/scripts/test_vendor_playbook.bats index 50ea96e..3d34c45 100644 --- a/tests/scripts/test_vendor_playbook.bats +++ b/tests/scripts/test_vendor_playbook.bats @@ -176,6 +176,20 @@ teardown() { [ -d "docs/standards/playbook/templates/ci" ] } +@test "vendor_playbook.sh - --apply-templates 应用模板到项目根目录" { + cd "$TARGET_DIR" + + sh "$SCRIPT_PATH" "$TARGET_DIR" cpp --apply-templates + + if [ -f "$PLAYBOOK_ROOT/templates/cpp/.clang-format" ]; then + [ -f ".clang-format" ] + fi + + if [ -f "$PLAYBOOK_ROOT/templates/ci/gitea/.gitea/workflows/standards-check.yml" ]; then + [ -f ".gitea/workflows/standards-check.yml" ] + fi +} + # ============================================== # 目标目录处理测试 # ============================================== diff --git a/tests/templates/validate_project_templates.sh b/tests/templates/validate_project_templates.sh new file mode 100644 index 0000000..ec0c54c --- /dev/null +++ b/tests/templates/validate_project_templates.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env sh +# 项目模板验证脚本 + +set -eu + +echo "========================================" +echo "🧩 项目模板验证" +echo "========================================" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +TEMPLATES_DIR="$PLAYBOOK_ROOT/templates" + +VALIDATION_PASSED=0 +VALIDATION_FAILED=0 +ERRORS_FILE="/tmp/project_template_validation_errors.txt" +REPORT_FILE="$SCRIPT_DIR/project_templates_report.txt" + +> "$ERRORS_FILE" +> "$REPORT_FILE" + +echo "📁 模板目录: $TEMPLATES_DIR" +echo "" + +# ============================================ +# 辅助函数 +# ============================================ + +validate_file_exists() { + local file="$1" + local description="$2" + + if [ -f "$file" ]; then + echo " ✅ $description: $(basename "$file")" + VALIDATION_PASSED=$((VALIDATION_PASSED + 1)) + return 0 + else + echo " ❌ $description: $(basename "$file") - 文件不存在" + echo "文件不存在: $file" >> "$ERRORS_FILE" + VALIDATION_FAILED=$((VALIDATION_FAILED + 1)) + return 1 + fi +} + +validate_contains() { + local file="$1" + local needle="$2" + local description="$3" + + if grep -Fq "$needle" "$file"; then + echo " ✓ $description" + VALIDATION_PASSED=$((VALIDATION_PASSED + 1)) + else + echo " ✗ $description" + echo "缺少内容: $needle in $file" >> "$ERRORS_FILE" + VALIDATION_FAILED=$((VALIDATION_FAILED + 1)) + fi +} + +echo "🔍 验证核心模板文件" +AGENTS_TEMPLATE="$TEMPLATES_DIR/AGENTS.template.md" +AGENT_RULES_TEMPLATE="$TEMPLATES_DIR/AGENT_RULES.template.md" +README_TEMPLATE="$TEMPLATES_DIR/README.md" + +if validate_file_exists "$AGENTS_TEMPLATE" "AGENTS.template.md"; then + validate_contains "$AGENTS_TEMPLATE" "" "包含 templates 标记" + validate_contains "$AGENTS_TEMPLATE" "" "包含 framework 标记" + validate_contains "$AGENTS_TEMPLATE" "{{DATE}}" "包含 {{DATE}} 占位符" +fi + +if validate_file_exists "$AGENT_RULES_TEMPLATE" "AGENT_RULES.template.md"; then + validate_contains "$AGENT_RULES_TEMPLATE" "AGENT_RULES" "包含 AGENT_RULES 标题" + validate_contains "$AGENT_RULES_TEMPLATE" "{{DATE}}" "包含 {{DATE}} 占位符" +fi + +validate_file_exists "$README_TEMPLATE" "templates/README.md" + +echo "" +echo "🔍 验证 memory-bank 模板" + +MEMORY_BANK_DIR="$TEMPLATES_DIR/memory-bank" +for name in project-brief tech-stack architecture progress decisions implementation-plan; do + validate_file_exists "$MEMORY_BANK_DIR/$name.template.md" "memory-bank/$name.template.md" +done + +echo "" +echo "🔍 验证 prompts 模板" + +PROMPTS_DIR="$TEMPLATES_DIR/prompts" +validate_file_exists "$PROMPTS_DIR/README.md" "prompts/README.md" +validate_file_exists "$PROMPTS_DIR/system/agent-behavior.template.md" "prompts/system/agent-behavior.template.md" +validate_file_exists "$PROMPTS_DIR/coding/clarify.template.md" "prompts/coding/clarify.template.md" +validate_file_exists "$PROMPTS_DIR/coding/verify.template.md" "prompts/coding/verify.template.md" + +echo "" + +# ============================================ +# 生成验证报告 +# ============================================ + +echo "========================================" +echo "📊 验证结果统计" +echo "========================================" +echo "✅ 通过: $VALIDATION_PASSED" +echo "❌ 失败: $VALIDATION_FAILED" + +if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then + echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%" +else + echo "📈 通过率: N/A (无测试项)" +fi + +echo "" + +{ + echo "项目模板验证报告" + echo "====================" + echo "" + echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "模板目录: $TEMPLATES_DIR" + echo "" + echo "统计结果:" + echo " 通过: $VALIDATION_PASSED" + echo " 失败: $VALIDATION_FAILED" + if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then + echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%" + fi + echo "" + if [ -s "$ERRORS_FILE" ]; then + echo "错误详情:" + cat "$ERRORS_FILE" + fi +} > "$REPORT_FILE" + +echo "📄 详细报告: $REPORT_FILE" +echo "========================================" + +rm -f "$ERRORS_FILE" + +if [ "$VALIDATION_FAILED" -eq 0 ]; then + echo "✅ 所有项目模板验证通过" + exit 0 +else + echo "❌ 项目模板验证失败 ($VALIDATION_FAILED 个错误)" + exit 1 +fi