package persistence import ( "bytes" "os" "path/filepath" "sync" "testing" ) func TestWriteFileReplacesAndPreservesPermissions(t *testing.T) { path := filepath.Join(t.TempDir(), "save.yaml") if err := WriteFile(path, []byte("old"), 0600); err != nil { t.Fatal(err) } if err := WriteFile(path, []byte("new"), 0644); err != nil { t.Fatal(err) } got, err := os.ReadFile(path) if err != nil || string(got) != "new" { t.Fatalf("read: %q, %v", got, err) } info, err := os.Stat(path) if err != nil || info.Mode().Perm() != 0600 { t.Fatalf("permissions: %v, %v", info, err) } entries, err := os.ReadDir(filepath.Dir(path)) if err != nil || len(entries) != 1 { t.Fatalf("temporary files left behind: %v, %v", entries, err) } } func TestFailedRenameCleansTempFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "destination") if err := os.Mkdir(path, 0755); err != nil { t.Fatal(err) } if err := WriteFile(path, []byte("new"), 0600); err == nil { t.Fatal("expected rename over directory to fail") } entries, err := os.ReadDir(dir) if err != nil || len(entries) != 1 || !entries[0].IsDir() { t.Fatalf("destination damaged or temporary file leaked: %v, %v", entries, err) } } func TestReadersSeeOnlyCompleteSaves(t *testing.T) { path := filepath.Join(t.TempDir(), "save.yaml") a, b := bytes.Repeat([]byte("a"), 8192), bytes.Repeat([]byte("b"), 16384) if err := WriteFile(path, a, 0600); err != nil { t.Fatal(err) } var wg sync.WaitGroup for _, data := range [][]byte{a, b} { wg.Add(1) go func(data []byte) { defer wg.Done() for i := 0; i < 30; i++ { if err := WriteFile(path, data, 0600); err != nil { t.Error(err) return } } }(data) } for i := 0; i < 300; i++ { got, err := os.ReadFile(path) if err != nil || (!bytes.Equal(got, a) && !bytes.Equal(got, b)) { t.Errorf("reader saw partial or missing save: length=%d, err=%v", len(got), err) break } } wg.Wait() }