package settings import ( "os" "path/filepath" "strings" "testing" ) func contains(list []string, want string) bool { for _, e := range list { if e == want { return true } } return false } func count(list []string, want string) int { n := 0 for _, e := range list { if e == want { n++ } } return n } func writeSettings(t *testing.T, json string) (string, *Settings) { t.Helper() p := filepath.Join(t.TempDir(), "settings.json") if err := os.WriteFile(p, []byte(json), 0644); err != nil { t.Fatal(err) } s, err := Load(p, discardLog()) if err != nil { t.Fatal(err) } return p, s } // The new warning type is on by default for new customers. func TestDefaultEnabledEvents_ContainsEnlargeBlocked(t *testing.T) { if !contains(DefaultEnabledEvents, "offbox_enlarge_blocked") { t.Error("DefaultEnabledEvents must contain offbox_enlarge_blocked (new customers get it)") } } // Part 0: the seed runs at LOAD (not the getter) — an existing customer's stored prefs gain the type, // their other choices preserved, and the change is persisted (marker + type on disk). func TestSeedOffboxEnlargeNotice_AtLoad(t *testing.T) { p, s := writeSettings(t, `{"notifications":{"enabled_events":["backup_failed","disk_warning"],"cooldown_hours":6}}`) ev := s.GetNotificationPrefs().EnabledEvents if !contains(ev, "offbox_enlarge_blocked") { t.Error("seed must append the type at load") } if !contains(ev, "backup_failed") || !contains(ev, "disk_warning") { t.Error("existing choices must be preserved") } raw, _ := os.ReadFile(p) if !strings.Contains(string(raw), "offbox_enlarge_notice_seeded") { t.Error("seed marker must persist to disk") } if !strings.Contains(string(raw), "offbox_enlarge_blocked") { t.Error("the seeded type must persist to disk (so the getter returns it verbatim)") } } // Part 0: the seed is idempotent across loads (a customer already having the type keeps exactly one). func TestSeedOffboxEnlargeNotice_Idempotent(t *testing.T) { p, _ := writeSettings(t, `{"notifications":{"enabled_events":["offbox_enlarge_blocked","backup_failed"],"cooldown_hours":6}}`) s2, err := Load(p, discardLog()) if err != nil { t.Fatal(err) } if c := count(s2.GetNotificationPrefs().EnabledEvents, "offbox_enlarge_blocked"); c != 1 { t.Errorf("idempotent seed → exactly one entry, got %d", c) } } // Part 0 (the whole point): a deliberate opt-out STICKS — after the seed, unchecking + saving and // reloading must NOT re-enable the type (the 3a-fix getter append re-enabled it forever; this fixes it). func TestSeedOffboxEnlargeNotice_OptOutSticks(t *testing.T) { p, s := writeSettings(t, `{"notifications":{"enabled_events":["backup_failed","offbox_enlarge_blocked"],"cooldown_hours":6}}`) // seed already ran at Load (seeded=true persisted). Customer unchecks the warning and saves. if err := s.SetNotificationPrefs(&NotificationPrefs{EnabledEvents: []string{"backup_failed"}, CooldownHours: 6}); err != nil { t.Fatal(err) } s2, err := Load(p, discardLog()) if err != nil { t.Fatal(err) } if contains(s2.GetNotificationPrefs().EnabledEvents, "offbox_enlarge_blocked") { t.Error("opt-out must STICK — the seed must not re-enable the type after a deliberate uncheck") } } // Fresh customer (no file → nil prefs) still gets the type via DefaultEnabledEvents. func TestGetNotificationPrefs_FreshCustomer(t *testing.T) { s, err := Load(filepath.Join(t.TempDir(), "settings.json"), discardLog()) if err != nil { t.Fatal(err) } if !contains(s.GetNotificationPrefs().EnabledEvents, "offbox_enlarge_blocked") { t.Error("fresh customer must get offbox_enlarge_blocked via DefaultEnabledEvents") } }