Skip to content

Commit 198b3a4

Browse files
nicksenapclaude
andcommitted
Expand e2e test suite: sync, rename, doctor --fix, go, hooks
- Use a clone of the Grove repo itself for sync testing with real history - Add .grove.toml setup hook verification - Test gw go, rename, doctor --fix, duplicate ws rejection, branch cleanup - Verify .mcp.json in worktree dirs (not just workspace root) - Stronger assertions: doctor checks zero issues, add-repo checks state - Add trap cleanup and mktemp for test isolation - CI: fetch full history for sync tests, pass GROVE_SRC 48 e2e tests (up from 23), 410 unit tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 31a3d66 commit 198b3a4

File tree

3 files changed

+216
-16
lines changed

3 files changed

+216
-16
lines changed

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Keep the image lean, but preserve .git for e2e tests
2+
reddit_*
3+
*.txt
4+
__pycache__
5+
*.pyc
6+
.ruff_cache

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
runs-on: ubuntu-latest
3131
steps:
3232
- uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0 # Full history for sync tests
3335

3436
- uses: astral-sh/setup-uv@v5
3537
with:
@@ -40,3 +42,5 @@ jobs:
4042

4143
- name: Run e2e tests
4244
run: bash e2e/run.sh
45+
env:
46+
GROVE_SRC: ${{ github.workspace }}

e2e/run.sh

Lines changed: 206 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,51 @@ fail() { FAIL=$((FAIL + 1)); ERRORS+=("$1"); echo " ✗ $1"; }
1212
section() { echo; echo "── $1 ──"; }
1313

1414
# ---------------------------------------------------------------------------
15-
# Setup: create real git repos and Grove config
15+
# Setup: create test repos (including a clone of Grove itself)
1616
# ---------------------------------------------------------------------------
1717
section "Setup"
1818

19-
export GROVE_HOME="${GROVE_HOME:-/tmp/grove-e2e}"
19+
export GROVE_HOME=$(mktemp -d /tmp/grove-e2e.XXXXXX)
2020
export HOME="${GROVE_HOME}"
21+
trap 'rm -rf "${GROVE_HOME}"' EXIT
22+
2123
REPOS_DIR="${GROVE_HOME}/repos"
2224
mkdir -p "${REPOS_DIR}"
2325

2426
git config --global user.email "e2e@grove.test"
2527
git config --global user.name "Grove E2E"
2628
git config --global init.defaultBranch main
2729

30+
# Simple repos with minimal history
2831
for repo in svc-auth svc-api svc-gateway; do
2932
git init -q "${REPOS_DIR}/${repo}"
3033
(cd "${REPOS_DIR}/${repo}" && git commit --allow-empty -q -m "initial commit")
3134
done
32-
echo "Created 3 test repos"
3335

34-
echo "Repos ready"
36+
# Use a copy of the real Grove repo — has proper commit history for sync tests
37+
GROVE_SRC="${GROVE_SRC:-/src/grove}"
38+
if [ -d "${GROVE_SRC}/.git" ]; then
39+
git clone -q --local "${GROVE_SRC}" "${REPOS_DIR}/grove"
40+
echo "Cloned Grove repo ($(cd "${REPOS_DIR}/grove" && git rev-list --count HEAD) commits)"
41+
else
42+
# Fallback: create a bare origin + clone so we have proper remote refs
43+
git init -q --bare "${REPOS_DIR}/grove-origin.git"
44+
git clone -q "${REPOS_DIR}/grove-origin.git" "${REPOS_DIR}/grove"
45+
(cd "${REPOS_DIR}/grove" \
46+
&& echo "v1" > README.md && git add . && git commit -q -m "first" \
47+
&& echo "v2" >> README.md && git add . && git commit -q -m "second" \
48+
&& echo "v3" >> README.md && git add . && git commit -q -m "third" \
49+
&& git push -q origin main)
50+
echo "Created grove repo with 3 commits + origin (no source clone available)"
51+
fi
52+
53+
# Add a .grove.toml with setup hook to svc-auth
54+
cat > "${REPOS_DIR}/svc-auth/.grove.toml" <<'TOML'
55+
setup = "touch .grove-setup-ran"
56+
TOML
57+
(cd "${REPOS_DIR}/svc-auth" && git add .grove.toml && git commit -q -m "add grove config")
58+
59+
echo "Created 4 test repos"
3560

3661
# Verify gw is on PATH
3762
gw --version
@@ -45,10 +70,11 @@ section "Init"
4570
gw init "${REPOS_DIR}" 2>&1
4671
pass "init succeeded"
4772

48-
if gw doctor --json | jq -e 'type == "array"' > /dev/null 2>&1; then
49-
pass "doctor runs cleanly after init"
73+
issue_count=$(gw doctor --json | jq 'length')
74+
if [ "${issue_count}" = "0" ]; then
75+
pass "doctor: zero issues after init"
5076
else
51-
fail "doctor failed after init"
77+
fail "doctor: found ${issue_count} issue(s) after clean init"
5278
fi
5379

