Files
dnswatcher/internal/watcher/skiprecordnotifications_test.go
T
sneak bbba4bfdcb
check / check (push) Canceled after 1m56s
watcher, config: no Record Change or Inconsistency for listed names (closes #255)
DNSWATCHER_SKIP_RECORD_NOTIFICATIONS takes a comma-separated list of
names from DNSWATCHER_TARGETS, read as the targets are (letter case,
trailing dot, repeats). For a listed name no Record Change and no
Inconsistency notification is sent; its records are still checked and
saved, and its other notifications are sent. A listed name that is not
a target stops startup with an error naming it.

The two detections return early for a listed name. Apex domains are
covered too: their own records go through the same detection.

Live DNS cannot be made to disagree on purpose, so the Inconsistency
test feeds records to the change detection directly, as the existing
inconsistency tests do; the live test covers Record Change.

Model: opus-5-5
2026-10-06 00:04:59 +00:00

108 lines
3.2 KiB
Go

package watcher_test
import (
"slices"
"testing"
"sneak.berlin/go/dnswatcher/internal/config"
"sneak.berlin/go/dnswatcher/internal/state"
"sneak.berlin/go/dnswatcher/internal/watcher"
)
// TestSkipRecordNotificationsAlerts runs the hostname change detection
// for two names on the same check: nsA's address changes, so that nsA
// now differs from nsB, and nsC stops answering. The name in
// SkipRecordNotifications gets only the NS Failure; the other name also
// gets the Record Change and the Inconsistency.
func TestSkipRecordNotificationsAlerts(t *testing.T) {
t.Parallel()
const skipped = "skipped.example.net"
prev := saved(map[string]*state.NameserverRecordState{
nsA: answered(map[string][]string{"A": {ip1}}),
nsB: answered(map[string][]string{"A": {ip1}}),
nsC: answered(map[string][]string{"A": {ip1}}),
})
current := saved(map[string]*state.NameserverRecordState{
nsA: answered(map[string][]string{"A": {ip2}}),
nsB: answered(map[string][]string{"A": {ip1}}),
nsC: failed(),
})
cfg := &config.Config{
Hostnames: []string{host, skipped},
SkipRecordNotifications: []string{skipped},
}
// The hostname change detection uses only the configuration and the
// notifier.
notifier := &mockNotifier{}
w := watcher.NewForTest(cfg, nil, nil, nil, nil, notifier)
w.DetectHostnameChanges(t.Context(), host, prev, current)
w.DetectHostnameChanges(t.Context(), skipped, prev, current)
sent := notifier.getNotifications()
titles := make([]string, 0, len(sent))
for _, n := range sent {
titles = append(titles, n.Title)
}
slices.Sort(titles)
want := []string{
"Inconsistency: " + host,
"NS Failure: " + skipped,
"NS Failure: " + host,
"Record Change: " + host,
}
if !slices.Equal(titles, want) {
t.Errorf("sent %v, want %v", titles, want)
}
}
// TestSkipRecordNotificationsCheck checks testHost, which is in
// SkipRecordNotifications, from a saved state in which every nameserver
// live DNS lists answered with an address live DNS never returns. No
// Record Change and no Inconsistency is sent, and the check still saves
// what the nameservers answer.
func TestSkipRecordNotificationsCheck(t *testing.T) {
t.Parallel()
cfg := defaultTestConfig(t)
cfg.Hostnames = []string{testHost}
cfg.SkipRecordNotifications = []string{testHost}
nameservers := lookupNameservers(t, testHost)
_, deps := runChecks(t, cfg, func(deps *testDeps) {
byNameserver := make(map[string]*state.NameserverRecordState)
for _, ns := range nameservers {
byNameserver[ns] = answered(map[string][]string{"A": {oldIP}})
}
deps.state.SetHostnameState(testHost, saved(byNameserver))
})
for _, title := range []string{
"Record Change: " + testHost,
"Inconsistency: " + testHost,
} {
if n := countNotifications(deps, title); n != 0 {
t.Errorf("sent %d %q, want 0", n, title)
}
}
// A nameserver whose query for A failed keeps oldIP, so the check
// is that some address live DNS gave was saved.
hs, _ := deps.state.GetHostnameState(testHost)
fromLiveDNS := func(ip string) bool { return ip != oldIP }
if !slices.ContainsFunc(addresses(hs), fromLiveDNS) {
t.Errorf("saved addresses %v, want those live DNS gave", addresses(hs))
}
}