package fileutil import ( "os" "path/filepath" "strings" "testing" ) func TestWriteFileAtomicCreatesParentDirectory(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", "artifact.txt") if err := WriteFileAtomic(path, []byte("artifact")); err != nil { t.Fatalf("WriteFileAtomic() error = %v", err) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile() error = %v", err) } if string(data) != "artifact" { t.Fatalf("data = %q, want artifact", data) } } func TestWriteFileAtomicOverwritesTarget(t *testing.T) { path := filepath.Join(t.TempDir(), "artifact.txt") if err := WriteFileAtomic(path, []byte("old")); err != nil { t.Fatalf("WriteFileAtomic() initial error = %v", err) } if err := WriteFileAtomic(path, []byte("new")); err != nil { t.Fatalf("WriteFileAtomic() overwrite error = %v", err) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile() error = %v", err) } if string(data) != "new" { t.Fatalf("data = %q, want new", data) } } func TestWriteFileAtomicCleansTemporaryFileAfterRenameError(t *testing.T) { dir := t.TempDir() target := filepath.Join(dir, "target") if err := os.Mkdir(target, 0o755); err != nil { t.Fatalf("Mkdir() error = %v", err) } err := WriteFileAtomic(target, []byte("data")) if err == nil { t.Fatal("WriteFileAtomic() error = nil, want rename error") } if !strings.Contains(err.Error(), "save") { t.Fatalf("error = %q, want save context", err.Error()) } matches, err := filepath.Glob(filepath.Join(dir, ".target.*.tmp")) if err != nil { t.Fatalf("Glob() error = %v", err) } if len(matches) != 0 { t.Fatalf("temporary files = %v, want none", matches) } } func TestWriteJSONAtomic(t *testing.T) { path := filepath.Join(t.TempDir(), "artifact.json") if err := WriteJSONAtomic(path, map[string]string{"status": "ok"}); err != nil { t.Fatalf("WriteJSONAtomic() error = %v", err) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile() error = %v", err) } if string(data) != "{\n \"status\": \"ok\"\n}" { t.Fatalf("json = %q, want indented object", data) } } func TestCopyFileAtomic(t *testing.T) { dir := t.TempDir() source := filepath.Join(dir, "source.txt") target := filepath.Join(dir, "nested", "target.txt") if err := os.WriteFile(source, []byte("copied"), 0o600); err != nil { t.Fatalf("WriteFile() error = %v", err) } if err := CopyFileAtomic(source, target); err != nil { t.Fatalf("CopyFileAtomic() error = %v", err) } data, err := os.ReadFile(target) if err != nil { t.Fatalf("ReadFile() error = %v", err) } if string(data) != "copied" { t.Fatalf("data = %q, want copied", data) } }