watcher: a stop during the first scan sends no startup notification (closes #271)
check / check (push) Canceled after 0s

Run sent the startup notification right after the first scan without
checking the context. RunOnce returns as usual when shutdown cuts the
scan short, so a stop during the first scan sent "has started and
completed its initial scan" to every endpoint as the process shut down.
Run now sends it only when the context was not cancelled.

The new test cancels the context before Run starts, so the scan never
sends its lookup; a stop part way through a scan follows the same path.

Model: opus-5-5
This commit is contained in:
2026-10-13 17:12:21 +00:00
parent b9686c6d7b
commit 2555714086
3 changed files with 32 additions and 1 deletions
+2
View File
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
# Completed Steps
- 2026-10-13: a stop before the first scan completes sends no startup
notification; a scan that ran to the end still sends it (closes #271).
- 2026-10-13: `DNSWATCHER_METRICS_USERNAME` set without
`DNSWATCHER_METRICS_PASSWORD` stops startup (closes #268).
- 2026-10-13: a save syncs the state file to disk before renaming it into place,
+24
View File
@@ -83,6 +83,30 @@ func TestCancelledCheckSavesNothing(t *testing.T) {
}
}
// TestStopBeforeFirstScanSendsNoStartupNotification runs a watcher with
// the startup notification on and its context already cancelled, so the
// first scan is cut short before its lookup is sent. The scan did not
// complete, so no startup notification may be sent.
func TestStopBeforeFirstScanSendsNoStartupNotification(t *testing.T) {
t.Parallel()
cfg := defaultTestConfig(t)
cfg.Hostnames = []string{testHost}
cfg.SendTestNotification = true
w, deps := newTestWatcher(t, cfg)
ctx, cancel := context.WithCancel(t.Context())
cancel()
w.Run(ctx)
notifications := deps.notifier.getNotifications()
if len(notifications) != 0 {
t.Errorf("sent %v, want no notifications", notifications)
}
}
// newLoggingWatcher returns a watcher for a domain and a hostname, with
// the real resolver, that writes what it logs at warning level or above
// into the returned buffer.
+6 -1
View File
@@ -133,7 +133,12 @@ func (w *Watcher) Run(ctx context.Context) {
w.cleanupRemovedTargets()
w.RunOnce(ctx)
w.maybeSendTestNotification(ctx)
// RunOnce returns as usual when shutdown cuts the scan short, and
// the startup notification says the scan completed.
if ctx.Err() == nil {
w.maybeSendTestNotification(ctx)
}
dnsTicker := time.NewTicker(w.config.DNSInterval)
tlsTicker := time.NewTicker(w.config.TLSInterval)