diff --git a/docs/release-control/v6/internal/subsystems/notifications.md b/docs/release-control/v6/internal/subsystems/notifications.md index 7392a0964..9164cb44d 100644 --- a/docs/release-control/v6/internal/subsystems/notifications.md +++ b/docs/release-control/v6/internal/subsystems/notifications.md @@ -825,3 +825,16 @@ missed decoded query representations and a separate ntfy transport caller: provider-only helper examples were not sufficient sink coverage. This contract does not assert arbitrary response-body/third-party error secrecy, installed recipient delivery, candidate qualification or historical customer exposure. + +### Saved notification choices at monitor construction + +`internal/monitoring/monitor_notification_startup_test.go` exercises the actual +monitor constructor twice against the same saved configuration without applying +notification-manager setters in the fixture. It checks initial routing, both +resolve choices, enabled/activation gating and encrypted webhook configuration +restoration. The cases include webhook, Apprise, email and all destinations. + +This boundary uses the configuration persistence API to save choices, not the +HTTP or browser save path. It does not start a new operating-system process, +drive an alert lifecycle or establish recipient delivery. Those installed +acceptance obligations remain separate from constructor restoration coverage. diff --git a/internal/monitoring/monitor_notification_startup_test.go b/internal/monitoring/monitor_notification_startup_test.go new file mode 100644 index 000000000..4580d9195 --- /dev/null +++ b/internal/monitoring/monitor_notification_startup_test.go @@ -0,0 +1,64 @@ +package monitoring + +import ( + "reflect" + "testing" + + "github.com/rcourtman/pulse-go-rewrite/internal/alerts" + "github.com/rcourtman/pulse-go-rewrite/internal/config" + "github.com/rcourtman/pulse-go-rewrite/internal/notifications" +) + +// Cover the real monitor constructor, not a fixture which reapplies manager +// setters after restart. Saving here uses persistence directly: this is not +// browser/API save acceptance, a process restart, or destination receipt proof. +func TestNewRestoresSavedNotificationChoices(t *testing.T) { + for _, tc := range []struct { + name, target string + resolve, enabled bool + activation alerts.ActivationState + }{ + {"webhook_without_recovery", "webhook", false, true, alerts.ActivationActive}, + {"apprise_with_recovery", "apprise", true, true, alerts.ActivationActive}, + {"email_disabled", "email", true, false, alerts.ActivationActive}, + {"all_pending", "all", false, true, alerts.ActivationPending}, + } { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + t.Setenv("PULSE_DATA_DIR", dir) + persistence := config.NewConfigPersistence(dir) + saved := alerts.AlertConfig{Enabled: tc.enabled, ActivationState: tc.activation, + Schedule: alerts.ScheduleConfig{InitialNotify: tc.target, NotifyOnResolve: tc.resolve}} + if err := persistence.SaveAlertConfig(saved); err != nil { + t.Fatal(err) + } + webhooks := []notifications.WebhookConfig{{ID: "saved-ops", Name: "saved-ops", URL: "https://example.invalid/alerts", Enabled: true, Service: "generic", MinimumSeverity: "warning", TagFilter: []string{"ops"}, TagMode: "any"}} + if err := persistence.SaveWebhooks(webhooks); err != nil { + t.Fatal(err) + } + for startup := 0; startup < 2; startup++ { + func() { + m, err := New(&config.Config{DataPath: dir}) + if err != nil { + t.Fatal(err) + } + defer m.Stop() + n := m.GetNotificationManager() + if got := n.GetInitialNotifyTarget(); got != tc.target { + t.Errorf("startup %d target=%q, want %q", startup, got, tc.target) + } + if got := n.GetNotifyOnResolve(); got != tc.resolve { + t.Errorf("startup %d resolve=%t, want %t", startup, got, tc.resolve) + } + wantEnabled := tc.enabled && tc.activation == alerts.ActivationActive + if got := n.IsEnabled(); got != wantEnabled { + t.Errorf("startup %d enabled=%t, want %t", startup, got, wantEnabled) + } + if got := n.GetWebhooks(); !reflect.DeepEqual(got, webhooks) { + t.Errorf("startup %d did not restore saved webhook configuration", startup) + } + }() + } + }) + } +}