diff options
Diffstat (limited to 'scripts/tests/sync-language-bundle.bats')
| -rw-r--r-- | scripts/tests/sync-language-bundle.bats | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/scripts/tests/sync-language-bundle.bats b/scripts/tests/sync-language-bundle.bats new file mode 100644 index 0000000..9fd1108 --- /dev/null +++ b/scripts/tests/sync-language-bundle.bats @@ -0,0 +1,153 @@ +#!/usr/bin/env bats +# +# Tests for scripts/sync-language-bundle.sh — per-project language-bundle +# freshness check for startup. +# +# Strategy: scaffold a synthetic project in a temp dir with a clean bundle +# (mirroring how install-lang.sh leaves .claude/), then perturb files and +# run the real script against it. Canonical source stays the real one (the +# script resolves languages/ + claude-rules/ relative to its own location). + +REAL_REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" +SCRIPT="$REAL_REPO/scripts/sync-language-bundle.sh" + +setup() { + PROJ="$(mktemp -d -t synclang-bats.XXXXXX)" +} + +teardown() { + rm -rf "$PROJ" +} + +# Mirror install-lang.sh: copy the bundle's files into a synthetic project. +install_bundle() { + local lang="$1" proj="$2" + mkdir -p "$proj/.claude/rules" + cp "$REAL_REPO/claude-rules"/*.md "$proj/.claude/rules/" + cp "$REAL_REPO/languages/$lang/claude/rules/"*.md "$proj/.claude/rules/" 2>/dev/null || true + if [ -d "$REAL_REPO/languages/$lang/claude/hooks" ]; then + mkdir -p "$proj/.claude/hooks" + cp -r "$REAL_REPO/languages/$lang/claude/hooks/." "$proj/.claude/hooks/" + fi + if [ -f "$REAL_REPO/languages/$lang/claude/settings.json" ]; then + cp "$REAL_REPO/languages/$lang/claude/settings.json" "$proj/.claude/settings.json" + fi + if [ -d "$REAL_REPO/languages/$lang/githooks" ]; then + mkdir -p "$proj/githooks" + cp -r "$REAL_REPO/languages/$lang/githooks/." "$proj/githooks/" + fi + # CLAUDE.md deliberately not seeded — the sync must treat its absence as + # fine (it's seed-only / project-owned, not bundle-tracked). +} + +matches_canonical() { # project-relative-path canonical-abs-path + diff -q "$PROJ/$1" "$2" >/dev/null 2>&1 +} + +# --- Normal: no bundle / clean --- + +@test "sync: project with no bundle is a quiet no-op (exit 0)" { + mkdir -p "$PROJ/.claude/rules" # generic rules only, no language fingerprint + cp "$REAL_REPO/claude-rules"/commits.md "$PROJ/.claude/rules/" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "sync: clean elisp bundle is a quiet no-op (exit 0)" { + install_bundle elisp "$PROJ" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "sync: absent CLAUDE.md is not flagged as drift (seed-only/project-owned)" { + install_bundle elisp "$PROJ" # helper never seeds CLAUDE.md + [ ! -f "$PROJ/CLAUDE.md" ] + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [ -z "$output" ] + [[ "$output" != *"CLAUDE.md"* ]] +} + +# --- Auto-fix: rules --- + +@test "sync: drifted language rule is auto-fixed and restored" { + install_bundle elisp "$PROJ" + echo ";; junk drift" >> "$PROJ/.claude/rules/elisp.md" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == *"fixed"* ]] + [[ "$output" == *".claude/rules/elisp.md"* ]] + matches_canonical ".claude/rules/elisp.md" "$REAL_REPO/languages/elisp/claude/rules/elisp.md" +} + +@test "sync: drifted generic rule is auto-fixed and restored" { + install_bundle elisp "$PROJ" + echo "junk" >> "$PROJ/.claude/rules/commits.md" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == *".claude/rules/commits.md"* ]] + matches_canonical ".claude/rules/commits.md" "$REAL_REPO/claude-rules/commits.md" +} + +@test "sync: missing rule is re-copied" { + install_bundle elisp "$PROJ" + rm "$PROJ/.claude/rules/elisp-testing.md" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [ -f "$PROJ/.claude/rules/elisp-testing.md" ] + matches_canonical ".claude/rules/elisp-testing.md" "$REAL_REPO/languages/elisp/claude/rules/elisp-testing.md" +} + +# --- Auto-fix: hooks --- + +@test "sync: drifted hook is auto-fixed and stays executable" { + install_bundle elisp "$PROJ" + echo "# junk" >> "$PROJ/.claude/hooks/validate-el.sh" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == *"validate-el.sh"* ]] + matches_canonical ".claude/hooks/validate-el.sh" "$REAL_REPO/languages/elisp/claude/hooks/validate-el.sh" + [ -x "$PROJ/.claude/hooks/validate-el.sh" ] +} + +# --- Surface-only: settings.json --- + +@test "sync: drifted settings.json is surfaced, NOT modified, exit 3" { + install_bundle elisp "$PROJ" + echo '{"_drift": true}' > "$PROJ/.claude/settings.json" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 3 ] + [[ "$output" == *"settings.json"* ]] + [[ "$output" == *"not auto-fixed"* ]] + # file left untouched — still carries the perturbation + grep -q "_drift" "$PROJ/.claude/settings.json" +} + +# --- Other language --- + +@test "sync: python bundle detected via python-testing.md and fixed" { + install_bundle python "$PROJ" + echo "junk" >> "$PROJ/.claude/rules/python-testing.md" + run bash "$SCRIPT" "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == *"python-testing.md"* ]] + matches_canonical ".claude/rules/python-testing.md" "$REAL_REPO/languages/python/claude/rules/python-testing.md" +} + +# --- Boundary / Error --- + +@test "sync: defaults to \$PWD when no path given" { + install_bundle elisp "$PROJ" + echo "junk" >> "$PROJ/.claude/rules/elisp.md" + run bash -c "cd '$PROJ' && bash '$SCRIPT'" + [ "$status" -eq 0 ] + [[ "$output" == *"elisp.md"* ]] +} + +@test "sync: nonexistent project path errors with exit 1" { + run bash "$SCRIPT" "/no/such/path/here" + [ "$status" -eq 1 ] + [[ "$output" == *"does not exist"* ]] +} |
