feat: add DNSWATCHER_SEND_TEST_NOTIFICATION env var
All checks were successful
check / check (push) Successful in 4s
All checks were successful
check / check (push) Successful in 4s
When set to a truthy value, sends a startup status notification to all configured notification channels after the first full scan completes. The notification is clearly an all-ok/success message showing the number of monitored domains, hostnames, ports, and certificates. Closes #84
This commit is contained in:
@@ -129,6 +129,7 @@ func (w *Watcher) Run(ctx context.Context) {
|
||||
)
|
||||
|
||||
w.RunOnce(ctx)
|
||||
w.maybeSendTestNotification(ctx)
|
||||
|
||||
dnsTicker := time.NewTicker(w.config.DNSInterval)
|
||||
tlsTicker := time.NewTicker(w.config.TLSInterval)
|
||||
@@ -854,6 +855,38 @@ func (w *Watcher) saveState() {
|
||||
}
|
||||
}
|
||||
|
||||
// maybeSendTestNotification sends a startup status notification
|
||||
// after the first full scan completes, if SEND_TEST_NOTIFICATION
|
||||
// is enabled. The message is clearly informational ("all ok")
|
||||
// and not an error or anomaly alert.
|
||||
func (w *Watcher) maybeSendTestNotification(ctx context.Context) {
|
||||
if !w.config.SendTestNotification {
|
||||
return
|
||||
}
|
||||
|
||||
snap := w.state.GetSnapshot()
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"dnswatcher has started and completed its initial scan.\n"+
|
||||
"Monitoring %d domain(s) and %d hostname(s).\n"+
|
||||
"Tracking %d port endpoint(s) and %d TLS certificate(s).\n"+
|
||||
"All notification channels are working.",
|
||||
len(snap.Domains),
|
||||
len(snap.Hostnames),
|
||||
len(snap.Ports),
|
||||
len(snap.Certificates),
|
||||
)
|
||||
|
||||
w.log.Info("sending startup test notification")
|
||||
|
||||
w.notify.SendNotification(
|
||||
ctx,
|
||||
"✅ dnswatcher startup complete",
|
||||
msg,
|
||||
"success",
|
||||
)
|
||||
}
|
||||
|
||||
// --- Utility functions ---
|
||||
|
||||
func toSet(items []string) map[string]bool {
|
||||
|
||||
@@ -756,6 +756,117 @@ func TestDNSRunsBeforePortAndTLSChecks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendTestNotification_Enabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := defaultTestConfig(t)
|
||||
cfg.Domains = []string{"example.com"}
|
||||
cfg.Hostnames = []string{"www.example.com"}
|
||||
cfg.SendTestNotification = true
|
||||
|
||||
w, deps := newTestWatcher(t, cfg)
|
||||
setupBaselineMocks(deps)
|
||||
|
||||
w.RunOnce(t.Context())
|
||||
|
||||
// RunOnce does not send the test notification — it is
|
||||
// sent by Run after RunOnce completes. Call the exported
|
||||
// RunOnce then check that no test notification was sent
|
||||
// (only Run triggers it). We test the full path via Run.
|
||||
notifications := deps.notifier.getNotifications()
|
||||
if len(notifications) != 0 {
|
||||
t.Errorf(
|
||||
"RunOnce should not send test notification, got %d",
|
||||
len(notifications),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendTestNotification_ViaRun(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := defaultTestConfig(t)
|
||||
cfg.Domains = []string{"example.com"}
|
||||
cfg.Hostnames = []string{"www.example.com"}
|
||||
cfg.SendTestNotification = true
|
||||
cfg.DNSInterval = 24 * time.Hour
|
||||
cfg.TLSInterval = 24 * time.Hour
|
||||
|
||||
w, deps := newTestWatcher(t, cfg)
|
||||
setupBaselineMocks(deps)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
w.Run(ctx)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Wait for the initial scan and test notification.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
cancel()
|
||||
|
||||
<-done
|
||||
|
||||
notifications := deps.notifier.getNotifications()
|
||||
|
||||
found := false
|
||||
|
||||
for _, n := range notifications {
|
||||
if n.Priority == "success" &&
|
||||
n.Title == "✅ dnswatcher startup complete" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf(
|
||||
"expected startup test notification, got: %v",
|
||||
notifications,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendTestNotification_Disabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := defaultTestConfig(t)
|
||||
cfg.Domains = []string{"example.com"}
|
||||
cfg.Hostnames = []string{"www.example.com"}
|
||||
cfg.SendTestNotification = false
|
||||
cfg.DNSInterval = 24 * time.Hour
|
||||
cfg.TLSInterval = 24 * time.Hour
|
||||
|
||||
w, deps := newTestWatcher(t, cfg)
|
||||
setupBaselineMocks(deps)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
w.Run(ctx)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
cancel()
|
||||
|
||||
<-done
|
||||
|
||||
notifications := deps.notifier.getNotifications()
|
||||
|
||||
for _, n := range notifications {
|
||||
if n.Title == "✅ dnswatcher startup complete" {
|
||||
t.Error(
|
||||
"test notification should not be sent when disabled",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNSFailureAndRecovery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user