TestSaveIsAtomicAndLeavesNoTemp: writeYAML writes a sibling temp then renames it over config.yaml so a torn write can't brick the next launch. Pin that the rename leaves no leftover .config-*.yaml temp and the result still decodes. A regression here would mean the atomic-write path leaks temps or wr
(t *testing.T)
| 374 | // rename leaves no leftover .config-*.yaml temp and the result still decodes. |
| 375 | // A regression here would mean the atomic-write path leaks temps or wrote junk. |
| 376 | func TestSaveIsAtomicAndLeavesNoTemp(t *testing.T) { |
| 377 | dir := t.TempDir() |
| 378 | cfg, _, err := Bootstrap(dir) |
| 379 | if err != nil { |
| 380 | t.Fatal(err) |
| 381 | } |
| 382 | cdir := filepath.Join(dir, DirName) |
| 383 | // Save a few times, each must rename cleanly with no temp accumulation. |
| 384 | for i := range 3 { |
| 385 | cfg.Models["hamrpass"].Key = fmt.Sprintf("hp-key-%d-0000000000", i) |
| 386 | if err := cfg.Save(); err != nil { |
| 387 | t.Fatal(err) |
| 388 | } |
| 389 | } |
| 390 | entries, err := os.ReadDir(cdir) |
| 391 | if err != nil { |
| 392 | t.Fatal(err) |
| 393 | } |
| 394 | for _, e := range entries { |
| 395 | if strings.HasPrefix(e.Name(), ".config-") { |
| 396 | t.Fatalf("Save left a temp file behind: %s", e.Name()) |
| 397 | } |
| 398 | } |
| 399 | // The committed file must still be a valid, re-decodable config. |
| 400 | reloaded, _, err := Bootstrap(dir) |
| 401 | if err != nil { |
| 402 | t.Fatalf("config.yaml not decodable after atomic Save: %v", err) |
| 403 | } |
| 404 | if reloaded.Models["hamrpass"].Key != "hp-key-2-0000000000" { |
| 405 | t.Fatalf("last Save not durable: key = %q", reloaded.Models["hamrpass"].Key) |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | // TestSetActivePersists: SetActive flips Active and writes config.yaml. |
| 410 | func TestSetActivePersists(t *testing.T) { |