R-1199: a box whose household never saved its notification choice pushes the default list its page shows at start, so the hub mails by it; unreleased
gates / gates (push) Successful in 1m37s

This commit is contained in:
2026-10-21 23:40:21 +02:00
parent 5776d54901
commit ba22f9b2d9
5 changed files with 88 additions and 1 deletions
+3 -1
View File
@@ -1891,7 +1891,9 @@ func main() {
go func() {
prefs := sett.GetNotificationPrefs()
// R-922: a household's clear is pushed too, so a clear the hub missed reaches it at the next start.
if prefs.SyncOnStartup() {
// R-1199: a box whose household never saved its choice pushes the default list its page shows (with no
// address — the hub keeps the stored one, its no-clobber guard).
if prefsSyncAtStartup(prefs, sett.NotificationPrefsSaved(), logger) {
if err := notifier.SyncPreferences(prefs.Email, prefs.EnabledEvents, prefs.CooldownHours, prefs.EmailCleared); err != nil {
logger.Printf("[WARN] Failed to sync notification preferences on startup: %v", err)
}
@@ -0,0 +1,30 @@
package main
import (
"log"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-1199 (rehearsal 2026-10-20; mechanism read the night of 2026-10-21, `audits/night-2026-10-21/P5-checks/`): a fresh
// box shows the household its DEFAULT notification list ticked (disk warning, drive disconnected, second-drive failure
// …), but pushed nothing to the hub until the household saved the page — and the hub mails the household only by its
// OWN stored list (`processCustomer`), which for Tester4 was a list saved two days before that box existed. So the
// household got one alarm of nine. The box's page is the household's choice (`08` „the household decides" — the three
// groups); the hub follows it. Decided by CC unattended — operator may reverse.
//
// prefsSyncAtStartup: push at start when the household saved an address or a clear (R-922, unchanged), or when it never
// saved a choice at all (the default list, pushed with no address — the hub's no-clobber guard keeps the stored one).
// Pinned by TestR1199_*.
func prefsSyncAtStartup(prefs *settings.NotificationPrefs, saved bool, logger *log.Logger) bool {
if prefs.SyncOnStartup() {
return true
}
if !saved && prefs != nil {
if logger != nil {
logger.Printf("[INFO] notification prefs: the household never saved its choice — pushing the default list the page shows, so the hub mails by it (R-1199)")
}
return true
}
return false
}
@@ -0,0 +1,38 @@
package main
import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-1199: a box whose household never saved its notification choice pushes the default list at start; a saved choice
// without an address (and no clear) is still not pushed (R-922's rule unchanged); a saved address is pushed.
// RED-PROOF: with the `!saved` branch removed the first case fails — the hub keeps mailing by a list the page never showed.
func TestR1199_ANeverSavedChoiceIsPushedAtStart(t *testing.T) {
def := &settings.NotificationPrefs{EnabledEvents: settings.DefaultEnabledEvents, CooldownHours: 6}
if !prefsSyncAtStartup(def, false, nil) {
t.Fatal("a box with no saved choice must push the default list its page shows")
}
if prefsSyncAtStartup(&settings.NotificationPrefs{EnabledEvents: []string{"backup_failed"}}, true, nil) {
t.Fatal("a saved choice with no address and no clear stays local (R-922 unchanged)")
}
if !prefsSyncAtStartup(&settings.NotificationPrefs{Email: "x@example.invalid", EnabledEvents: []string{"backup_failed"}}, true, nil) {
t.Fatal("a saved address is pushed, as before")
}
}
// The default list the page shows carries the three events the rehearsal household did not get (the consequence).
func TestR1199_TheDefaultListHoldsTheAlarmsTheHouseholdMissed(t *testing.T) {
want := map[string]bool{"disk_warning": false, "storage_disconnected": false, "crossdrive_failed": false}
for _, e := range settings.DefaultEnabledEvents {
if _, ok := want[e]; ok {
want[e] = true
}
}
for e, ok := range want {
if !ok {
t.Fatalf("%s is not in the default list", e)
}
}
}
+9
View File
@@ -676,6 +676,15 @@ func (p *NotificationPrefs) SyncOnStartup() bool {
return p != nil && (p.Email != "" || p.EmailCleared)
}
// NotificationPrefsSaved reports whether the household ever saved its notification choice on this box. A box that never
// did shows DefaultEnabledEvents ticked on its page (GetNotificationPrefs) — R-1199: the hub must then mail by that
// same list, so the start-up sync pushes it (main.go, prefsSyncAtStartup).
func (s *Settings) NotificationPrefsSaved() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.Notifications != nil
}
// DefaultEnabledEvents are the events enabled by default for new customers.
var DefaultEnabledEvents = []string{
"backup_failed",