3603d1fc7f
tier2_capture.go: classified apps get TierSecondary per-bind legs (paperless copy shrinks — export
drops); legacy apps keep the byte-identical resolver set. v2 relpath-mirroring layout
(backups/secondary/<stack>/{marker LAST, recovery-unit/, hdd/<rel>/, userdata/<rel>/}); N>1 native
(errTier2MultiDir/tier2AppDataName deleted). Migration=delete-and-rebuild + reconcile; all RemoveAll
via tier2SafeRemove (refuses outside backups/secondary/). SSD=state-only tier. selectTier2Target
never picks network storage (pinned+auto, F-6C-1). Restore reads v2 behind a marker gate.
Part 0: offbox_enlarge_blocked is a persisted one-time Load seed (opt-out sticks), not a getter
append. Part 0.5: offsite restore scratch prefers a local (non-network) path.
Full v2 test suite + all 10 §10 red-proofs verified. Destructive writes bounded to backups/secondary/.
107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
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")
|
|
}
|
|
}
|