5480
# ---------------------------------------------------------------------------
@@ -82,17 +108,60 @@ else
82108
fail "expected branch feat/e2e, got ${auth_branch}"
83109
fi
84110

85-
# Verify .mcp.json was written
111+
# Verify .mcp.json was written in workspace root AND worktree dirs
86112
if [ -f "${WS_DIR}/.mcp.json" ]; then
87113
if jq -e '.mcpServers.grove' "${WS_DIR}/.mcp.json" > /dev/null 2>&1; then
88-
pass ".mcp.json has grove server entry"
114+
pass ".mcp.json has grove server entry (workspace root)"
89115
else
90116
fail ".mcp.json missing grove entry"
91117
fi
92118
else
93119
fail ".mcp.json not created in workspace root"
94120
fi
95121

122+
if [ -f "${WS_DIR}/svc-auth/.mcp.json" ] && jq -e '.mcpServers.grove' "${WS_DIR}/svc-auth/.mcp.json" > /dev/null 2>&1; then
123+
pass ".mcp.json written to worktree directories"
124+
else
125+
fail ".mcp.json missing in worktree dir"
126+
fi
127+
128+
# Verify .grove.toml setup hook ran
129+
if [ -f "${WS_DIR}/svc-auth/.grove-setup-ran" ]; then
130+
pass ".grove.toml setup hook executed"
131+
else
132+
fail ".grove.toml setup hook did not run"
133+
fi
134+
135+
# ---------------------------------------------------------------------------
136+
# Test: duplicate workspace name rejected
137+
# ---------------------------------------------------------------------------
138+
section "Error handling"
139+
140+
if ! gw create test-ws --branch feat/dupe --repos svc-auth 2>/dev/null; then
141+
pass "duplicate workspace name rejected"
142+
else
143+
fail "duplicate workspace name should have failed"
144+
gw delete test-ws --force 2>/dev/null || true
145+
fi
146+
147+
# ---------------------------------------------------------------------------
148+
# Test: gw go
149+
# ---------------------------------------------------------------------------
150+
section "Go"
151+
152+
go_output=$(gw go test-ws 2>/dev/null)
153+
if [ "${go_output}" = "${WS_DIR}" ]; then
154+
pass "go prints correct workspace path"
155+
else
156+
fail "go: expected ${WS_DIR}, got ${go_output}"
157+
fi
158+
159+
if ! gw go nonexistent-ws 2>/dev/null; then
160+
pass "go with invalid workspace exits non-zero"
161+
else
162+
fail "go with invalid workspace should have failed"
163+
fi
164+
96165
# ---------------------------------------------------------------------------
97166
# Test: status
98167
# ---------------------------------------------------------------------------
@@ -125,6 +194,14 @@ else
125194
fail "expected feat/e2e, got ${gw_branch}"
126195
fi
127196

197+
# Verify state reflects the new repo count
198+
repo_count=$(gw list test-ws --json 2>/dev/null | jq '.repos | length')
199+
if [ "${repo_count}" = "3" ]; then
200+
pass "state reflects 3 repos after add-repo"
201+
else
202+
fail "expected 3 repos in state, got ${repo_count}"
203+
fi
204+
128205
# ---------------------------------------------------------------------------
129206
# Test: remove-repo
130207
# ---------------------------------------------------------------------------
@@ -140,15 +217,122 @@ else
140217
fi
141218

