check / check (push) Canceled after 0s
The port checks, which the first check after startup runs after its DNS checks, now also remove the domain and hostname entries of names no longer in DNSWATCHER_TARGETS, and the certificate entries of those names and of addresses a name no longer resolves to. A configured domain's own records, saved as a hostname entry under its name, are kept, and so are the certificate entries of a configured name none of whose nameservers answered, as its port entries already were. Nothing is notified. Model: opus-5-5
168 lines
4.3 KiB
Go
168 lines
4.3 KiB
Go
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 port checks, which the first check after startup runs
|
|
// after its DNS checks. 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,
|
|
)
|
|
|
|
// A state file is loaded, so changes are notified from the first
|
|
// check on.
|
|
w.SetFirstRun(false)
|
|
|
|
// 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.CheckAllPorts(t.Context())
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|