package settings import ( "io" "log" "os" "path/filepath" "strings" "testing" ) func discardLog() *log.Logger { return log.New(io.Discard, "", 0) } // T-S1a: a corrupt primary recovers from the last-known-good .bak (no error, no crash-loop). // Companion: pre-fix Load (return error on parse) → err != nil → this fails. func TestLoad_RecoversFromBak(t *testing.T) { p := filepath.Join(t.TempDir(), "settings.json") if err := os.WriteFile(p+".bak", []byte(`{"password_hash":"bak-hash"}`), 0644); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(`{"password_hash": BROKEN not json`), 0644); err != nil { t.Fatal(err) } s, err := Load(p, discardLog()) if err != nil { t.Fatalf("Load must not error (no crash-loop): %v", err) } if s.GetPasswordHash() != "bak-hash" { t.Fatalf("want recovered hash bak-hash, got %q", s.GetPasswordHash()) } if s.LoadWarning == "" { t.Error("expected LoadWarning to be set after recovery") } if b, _ := os.ReadFile(p); !strings.Contains(string(b), "bak-hash") { t.Error("primary should be re-promoted from .bak") } } // T-S1b: unrecoverable (corrupt primary, no .bak) → safe defaults, NO error, corrupt file preserved. func TestLoad_UnrecoverableNoBak(t *testing.T) { p := filepath.Join(t.TempDir(), "settings.json") if err := os.WriteFile(p, []byte(`{ BROKEN not json`), 0644); err != nil { t.Fatal(err) } s, err := Load(p, discardLog()) if err != nil { t.Fatalf("Load must not error (no crash-loop): %v", err) } if s == nil { t.Fatal("expected non-nil safe-default settings") } if s.LoadWarning == "" { t.Error("expected LoadWarning to be set") } if m, _ := filepath.Glob(p + ".corrupt-*"); len(m) == 0 { t.Error("corrupt file must be preserved as *.corrupt-*") } if _, e := os.Stat(p); e == nil { t.Error("original corrupt primary should have been renamed away") } } // T-S1c: save() writes a .bak equal to the primary (last-known-good). func TestSave_WritesBak(t *testing.T) { p := filepath.Join(t.TempDir(), "settings.json") s, err := Load(p, discardLog()) // no file → defaults if err != nil { t.Fatal(err) } if err := s.SetPasswordHash("h1"); err != nil { // SetPasswordHash persists via save() t.Fatal(err) } prim, _ := os.ReadFile(p) bak, e := os.ReadFile(p + ".bak") if e != nil { t.Fatalf(".bak missing after save: %v", e) } if string(prim) != string(bak) { t.Error(".bak must equal the primary after save") } } // T-S1d: happy path — valid primary loads normally, no warning (regression). func TestLoad_HappyPath(t *testing.T) { p := filepath.Join(t.TempDir(), "settings.json") if err := os.WriteFile(p, []byte(`{"password_hash":"good"}`), 0644); err != nil { t.Fatal(err) } s, err := Load(p, discardLog()) if err != nil || s.GetPasswordHash() != "good" || s.LoadWarning != "" { t.Fatalf("happy load failed: err=%v hash=%q warn=%q", err, s.GetPasswordHash(), s.LoadWarning) } }