Skip to content

Commit 5dd78ad

Browse files
kevincodex1claude
andcommitted
test(tools): remove the move source after planning via a deterministic hook
The refused-move test now creates source.txt, plans the move, and removes the file through a test-only pre-commit hook (structuredPatchBeforeCommit) so it exercises the post-planning disappearance path rather than a source that was never there. Assertions unchanged: refused before publishing, no destination created. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012JqvAf7zsP1qYFVZayKXHq
1 parent 03e2da1 commit 5dd78ad

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

‎internal/tools/structured_patch.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,15 @@ func forgetStructuredPatchFiles(tracker *FileTracker, changes []structuredPatchC
700700
}
701701
}
702702

703+
// structuredPatchBeforeCommit, when set, runs before each change's pre-commit
704+
// recheck. Tests use it to alter the filesystem between planning and commit
705+
// deterministically; it is nil in production.
706+
var structuredPatchBeforeCommit func(change structuredPatchChange)
707+
703708
func applyStructuredPatchChange(root *os.Root, change structuredPatchChange) (bool, error) {
709+
if structuredPatchBeforeCommit != nil {
710+
structuredPatchBeforeCommit(change)
711+
}
704712
// The planner validated the source against the bytes it read; re-read
705713
// through the same root immediately before committing so a change made
706714
// by another process in between is refused rather than overwritten or

‎internal/tools/write_tools_test.go‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ func TestStructuredPatchFailedDeleteDoesNotRecreateMissingFile(t *testing.T) {
866866
}
867867
}
868868

869-
// A move whose source vanished after planning is refused by the pre-commit
869+
// A move whose source vanishes after planning is refused by the pre-commit
870870
// recheck before the destination is published, so nothing is left half-done.
871871
func TestStructuredPatchMoveWithMissingSourceIsRefusedBeforePublishing(t *testing.T) {
872872
root := t.TempDir()
@@ -877,14 +877,27 @@ func TestStructuredPatchMoveWithMissingSourceIsRefusedBeforePublishing(t *testin
877877
defer workspace.Close()
878878
sourcePath := filepath.Join(root, "source.txt")
879879
destinationPath := filepath.Join(root, "destination.txt")
880+
writeTestFile(t, sourcePath, "removed source\n")
880881
change := structuredPatchChange{
881882
kind: structuredPatchUpdate,
882883
from: structuredPatchTarget{absolute: sourcePath, relative: "source.txt"},
883884
to: structuredPatchTarget{absolute: destinationPath, relative: "destination.txt"},
884885
before: "removed source\n", after: "moved content\n", mode: 0o644,
885886
}
887+
// The source exists at planning time and disappears just before commit.
888+
removed := false
889+
structuredPatchBeforeCommit = func(structuredPatchChange) {
890+
if err := os.Remove(sourcePath); err != nil {
891+
t.Fatal(err)
892+
}
893+
removed = true
894+
}
895+
defer func() { structuredPatchBeforeCommit = nil }()
886896

887897
err = applyStructuredPatchChanges(workspace, []structuredPatchChange{change}, nil)
898+
if !removed {
899+
t.Fatal("pre-commit hook did not run")
900+
}
888901
if err == nil || !strings.Contains(err.Error(), "before commit") || strings.Contains(err.Error(), "partially applied") {
889902
t.Fatalf("move with a missing source = %v, want a pre-commit refusal with nothing applied", err)
890903
}

0 commit comments

Comments
 (0)