test(notifications): verify saved choices at monitor startup

Exercise the production monitor constructor twice from persisted routing, resolve, activation and webhook filtering choices without reapplying notification setters. Document that constructor restoration does not establish browser saving, process restart or destination delivery.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-08 10:28:43 +01:00
parent 546e0a864a
commit 2a43c51815
2 changed files with 77 additions and 0 deletions
@@ -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.
@@ -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)
}
}()
}
})
}
}