R-1199 reopened: a box with no list of its own reads the household's from the hub and adopts it, pushes its default once only when no household saved one; every push names its source; an empty saved list stays empty; R-1216 (D28 = A): the install page's swap sentence only when host swap is really short (shared case table); unreleased
gates / gates (push) Successful in 1m55s
gates / gates (push) Successful in 1m55s
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1199 reopened (2026-10-22): a household that unticks every mail must see an empty list after a restart — the hub
|
||||
// mails by that empty list. Before: omitempty dropped the [] and the getter turned the missing list into the defaults.
|
||||
// Both shapes: a fresh save, and the file Tester 1 and demo-hp actually hold (`{"cooldown_hours": 6}` only).
|
||||
// RED-PROOF: with the getter's nil → DefaultEnabledEvents restored, both assertions fail — seen failing 2026-10-22.
|
||||
func TestR1199Reopened_AnEmptySavedListStaysEmpty(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "s.json")
|
||||
s, _ := Load(p, log.New(io.Discard, "", 0))
|
||||
if err := s.SetNotificationPrefs(&NotificationPrefs{EnabledEvents: []string{}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s2, _ := Load(p, log.New(io.Discard, "", 0))
|
||||
if got := s2.GetNotificationPrefs().EnabledEvents; len(got) != 0 {
|
||||
t.Fatalf("a saved empty list came back as %v", got)
|
||||
}
|
||||
q := filepath.Join(t.TempDir(), "old.json")
|
||||
os.WriteFile(q, []byte(`{"notifications":{"cooldown_hours":6}}`), 0o600)
|
||||
s3, _ := Load(q, log.New(io.Discard, "", 0))
|
||||
if got := s3.GetNotificationPrefs().EnabledEvents; len(got) != 0 || !s3.NotificationPrefsSaved() {
|
||||
t.Fatalf("the live boxes' file shape reads as %v (saved=%t), want an empty saved list", got, s3.NotificationPrefsSaved())
|
||||
}
|
||||
// A box with nothing saved still shows the defaults.
|
||||
s4, _ := Load(filepath.Join(t.TempDir(), "n.json"), log.New(io.Discard, "", 0))
|
||||
if got := s4.GetNotificationPrefs().EnabledEvents; len(got) != len(DefaultEnabledEvents) {
|
||||
t.Fatalf("an unsaved box must show the defaults, got %d", len(got))
|
||||
}
|
||||
}
|
||||
@@ -649,8 +649,11 @@ func (p StoragePath) IsInternal() bool { return p.Kind == StorageKindInternal }
|
||||
|
||||
// NotificationPrefs holds customer notification preferences.
|
||||
type NotificationPrefs struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
EnabledEvents []string `json:"enabled_events,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
// EnabledEvents: no omitempty since R-1199 reopened (2026-10-22) — a household that unticks every mail saves [],
|
||||
// and omitempty dropped it, so after the next start the page showed every default mail ticked while the hub mailed
|
||||
// by the empty list (measured on Tester 1 and demo-hp: their file held only `cooldown_hours`).
|
||||
EnabledEvents []string `json:"enabled_events"`
|
||||
CooldownHours int `json:"cooldown_hours,omitempty"` // default: 6
|
||||
// EmailCleared (R-922, operator ruling 2026-10-09 option A) marks a DELIBERATE clear: the household
|
||||
// saved an empty address where one was stored. Every hub push carries it as `email_cleared: true`
|
||||
@@ -770,6 +773,11 @@ func Load(path string, logger *log.Logger) (*Settings, error) {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
logger.Printf("[INFO] [settings] No settings.json found, using defaults")
|
||||
// A fresh box's defaults already carry every type the one-time add-only seeds add, so they are DONE for it.
|
||||
// Unset, they ran at the second start and re-added a type the household had just switched off — the
|
||||
// „a later opt-out sticks" promise of each seed was false on every new box (found 2026-10-22, R-1199
|
||||
// reopened). Persisted with the first save. Pinned by TestR1199Reopened_AnEmptySavedListStaysEmpty.
|
||||
s.OffboxEnlargeNoticeSeeded, s.AppUpdateEventsSeeded, s.UnhealthyStopEventSeeded = true, true, true
|
||||
return s, nil
|
||||
}
|
||||
return nil, fmt.Errorf("reading settings file: %w", err)
|
||||
@@ -1354,7 +1362,10 @@ func (s *Settings) GetNotificationPrefs() *NotificationPrefs {
|
||||
prefs.CooldownHours = 6
|
||||
}
|
||||
if prefs.EnabledEvents == nil {
|
||||
prefs.EnabledEvents = DefaultEnabledEvents
|
||||
// A SAVED list with no events is a household that unticked every mail (the only writer is the settings page;
|
||||
// before R-1199 reopened omitempty dropped its []). It reads as empty — never as the defaults, which the hub
|
||||
// does not mail by. Pinned by TestR1199Reopened_AnEmptySavedListStaysEmpty.
|
||||
prefs.EnabledEvents = []string{}
|
||||
}
|
||||
// Return a copy of the slice verbatim. The offbox_enlarge_blocked seed is a ONE-TIME persisted
|
||||
// migration (seedOffboxEnlargeNotice at Load), NOT a getter append — so a customer's later opt-out
|
||||
|
||||
Reference in New Issue
Block a user