blob: 3df69c9ff3b2dbf5bc17659f8bc8754dc6f29f65 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#!/usr/bin/env bats
#
# Tests for scripts/audit.sh — cross-project .ai/ drift detector.
#
# Strategy: redirect HOME to a temp dir per test, scaffold synthetic
# project trees under HOME/code/, run the real audit.sh against the
# synthetic state. The canonical source stays the real one (audit.sh
# resolves it relative to its own location, not HOME).
REAL_REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
AUDIT="$REAL_REPO/scripts/audit.sh"
CANONICAL="$REAL_REPO/claude-templates/.ai"
setup() {
TEST_HOME="$(mktemp -d -t audit-bats.XXXXXX)"
HOME_BAK="$HOME"
export HOME="$TEST_HOME"
mkdir -p "$TEST_HOME/code" "$TEST_HOME/projects" "$TEST_HOME/.emacs.d"
}
teardown() {
export HOME="$HOME_BAK"
rm -rf "$TEST_HOME"
}
# Mirror canonical .ai/ content into a synthetic project, matching how
# the startup-rsync would leave a clean .ai/.
scaffold_synced_ai() {
local proj_dir="$1"
mkdir -p "$proj_dir/.ai"
rsync -a "$CANONICAL/protocols.org" "$proj_dir/.ai/protocols.org"
rsync -a --delete "$CANONICAL/workflows/" "$proj_dir/.ai/workflows/"
rsync -a --delete "$CANONICAL/scripts/" "$proj_dir/.ai/scripts/"
}
# Make a project a tracked git checkout with .ai/ committed clean.
# Subsequent edits to .ai/ then count as uncommitted (dirty) per
# audit's git status check.
git_init_with_ai_tracked() {
local proj_dir="$1"
(cd "$proj_dir" \
&& git init -q \
&& git add -A \
&& git -c user.email=test@test -c user.name=test commit -q -m initial)
}
@test "audit: clean projects report ok with exit 0" {
scaffold_synced_ai "$TEST_HOME/code/alpha"
scaffold_synced_ai "$TEST_HOME/code/beta"
run bash "$AUDIT" --no-doctor
[ "$status" -eq 0 ]
[[ "$output" == *"ok ~/code/alpha"* ]]
[[ "$output" == *"ok ~/code/beta"* ]]
[[ "$output" == *"Summary: 2 ok, 0 drift, 0 skipped, 0 failed"* ]]
}
@test "audit: drift detection reports drift and exits 1" {
scaffold_synced_ai "$TEST_HOME/code/alpha"
echo "# drift marker" >> "$TEST_HOME/code/alpha/.ai/protocols.org"
run bash "$AUDIT" --no-doctor
[ "$status" -eq 1 ]
[[ "$output" == *"drift ~/code/alpha"* ]]
[[ "$output" == *"1 drift"* ]]
}
@test "audit --apply: drifted untracked project converges" {
scaffold_synced_ai "$TEST_HOME/code/alpha"
echo "# drift marker" >> "$TEST_HOME/code/alpha/.ai/protocols.org"
run bash "$AUDIT" --apply --no-doctor
# Applied counts as non-ok in the audit summary, so exit 1.
[ "$status" -eq 1 ]
[[ "$output" == *"applied ~/code/alpha"* ]]
# Re-run confirms convergence.
run bash "$AUDIT" --no-doctor
[ "$status" -eq 0 ]
[[ "$output" == *"ok ~/code/alpha"* ]]
}
@test "audit: tracked project with dirty .ai/ is skipped" {
scaffold_synced_ai "$TEST_HOME/code/alpha"
git_init_with_ai_tracked "$TEST_HOME/code/alpha"
echo "# uncommitted" >> "$TEST_HOME/code/alpha/.ai/protocols.org"
run bash "$AUDIT" --apply --no-doctor
[ "$status" -eq 1 ]
[[ "$output" == *"skipped ~/code/alpha"* ]]
[[ "$output" == *"use --force"* ]]
# The dirty edit survived (skip is a safety guard).
grep -q "# uncommitted" "$TEST_HOME/code/alpha/.ai/protocols.org"
}
@test "audit --apply --force: tracked dirty .ai/ gets clobbered" {
# Edge case 1 from todo.org:1766. Verifies the override path.
scaffold_synced_ai "$TEST_HOME/code/alpha"
git_init_with_ai_tracked "$TEST_HOME/code/alpha"
echo "# uncommitted" >> "$TEST_HOME/code/alpha/.ai/protocols.org"
run bash "$AUDIT" --apply --force --no-doctor
[ "$status" -eq 1 ]
[[ "$output" == *"applied ~/code/alpha"* ]]
# The uncommitted edit is gone — force clobbered it.
! grep -q "# uncommitted" "$TEST_HOME/code/alpha/.ai/protocols.org"
}
@test "audit: loop continues past .ai/-missing failure" {
# Edge case 2 from todo.org:1766. The defensive [ ! -d "$proj/.ai" ]
# branch fires when a discovered .ai/ disappears between find and
# the loop iteration. That race can't be timed reliably, so stub
# find to inject a fabricated path; the for-loop hits the FAIL
# branch on the ghost and must continue to the real project.
scaffold_synced_ai "$TEST_HOME/code/alpha"
stub_bin="$(mktemp -d)"
cat > "$stub_bin/find" <<EOF
#!/bin/bash
/usr/bin/find "\$@"
echo "$TEST_HOME/code/ghost/.ai"
EOF
chmod +x "$stub_bin/find"
PATH="$stub_bin:$PATH" run bash "$AUDIT" --no-doctor
rm -rf "$stub_bin"
[ "$status" -eq 1 ]
[[ "$output" == *"FAIL ~/code/ghost"* ]]
[[ "$output" == *".ai/ missing"* ]]
# Loop continued past the failure to the real project.
[[ "$output" == *"ok ~/code/alpha"* ]]
}
|