check / check (push) Successful in 57s
detectInconsistencies alerted for every neighbouring pair of nameservers whose records differed, on every DNS check, for as long as they differed. It now also takes the previous hostname state and alerts for a pair only when both nameservers were in that state with equal records. The state loaded at startup is the previous state for the first check, so a disagreement saved before a restart is not reported again. The choice of pairs is in newlyDisagreeingPairs, tested on record data built in the test. The README describes the new behaviour. Model: opus-5-5
110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package watcher_test
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/state"
|
|
"sneak.berlin/go/dnswatcher/internal/watcher"
|
|
)
|
|
|
|
// hostnameState builds the state a check with these records leaves behind.
|
|
func hostnameState(
|
|
records map[string]map[string][]string,
|
|
) *state.HostnameState {
|
|
hs := &state.HostnameState{
|
|
RecordsByNameserver: make(map[string]*state.NameserverRecordState),
|
|
}
|
|
|
|
for ns, recs := range records {
|
|
hs.RecordsByNameserver[ns] = &state.NameserverRecordState{
|
|
Records: recs,
|
|
Status: "ok",
|
|
}
|
|
}
|
|
|
|
return hs
|
|
}
|
|
|
|
func TestNewlyDisagreeingPairs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
nsA = "a.ns.example.net."
|
|
nsB = "b.ns.example.net."
|
|
ip1 = "192.0.2.1"
|
|
ip2 = "192.0.2.2"
|
|
)
|
|
|
|
onlyA := map[string]map[string][]string{nsA: {"A": {ip1}}}
|
|
agree := map[string]map[string][]string{
|
|
nsA: {"A": {ip1}},
|
|
nsB: {"A": {ip1}},
|
|
}
|
|
disagree := map[string]map[string][]string{
|
|
nsA: {"A": {ip1}},
|
|
nsB: {"A": {ip2}},
|
|
}
|
|
alert := [][2]string{{nsA, nsB}}
|
|
|
|
// Each case starts from the state loaded at startup and runs the
|
|
// checks in order; want[i] is what check i alerts for.
|
|
tests := []struct {
|
|
name string
|
|
loaded map[string]map[string][]string
|
|
checks []map[string]map[string][]string
|
|
want [][][2]string
|
|
}{
|
|
{
|
|
name: "disagreement persisting across checks alerts once",
|
|
loaded: agree,
|
|
checks: []map[string]map[string][]string{disagree, disagree, disagree},
|
|
want: [][][2]string{alert, nil, nil},
|
|
},
|
|
{
|
|
name: "disagreement starting on a later check alerts on it",
|
|
loaded: agree,
|
|
checks: []map[string]map[string][]string{agree, agree, disagree},
|
|
want: [][][2]string{nil, nil, alert},
|
|
},
|
|
{
|
|
name: "disagreement in the loaded state does not alert",
|
|
loaded: disagree,
|
|
checks: []map[string]map[string][]string{disagree, disagree},
|
|
want: [][][2]string{nil, nil},
|
|
},
|
|
{
|
|
name: "nameserver new on this check does not alert",
|
|
loaded: onlyA,
|
|
checks: []map[string]map[string][]string{disagree},
|
|
want: [][][2]string{nil},
|
|
},
|
|
{
|
|
name: "disagreement after agreeing again alerts again",
|
|
loaded: agree,
|
|
checks: []map[string]map[string][]string{disagree, agree, disagree},
|
|
want: [][][2]string{alert, nil, alert},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prev := hostnameState(tt.loaded)
|
|
|
|
for i, current := range tt.checks {
|
|
got := watcher.NewlyDisagreeingPairs(prev, current)
|
|
if !slices.Equal(got, tt.want[i]) {
|
|
t.Errorf(
|
|
"check %d: alerted for %v, want %v",
|
|
i, got, tt.want[i],
|
|
)
|
|
}
|
|
|
|
prev = hostnameState(current)
|
|
}
|
|
})
|
|
}
|
|
}
|