package fileops import ( "errors" "os" "path/filepath" "strings" "testing" ) func TestRemoveAllUnderRootRemovesNestedTreeIdempotently(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "runs", "run-1") mustWriteFile(t, filepath.Join(target, "nested", "result.txt"), []byte("result"), 0o640) if err := RemoveAllUnderRoot(root, target); err != nil { t.Fatalf("RemoveAllUnderRoot() error = %v", err) } if _, err := os.Lstat(target); !os.IsNotExist(err) { t.Fatalf("Lstat(%q) error = %v, want not exist", target, err) } if err := RemoveAllUnderRoot(root, target); err != nil { t.Fatalf("second RemoveAllUnderRoot() error = %v", err) } } func TestRemoveAllUnderRootRejectsUnsafeTargets(t *testing.T) { root := t.TempDir() outside := t.TempDir() target := filepath.Join(root, "runs", "run-1") mustWriteFile(t, filepath.Join(target, "result.txt"), []byte("result"), 0o640) sentinel := filepath.Join(outside, "sentinel.txt") mustWriteFile(t, sentinel, []byte("outside"), 0o640) if err := RemoveAllUnderRoot(root, root); err == nil { t.Fatal("RemoveAllUnderRoot(root, root) error = nil, want root rejection") } if err := RemoveAllUnderRoot(root, filepath.Join(outside, "target")); err == nil { t.Fatal("RemoveAllUnderRoot(outside) error = nil, want outside-root rejection") } if err := os.Symlink(outside, filepath.Join(root, "runs")); err == nil { t.Fatal("Symlink() error = nil, want collision because runs already exists") } if err := os.RemoveAll(filepath.Join(root, "runs")); err != nil { t.Fatalf("RemoveAll(runs) error = %v", err) } if err := os.Symlink(outside, filepath.Join(root, "runs")); err != nil { t.Fatalf("Symlink() error = %v", err) } if err := RemoveAllUnderRoot(root, target); err == nil { t.Fatal("RemoveAllUnderRoot(symlinked ancestor) error = nil, want rejection") } data, err := os.ReadFile(sentinel) if err != nil { t.Fatalf("ReadFile(sentinel) error = %v", err) } if string(data) != "outside" { t.Fatalf("sentinel content = %q, want outside", data) } } func TestRemoveAllUnderRootRejectsSymlinkInTree(t *testing.T) { root := t.TempDir() outside := t.TempDir() target := filepath.Join(root, "runs", "run-1") if err := os.MkdirAll(target, 0o750); err != nil { t.Fatalf("MkdirAll(target) error = %v", err) } sentinel := filepath.Join(outside, "sentinel.txt") mustWriteFile(t, sentinel, []byte("outside"), 0o640) if err := os.Symlink(outside, filepath.Join(target, "link")); err != nil { t.Fatalf("Symlink() error = %v", err) } if err := RemoveAllUnderRoot(root, target); err == nil { t.Fatal("RemoveAllUnderRoot() error = nil, want symlink rejection") } if _, err := os.Stat(sentinel); err != nil { t.Fatalf("outside sentinel was changed: %v", err) } } func TestRemoveFileUnderRootRemovesOnlyRegularFile(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "inputs", "spell_catalog.json") if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { t.Fatalf("MkdirAll() error = %v", err) } if err := os.WriteFile(target, []byte("{}\n"), 0o644); err != nil { t.Fatalf("WriteFile() error = %v", err) } if err := RemoveFileUnderRoot(root, target); err != nil { t.Fatalf("RemoveFileUnderRoot() error = %v", err) } if _, err := os.Lstat(target); !errors.Is(err, os.ErrNotExist) { t.Fatalf("Lstat() error = %v, want not exist", err) } if err := RemoveFileUnderRoot(root, target); err != nil { t.Fatalf("RemoveFileUnderRoot(missing) error = %v", err) } for _, tt := range []struct { name string setup func(string) error }{ {name: "directory", setup: func(path string) error { return os.Mkdir(path, 0o755) }}, {name: "symlink", setup: func(path string) error { return os.Symlink(filepath.Join(root, "outside"), path) }}, } { t.Run(tt.name, func(t *testing.T) { if err := tt.setup(target); err != nil { t.Fatalf("setup target: %v", err) } err := RemoveFileUnderRoot(root, target) if err == nil || !strings.Contains(err.Error(), "non-regular") { t.Fatalf("RemoveFileUnderRoot() error = %v, want non-regular rejection", err) } if _, err := os.Lstat(target); err != nil { t.Fatalf("ambiguous target was removed: %v", err) } if err := os.Remove(target); err != nil { t.Fatalf("cleanup target: %v", err) } }) } }