demo
This commit is contained in:
356
tools/tools_test.go
Normal file
356
tools/tools_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func writeMockShell(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
|
||||
shellPath := filepath.Join(dir, "mock-shell")
|
||||
script := `#!/bin/sh
|
||||
set -eu
|
||||
printf '%s\n' "$1" > shell-flag.txt
|
||||
printf '%s\n' "$2" > shell-command.txt
|
||||
shift
|
||||
eval "$1"
|
||||
`
|
||||
if err := os.WriteFile(shellPath, []byte(script), 0755); err != nil {
|
||||
t.Fatalf("failed to write mock shell: %v", err)
|
||||
}
|
||||
return shellPath
|
||||
}
|
||||
|
||||
func TestValidateResolvesRelativePathAgainstWorkspace(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
|
||||
got, err := validator.Validate("src/main.go")
|
||||
if err != nil {
|
||||
t.Fatalf("Validate returned error: %v", err)
|
||||
}
|
||||
|
||||
want := filepath.Join(workspace, "src/main.go")
|
||||
if got != want {
|
||||
t.Fatalf("Validate returned %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsTraversalOutsideWorkspace(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
|
||||
if _, err := validator.Validate("../outside.go"); err == nil {
|
||||
t.Fatal("expected Validate to reject path traversal outside the workspace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFileReturnsRequestedLineRange(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
path := filepath.Join(workspace, "sample.txt")
|
||||
if err := os.WriteFile(path, []byte("alpha\nbeta\ngamma\ndelta\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
result, err := validator.ReadFile(context.Background(), "sample.txt", 2, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("ReadFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Content, "Lines 2-3 of 4:") {
|
||||
t.Fatalf("ReadFile content did not include requested range: %q", result.Content)
|
||||
}
|
||||
if !strings.Contains(result.Content, "2: beta") || !strings.Contains(result.Content, "3: gamma") {
|
||||
t.Fatalf("ReadFile content did not include expected lines: %q", result.Content)
|
||||
}
|
||||
if strings.Contains(result.Content, "1: alpha") || strings.Contains(result.Content, "4: delta") {
|
||||
t.Fatalf("ReadFile content included lines outside the requested range: %q", result.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFileReplacesRangePrependsAndCreates(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("replace middle range", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "sample.txt")
|
||||
if err := os.WriteFile(path, []byte("one\ntwo\nthree\nfour\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.WriteFile(ctx, "sample.txt", 2, 3, "TWO\nTHREE")
|
||||
if err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("WriteFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read updated file: %v", err)
|
||||
}
|
||||
want := "one\nTWO\nTHREE\nfour"
|
||||
if string(got) != want {
|
||||
t.Fatalf("WriteFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("prepend at start", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "prepend.txt")
|
||||
if err := os.WriteFile(path, []byte("body\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.WriteFile(ctx, "prepend.txt", 1, 0, "header")
|
||||
if err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("WriteFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read updated file: %v", err)
|
||||
}
|
||||
want := "header\nbody"
|
||||
if string(got) != want {
|
||||
t.Fatalf("WriteFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("create new file", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "created.txt")
|
||||
|
||||
result, err := validator.WriteFile(ctx, "created.txt", 1, -1, "first\nsecond")
|
||||
if err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("WriteFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read created file: %v", err)
|
||||
}
|
||||
want := "first\nsecond"
|
||||
if string(got) != want {
|
||||
t.Fatalf("WriteFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunCommandExecutesThroughShell(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
shellDir := t.TempDir()
|
||||
t.Setenv("SHELL", writeMockShell(t, shellDir))
|
||||
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
result, err := validator.RunCommand(context.Background(), "echo hello from shell", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("RunCommand returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("RunCommand returned unsuccessful result: %+v", result)
|
||||
}
|
||||
if got, want := strings.TrimSpace(result.Content), "hello from shell"; got != want {
|
||||
t.Fatalf("RunCommand output = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
commandBytes, err := os.ReadFile(filepath.Join(workspace, "shell-command.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read shell command capture: %v", err)
|
||||
}
|
||||
if got, want := strings.TrimSpace(string(commandBytes)), "echo hello from shell"; got != want {
|
||||
t.Fatalf("shell command capture = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandQuotesArgsInShellCommand(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
shellDir := t.TempDir()
|
||||
t.Setenv("SHELL", writeMockShell(t, shellDir))
|
||||
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
result, err := validator.RunCommand(context.Background(), "printf", []string{"%s", "hello world"})
|
||||
if err != nil {
|
||||
t.Fatalf("RunCommand returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("RunCommand returned unsuccessful result: %+v", result)
|
||||
}
|
||||
if got, want := strings.TrimSpace(result.Content), "hello world"; got != want {
|
||||
t.Fatalf("RunCommand output = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
commandBytes, err := os.ReadFile(filepath.Join(workspace, "shell-command.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read shell command capture: %v", err)
|
||||
}
|
||||
commandLine := strings.TrimSpace(string(commandBytes))
|
||||
if !strings.Contains(commandLine, "'hello world'") {
|
||||
t.Fatalf("shell command capture did not quote spaced argument: %q", commandLine)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandRunsInstalledToolThroughShellPath(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
|
||||
result, err := validator.RunCommand(context.Background(), "go version", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("RunCommand returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("RunCommand returned unsuccessful result: %+v", result)
|
||||
}
|
||||
if !strings.Contains(result.Content, "go version") {
|
||||
t.Fatalf("RunCommand output did not include go version information: %q", result.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchFileGuardsAgainstStaleRanges(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
validator := NewWorkspaceValidator(workspace)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("applies when expected text matches", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "guarded.txt")
|
||||
if err := os.WriteFile(path, []byte("one\ntwo\nthree\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.PatchFile(ctx, "guarded.txt", 2, 2, "two", "TWO")
|
||||
if err != nil {
|
||||
t.Fatalf("PatchFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("PatchFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read patched file: %v", err)
|
||||
}
|
||||
want := "one\nTWO\nthree"
|
||||
if string(got) != want {
|
||||
t.Fatalf("PatchFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("matches a snippet inside a larger requested window", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "snippet.txt")
|
||||
original := "alpha\nbeta\n\n\nneedle\nomega\n"
|
||||
if err := os.WriteFile(path, []byte(original), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.PatchFile(ctx, "snippet.txt", 1, -1, "\n\nneedle", "\n\nreplacement")
|
||||
if err != nil {
|
||||
t.Fatalf("PatchFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("PatchFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read patched file: %v", err)
|
||||
}
|
||||
want := "alpha\nbeta\n\n\nreplacement\nomega"
|
||||
if string(got) != want {
|
||||
t.Fatalf("PatchFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("accepts read_file output as expected text", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "readfile.txt")
|
||||
if err := os.WriteFile(path, []byte("alpha\nbeta\ngamma\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
readResult, err := validator.ReadFile(ctx, "readfile.txt", 1, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile returned error: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.PatchFile(ctx, "readfile.txt", 1, 2, readResult.Content, "ALPHA\nBETA")
|
||||
if err != nil {
|
||||
t.Fatalf("PatchFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("PatchFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read patched file: %v", err)
|
||||
}
|
||||
want := "ALPHA\nBETA\ngamma"
|
||||
if string(got) != want {
|
||||
t.Fatalf("PatchFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("matches blank lines exactly", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "blank.txt")
|
||||
if err := os.WriteFile(path, []byte("alpha\n\nomega\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.PatchFile(ctx, "blank.txt", 2, 2, "\n", "BETA")
|
||||
if err != nil {
|
||||
t.Fatalf("PatchFile returned error: %v", err)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Fatalf("PatchFile returned unsuccessful result: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read patched file: %v", err)
|
||||
}
|
||||
want := "alpha\nBETA\nomega"
|
||||
if string(got) != want {
|
||||
t.Fatalf("PatchFile wrote %q, want %q", string(got), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects stale content", func(t *testing.T) {
|
||||
path := filepath.Join(workspace, "stale.txt")
|
||||
if err := os.WriteFile(path, []byte("alpha\nbeta\n"), 0644); err != nil {
|
||||
t.Fatalf("failed to write sample file: %v", err)
|
||||
}
|
||||
|
||||
result, err := validator.PatchFile(ctx, "stale.txt", 2, 2, "gamma", "BETA")
|
||||
if err != nil {
|
||||
t.Fatalf("PatchFile returned error: %v", err)
|
||||
}
|
||||
if result.Success {
|
||||
t.Fatalf("expected guarded patch to fail, got success: %+v", result)
|
||||
}
|
||||
if !strings.Contains(result.Error, "patch guard failed") {
|
||||
t.Fatalf("unexpected guarded patch error: %+v", result)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read unchanged file: %v", err)
|
||||
}
|
||||
want := "alpha\nbeta\n"
|
||||
if string(got) != want {
|
||||
t.Fatalf("PatchFile changed file content unexpectedly: %q", string(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user