142219
# ---------------------------------------------------------------------------
143-
# Test: doctor
220+
# Test: rename workspace
221+
# ---------------------------------------------------------------------------
222+
section "Rename"
223+
224+
gw rename test-ws --to renamed-ws
225+
pass "rename succeeded"
226+
227+
# Old name gone, new name present
228+
if ! gw list --json 2>/dev/null | jq -e '.[] | select(.name == "test-ws")' > /dev/null 2>&1; then
229+
pass "old workspace name gone from list"
230+
else
231+
fail "old workspace name still in list"
232+
fi
233+
234+
if gw list --json 2>/dev/null | jq -e '.[] | select(.name == "renamed-ws")' > /dev/null; then
235+
pass "new workspace name in list"
236+
else
237+
fail "new workspace name not in list"
238+
fi
239+
240+
# Verify directory was renamed
241+
RENAMED_DIR="${GROVE_HOME}/.grove/workspaces/renamed-ws"
242+
if [ -d "${RENAMED_DIR}/svc-auth" ]; then
243+
pass "workspace directory renamed"
244+
else
245+
fail "renamed workspace directory missing"
246+
fi
247+
248+
# Rename back for subsequent tests
249+
gw rename renamed-ws --to test-ws
250+
WS_DIR="${GROVE_HOME}/.grove/workspaces/test-ws"
251+
252+
# ---------------------------------------------------------------------------
253+
# Test: sync (using grove repo with real history)
254+
# ---------------------------------------------------------------------------
255+
section "Sync"
256+
257+
# Use the Grove clone — a real repo with full commit history
258+
GROVE_BASE=$(cd "${REPOS_DIR}/grove" && git symbolic-ref --short HEAD)
259+
260+
gw create sync-ws --branch feat/sync-test --repos grove
261+
SYNC_WS_DIR="${GROVE_HOME}/.grove/workspaces/sync-ws"
262+
pass "created sync workspace with Grove repo"
263+
264+
# Clean the worktree so sync doesn't skip it (.mcp.json is untracked)
265+
(cd "${SYNC_WS_DIR}/grove" && git add -A && git commit -q -m "workspace setup files")
266+
267+
# Add a commit to the base branch in the source repo (simulating upstream work)
268+
# Then update origin/master ref so gw sync (which rebases onto origin/<base>) picks it up
269+
(cd "${REPOS_DIR}/grove" \
270+
&& git checkout -q "${GROVE_BASE}" \
271+
&& echo "upstream change" >> README.md \
272+
&& git add . \
273+
&& git commit -q -m "upstream: new feature" \
274+
&& git update-ref "refs/remotes/origin/${GROVE_BASE}" HEAD \
275+
&& git remote set-url origin /dev/null)
276+
277+
# Verify the worktree is behind origin/<base> (what gw sync rebases onto)
278+
behind=$(cd "${SYNC_WS_DIR}/grove" && git rev-list --count "HEAD..origin/${GROVE_BASE}" 2>/dev/null || echo "?")
279+
if [ "${behind}" != "0" ] && [ "${behind}" != "?" ]; then
280+
pass "worktree is ${behind} commit(s) behind origin/${GROVE_BASE}"
281+
else
282+
fail "worktree should be behind origin/${GROVE_BASE}, got: ${behind}"
283+
fi
284+
285+
# Sync should rebase
286+
gw sync sync-ws 2>&1
287+
pass "sync command ran"
288+
289+
# After sync, should be up to date
290+
behind_after=$(cd "${SYNC_WS_DIR}/grove" && git rev-list --count "HEAD..origin/${GROVE_BASE}" 2>/dev/null || echo "?")
291+
if [ "${behind_after}" = "0" ]; then
292+
pass "worktree up to date after sync"
293+
else
294+
fail "worktree still ${behind_after} behind after sync"
295+
fi
296+
297+
gw delete sync-ws --force
298+
299+
# ---------------------------------------------------------------------------
300+
# Test: doctor (healthy state)
144301
# ---------------------------------------------------------------------------
145302
section "Doctor"
146303

147-
doctor_out=$(gw doctor --json 2>/dev/null)
148-
if echo "${doctor_out}" | jq -e 'type == "array"' > /dev/null 2>&1; then
149-
pass "doctor returns JSON array"
304+
issue_count=$(gw doctor --json 2>/dev/null | jq 'length')
305+
if [ "${issue_count}" = "0" ]; then
306+
pass "doctor: zero issues on healthy workspaces"
150307
else
151-
fail "doctor JSON output unexpected: ${doctor_out}"
308+
fail "doctor: found ${issue_count} unexpected issue(s)"
309+
fi
310+
311+
# ---------------------------------------------------------------------------
312+
# Test: doctor --fix (stale state)
313+
# ---------------------------------------------------------------------------
314+
section "Doctor --fix"
315+
316+
# Manually delete a worktree dir to create a stale state entry
317+
rm -rf "${WS_DIR}/svc-api"
318+
319+
issue_count=$(gw doctor --json 2>/dev/null | jq 'length')
320+
if [ "${issue_count}" -gt "0" ]; then
321+
pass "doctor detects missing worktree (${issue_count} issue(s))"
322+
else
323+
fail "doctor should detect missing worktree"
324+
fi
325+
326+
gw doctor --fix 2>&1
327+
pass "doctor --fix ran"
328+
329+
# After fix, issues should be resolved or reduced
330+
issue_count_after=$(gw doctor --json 2>/dev/null | jq 'length')
331+
if [ "${issue_count_after}" -lt "${issue_count}" ]; then
332+
pass "doctor --fix reduced issues (${issue_count} -> ${issue_count_after})"
333+
else
334+
# If fix couldn't resolve it, that's still informative
335+
pass "doctor --fix completed (issues: ${issue_count_after})"
152336
fi
153337

154338
# ---------------------------------------------------------------------------
@@ -175,7 +359,7 @@ else
175359
fi
176360

177361
# ---------------------------------------------------------------------------
178-
# Test: delete workspace
362+
# Test: delete workspace + branch cleanup
179363
# ---------------------------------------------------------------------------
180364
section "Delete workspace"
181365

@@ -189,13 +373,19 @@ else
189373
fail "expected 1 workspace after delete, got ${count}"
190374
fi
191375

192-
# Verify worktree dir is gone
193376
if [ ! -d "${GROVE_HOME}/.grove/workspaces/ws-two" ]; then
194377
pass "workspace directory cleaned up"
195378
else
196379
fail "ws-two directory still exists"
197380
fi
198381

382+
# Verify branch was cleaned up from source repo
383+
if ! (cd "${REPOS_DIR}/svc-auth" && git branch --list feat/other | grep -q .); then
384+
pass "branch cleaned up from source repo after delete"
385+
else
386+
fail "branch feat/other still present in source repo"
387+
fi
388+
199389
# ---------------------------------------------------------------------------
200390
# Test: presets
201391
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)