Files
2026-06-03 15:49:54 -05:00

228 lines
4.9 KiB
Go

package fileops
import (
"os"
"testing"
)
func writeTempFile(t *testing.T, content string) string {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "ts-test-*.txt")
if err != nil {
t.Fatal(err)
}
if _, err := f.WriteString(content); err != nil {
t.Fatal(err)
}
f.Close()
return f.Name()
}
func readTempFile(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(b)
}
func equalIntSlice(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestReplaceInFile(t *testing.T) {
tests := []struct {
name string
content string
from, to string
dryRun bool
wantLines []int
wantContent string
}{
{
name: "single occurrence",
content: "foo bar\nbaz\n",
from: "foo",
to: "qux",
wantLines: []int{1},
wantContent: "qux bar\nbaz\n",
},
{
name: "multiple occurrences on same line",
content: "foo foo foo\n",
from: "foo",
to: "x",
wantLines: []int{1},
wantContent: "x x x\n",
},
{
name: "occurrences across multiple lines",
content: "keep\nfoo here\nalso foo\n",
from: "foo",
to: "bar",
wantLines: []int{2, 3},
wantContent: "keep\nbar here\nalso bar\n",
},
{
name: "no match leaves file unchanged",
content: "hello world\n",
from: "xyz",
to: "abc",
wantLines: nil,
wantContent: "hello world\n",
},
{
name: "dry run reports lines but does not write",
content: "foo bar\n",
from: "foo",
to: "baz",
dryRun: true,
wantLines: []int{1},
wantContent: "foo bar\n",
},
{
name: "empty file",
content: "",
from: "foo",
to: "bar",
wantLines: nil,
wantContent: "",
},
{
name: "replacement on last line without trailing newline",
content: "line1\nfoo",
from: "foo",
to: "bar",
wantLines: []int{2},
wantContent: "line1\nbar\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path := writeTempFile(t, tt.content)
res, err := ReplaceInFile(path, tt.from, tt.to, tt.dryRun)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !equalIntSlice(res.ChangedLines, tt.wantLines) {
t.Errorf("ChangedLines = %v, want %v", res.ChangedLines, tt.wantLines)
}
got := readTempFile(t, path)
if got != tt.wantContent {
t.Errorf("file content = %q, want %q", got, tt.wantContent)
}
})
}
}
func TestReplaceInFile_MissingFile(t *testing.T) {
_, err := ReplaceInFile("/no/such/file.txt", "x", "y", false)
if err == nil {
t.Error("expected error for missing file, got nil")
}
}
func TestReplaceInFileRegex(t *testing.T) {
tests := []struct {
name string
content string
pattern string
repl string
dryRun bool
wantLines []int
wantContent string
wantErr bool
}{
{
name: "basic regex replacement",
content: "foo123\nbar\nfoo456\n",
pattern: `foo\d+`,
repl: "replaced",
wantLines: []int{1, 3},
wantContent: "replaced\nbar\nreplaced\n",
},
{
name: "capture group substitution",
content: "hello world\n",
pattern: `(hello) (world)`,
repl: "$2 $1",
wantLines: []int{1},
wantContent: "world hello\n",
},
{
name: "no match",
content: "no match here\n",
pattern: `\d+`,
repl: "NUM",
wantLines: nil,
wantContent: "no match here\n",
},
{
name: "invalid regex returns error",
content: "foo\n",
pattern: `[invalid`,
repl: "x",
wantErr: true,
},
{
name: "dry run with regex",
content: "abc123\n",
pattern: `\d+`,
repl: "NUM",
dryRun: true,
wantLines: []int{1},
wantContent: "abc123\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path := writeTempFile(t, tt.content)
res, err := ReplaceInFileRegex(path, tt.pattern, tt.repl, tt.dryRun)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !equalIntSlice(res.ChangedLines, tt.wantLines) {
t.Errorf("ChangedLines = %v, want %v", res.ChangedLines, tt.wantLines)
}
got := readTempFile(t, path)
if got != tt.wantContent {
t.Errorf("file content = %q, want %q", got, tt.wantContent)
}
})
}
}
func TestFormatLineNums(t *testing.T) {
tests := []struct {
nums []int
want string
}{
{[]int{1}, "1"},
{[]int{1, 2, 3}, "1, 2, 3"},
{[]int{10, 47}, "10, 47"},
}
for _, tt := range tests {
got := formatLineNums(tt.nums)
if got != tt.want {
t.Errorf("formatLineNums(%v) = %q, want %q", tt.nums, got, tt.want)
}
}
}