package watcher_test import ( "context" "log/slog" "reflect" "testing" "sneak.berlin/go/dnswatcher/internal/livednstest" "sneak.berlin/go/dnswatcher/internal/resolver" "sneak.berlin/go/dnswatcher/internal/watcher" ) const domain = "example.net" func TestNSAddressChangeAlerts(t *testing.T) { t.Parallel() // Each case is the nameserver addresses saved by the previous check // and by the current one. tests := []struct { name string prev, current map[string][]string want int }{ { "same addresses", map[string][]string{nsA: {ip1, ip2}}, map[string][]string{nsA: {ip1, ip2}}, 0, }, { "same addresses in another order", map[string][]string{nsA: {ip2, ip1}}, map[string][]string{nsA: {ip1, ip2}}, 0, }, { "address replaced", map[string][]string{nsA: {ip1}}, map[string][]string{nsA: {ip2}}, 1, }, { "address added", map[string][]string{nsA: {ip1}}, map[string][]string{nsA: {ip1, ip2}}, 1, }, { "two nameservers changed", map[string][]string{nsA: {ip1}, nsB: {ip2}}, map[string][]string{nsA: {ip3}, nsB: {ip3}}, 2, }, { "nameserver added", map[string][]string{nsA: {ip1}}, map[string][]string{nsA: {ip1}, nsB: {ip2}}, 0, }, { "nameserver removed", map[string][]string{nsA: {ip1}, nsB: {ip2}}, map[string][]string{nsA: {ip1}}, 0, }, { "state file from before addresses were saved", nil, map[string][]string{nsA: {ip1}, nsB: {ip2}}, 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() notifier := &mockNotifier{} w := watcher.NewForTest(nil, nil, nil, nil, nil, notifier) w.DetectNSAddressChanges(t.Context(), domain, tt.prev, tt.current) got := len(notifier.getNotifications()) if got != tt.want { t.Errorf("sent %d address changes, want %d", got, tt.want) } }) } } func TestNSAddressChangeAlertNamesDomainNameserverAndAddresses( t *testing.T, ) { t.Parallel() notifier := &mockNotifier{} w := watcher.NewForTest(nil, nil, nil, nil, nil, notifier) w.DetectNSAddressChanges( t.Context(), domain, map[string][]string{nsA: {ip1}}, map[string][]string{nsA: {ip2, ip3}}, ) want := notification{ Title: "NS Address Change: " + domain, Message: "Domain: " + domain + "\nNameserver: " + nsA + "\nOld: " + ip1 + "\nNew: " + ip2 + ", " + ip3, Priority: "warning", } got := notifier.getNotifications() if len(got) != 1 || got[0] != want { t.Errorf("sent %v, want %v", got, want) } } // TestNameserverWithNoAddressKeepsPrevious looks up nameserver names // with no address: two under .invalid, whose lookup fails with an // error, and one that does not exist under a real zone, which live DNS // answers with no address and no error. Each one with addresses saved // by the previous check keeps them; the one without gets none. func TestNameserverWithNoAddressKeepsPrevious(t *testing.T) { t.Parallel() w := watcher.NewForTest( nil, nil, resolver.NewFromLogger(slog.Default()), nil, nil, nil, ) nonexistentNS := "this-surely-does-not-exist-xyz." + testSmallDomain + "." prev := map[string][]string{oldNS1: {oldIP}, nonexistentNS: {oldIP}} var got map[string][]string // The result is the same whether or not live DNS answers, so the // lookup is not retried. _ = livednstest.Run(func(ctx context.Context) error { got = w.ResolveNameserverAddresses( ctx, []string{oldNS1, oldNS2, nonexistentNS}, prev, ) return nil }) if !reflect.DeepEqual(got, prev) { t.Errorf("saved %v, want %v", got, prev) } }