package watcher_test import ( "maps" "slices" "testing" "sneak.berlin/go/dnswatcher/internal/state" "sneak.berlin/go/dnswatcher/internal/watcher" ) // TestRemovedTargetsLeaveTheState loads a state saved while a domain // and a hostname now removed from the configuration were still in it, // and runs the removal that Run does before the first check. The // removed names' domain, hostname and certificate entries are gone, // the configured names' are kept, and nothing is notified. Nothing is // looked up: the watcher has no resolver. func TestRemovedTargetsLeaveTheState(t *testing.T) { t.Parallel() const ( removedDomain = "example.com" removedHost = "www.example.com" ) cfg := defaultTestConfig(t) cfg.Domains = []string{domain} cfg.Hostnames = []string{host} deps := newTestDeps(t, cfg) w := watcher.NewForTest( cfg, deps.state, nil, deps.portChecker, deps.tlsChecker, deps.notifier, ) // The state a check of all four names saves, each name at ip1. for _, name := range []string{domain, removedDomain} { deps.state.SetDomainState(name, &state.DomainState{ Nameservers: []string{nsA}, }) } for _, name := range []string{domain, host, removedDomain, removedHost} { deps.state.SetHostnameState(name, saved( map[string]*state.NameserverRecordState{ nsA: answered(map[string][]string{"A": {ip1}}), }, )) deps.state.SetCertificateState( ip1+":443:"+name, &state.CertificateState{Status: "ok"}, ) } err := deps.state.Save() if err != nil { t.Fatalf("saving the state: %v", err) } err = deps.state.Load() if err != nil { t.Fatalf("loading the state: %v", err) } w.CleanupRemovedTargets() snap := deps.state.GetSnapshot() got := slices.Sorted(maps.Keys(snap.Domains)) if want := []string{domain}; !slices.Equal(got, want) { t.Errorf("domain entries %v, want %v", got, want) } got = slices.Sorted(maps.Keys(snap.Hostnames)) if want := []string{domain, host}; !slices.Equal(got, want) { t.Errorf("hostname entries %v, want %v", got, want) } got = slices.Sorted(maps.Keys(snap.Certificates)) if want := []string{ ip1 + ":443:" + domain, ip1 + ":443:" + host, }; !slices.Equal(got, want) { t.Errorf("certificate entries %v, want %v", got, want) } if sent := deps.notifier.getNotifications(); len(sent) != 0 { t.Errorf("sent %v, want nothing", sent) } } // TestCertificateStateForAnAddressGone runs the port checks on hostname // state built here for a configured hostname, with certificate entries // saved for it at ip1, ip2 and an IPv6 address. When its nameservers // answered with ip1 and the IPv6 address, the entry for ip2 is removed. // When none of them answered, its addresses are not known, and every // entry is kept. Nothing is notified, and nothing is looked up. func TestCertificateStateForAnAddressGone(t *testing.T) { t.Parallel() const ip6 = "2001:db8::1" tests := []struct { name string hostname *state.HostnameState want []string }{ { "answered without ip2", saved(map[string]*state.NameserverRecordState{ nsA: answered(map[string][]string{ "A": {ip1}, "AAAA": {ip6}, }), }), []string{ip1 + ":443:" + host, ip6 + ":443:" + host}, }, { "no nameserver answered", saved(map[string]*state.NameserverRecordState{ nsA: failed(), nsB: failed(), }), []string{ ip1 + ":443:" + host, ip2 + ":443:" + host, ip6 + ":443:" + host, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cfg := defaultTestConfig(t) cfg.Hostnames = []string{host} deps := newTestDeps(t, cfg) w := watcher.NewForTest( cfg, deps.state, nil, deps.portChecker, deps.tlsChecker, deps.notifier, ) w.SetFirstRun(false) deps.state.SetHostnameState(host, tt.hostname) for _, ip := range []string{ip1, ip2, ip6} { deps.state.SetCertificateState( ip+":443:"+host, &state.CertificateState{Status: "ok"}, ) } w.CheckAllPorts(t.Context()) got := slices.Sorted(maps.Keys(deps.state.GetSnapshot().Certificates)) if !slices.Equal(got, tt.want) { t.Errorf("certificate entries %v, want %v", got, tt.want) } if sent := deps.notifier.getNotifications(); len(sent) != 0 { t.Errorf("sent %v, want nothing", sent) } }) } }