200 lines
5.2 KiB
Go
200 lines
5.2 KiB
Go
package builtin
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestEditFileFuzzyTrailingWhitespace(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "main.go")
|
|
seed := "func main() { \n\tfmt.Println(\"hello\") \n}\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "func main() {\n\tfmt.Println(\"hello\")\n}",
|
|
"new_string": "func main() {\n\tfmt.Println(\"bye\")\n}",
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("edit_file: %v", err)
|
|
}
|
|
if !strings.Contains(out, "fuzzy match") {
|
|
t.Fatalf("output should disclose fuzzy matching, got %q", out)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "func main() {\n\tfmt.Println(\"bye\")\n}\n"
|
|
if string(got) != want {
|
|
t.Fatalf("content = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEditFileFuzzyReadFileLinePrefixes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "notes.txt")
|
|
seed := "alpha\nbeta\ngamma\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "1\u2192alpha\n2\u2192beta",
|
|
"new_string": "ALPHA\nBETA",
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("edit_file: %v", err)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "ALPHA\nBETA\ngamma\n"
|
|
if string(got) != want {
|
|
t.Fatalf("content = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEditFileFuzzyCRLFPreservesLineEndings(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "win.txt")
|
|
seed := "one \r\ntwo \r\nthree\r\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "one\ntwo",
|
|
"new_string": "ONE\nTWO",
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("edit_file: %v", err)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "ONE\r\nTWO\r\nthree\r\n"
|
|
if string(got) != want {
|
|
t.Fatalf("content = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEditFileCRLFNotFoundHintAvoidsMisattribution(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "win.txt")
|
|
seed := "one\r\ntwo\r\nthree\r\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "one\nmissing",
|
|
"new_string": "ONE\nMISSING",
|
|
}))
|
|
if err == nil {
|
|
t.Fatal("expected old_string not found")
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{
|
|
"old_string not found",
|
|
"CRLF line endings",
|
|
"already tolerate LF-only old_string",
|
|
"stale, incomplete, or non-unique context",
|
|
} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Fatalf("error %q does not contain %q", msg, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEditFileFuzzyAmbiguousDoesNotWrite(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "dup.txt")
|
|
seed := "target \ntarget \n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "target\n",
|
|
"new_string": "updated\n",
|
|
}))
|
|
if err == nil || !strings.Contains(err.Error(), "not unique") {
|
|
t.Fatalf("expected not-unique error, got %v", err)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != seed {
|
|
t.Fatalf("ambiguous fuzzy edit changed file: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestEditFileFuzzyLeadingIndentDriftDoesNotWrite(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "indent.go")
|
|
seed := "func f() {\n if ok {\n return nil\n }\n}\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := (editFile{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"old_string": "if ok {\n return nil\n}",
|
|
"new_string": "if ok {\n return errors.New(\"nope\")\n}",
|
|
}))
|
|
if err == nil || !strings.Contains(err.Error(), "old_string not found") {
|
|
t.Fatalf("expected not-found error for leading indentation drift, got %v", err)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != seed {
|
|
t.Fatalf("leading indentation drift changed file: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestMultiEditFuzzyReplaceAll(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "list.txt")
|
|
seed := "item \nitem\t\n"
|
|
if err := os.WriteFile(path, []byte(seed), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err := (multiEdit{}).Execute(context.Background(), argsJSON(t, map[string]any{
|
|
"path": path,
|
|
"edits": []map[string]any{
|
|
{"old_string": "item\n", "new_string": "thing\n", "replace_all": true},
|
|
},
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("multi_edit: %v", err)
|
|
}
|
|
if !strings.Contains(out, "2 total replacements") || !strings.Contains(out, "fuzzy match") {
|
|
t.Fatalf("unexpected output: %q", out)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "thing\nthing\n"
|
|
if string(got) != want {
|
|
t.Fatalf("content = %q, want %q", got, want)
|
|
}
|
|
}
|