Initial commit
This commit is contained in:
commit
ac57589750
25 changed files with 3026 additions and 0 deletions
193
checker/rules_discovery_test.go
Normal file
193
checker/rules_discovery_test.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"testing"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// stubObs implements the minimal subset of sdk.ObservationGetter the rules use.
|
||||
type stubObs struct {
|
||||
data *ObservationData
|
||||
err error
|
||||
}
|
||||
|
||||
func (s stubObs) Get(_ context.Context, _ sdk.ObservationKey, dst any) error {
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
b, err := json.Marshal(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(b, dst)
|
||||
}
|
||||
|
||||
func (s stubObs) GetRelated(_ context.Context, _ sdk.ObservationKey) ([]sdk.RelatedObservation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func mkOpts(kv map[string]any) sdk.CheckerOptions {
|
||||
out := sdk.CheckerOptions{}
|
||||
maps.Copy(out, kv)
|
||||
return out
|
||||
}
|
||||
|
||||
func TestNSDeclaredRule(t *testing.T) {
|
||||
rule := &nsDeclaredRule{}
|
||||
|
||||
t.Run("ok with two NS", func(t *testing.T) {
|
||||
d := &ObservationData{
|
||||
DeclaredNS: []string{"ns1.example.com.", "ns2.example.com."},
|
||||
Probed: []string{"ns1.example.com.", "ns2.example.com."},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(nil))
|
||||
if len(states) != 1 || states[0].Status != sdk.StatusOK {
|
||||
t.Errorf("expected OK, got %#v", states)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("too few NS", func(t *testing.T) {
|
||||
d := &ObservationData{
|
||||
DeclaredNS: []string{"ns1.example.com."},
|
||||
Probed: []string{"ns1.example.com."},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(map[string]any{"minNameServers": 2}))
|
||||
if len(states) != 1 || states[0].Code != CodeTooFewNS {
|
||||
t.Errorf("expected TooFewNS, got %#v", states)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no NS at all", func(t *testing.T) {
|
||||
d := &ObservationData{}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(map[string]any{"useParentNS": false}))
|
||||
var hasNoNS bool
|
||||
for _, st := range states {
|
||||
if st.Code == CodeNoNS {
|
||||
hasNoNS = true
|
||||
}
|
||||
}
|
||||
if !hasNoNS {
|
||||
t.Errorf("expected NoNS finding, got %#v", states)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNSReachableRule(t *testing.T) {
|
||||
rule := &nsReachableRule{}
|
||||
|
||||
t.Run("UDP fail is critical", func(t *testing.T) {
|
||||
d := &ObservationData{
|
||||
Probed: []string{"ns1."},
|
||||
Results: map[string]*NSResult{
|
||||
"ns1.": {UDPReachable: false, TCPReachable: false},
|
||||
},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(nil))
|
||||
if len(states) != 1 || states[0].Code != CodeNSUDPFailed || states[0].Status != sdk.StatusCrit {
|
||||
t.Errorf("expected critical UDP fail, got %#v", states)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TCP fail crit when requireTCP", func(t *testing.T) {
|
||||
d := &ObservationData{
|
||||
Probed: []string{"ns1."},
|
||||
Results: map[string]*NSResult{
|
||||
"ns1.": {UDPReachable: true, TCPReachable: false},
|
||||
},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(map[string]any{"requireTCP": true}))
|
||||
if len(states) != 1 || states[0].Code != CodeNSTCPFailed || states[0].Status != sdk.StatusCrit {
|
||||
t.Errorf("got %#v", states)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TCP fail warn when not required", func(t *testing.T) {
|
||||
d := &ObservationData{
|
||||
Probed: []string{"ns1."},
|
||||
Results: map[string]*NSResult{
|
||||
"ns1.": {UDPReachable: true, TCPReachable: false},
|
||||
},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(map[string]any{"requireTCP": false}))
|
||||
if len(states) != 1 || states[0].Status != sdk.StatusWarn {
|
||||
t.Errorf("got %#v", states)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthoritativeRule_Lame(t *testing.T) {
|
||||
rule := &authoritativeRule{}
|
||||
d := &ObservationData{
|
||||
Zone: "example.com.",
|
||||
HasSOA: true,
|
||||
Probed: []string{"ns1."},
|
||||
Results: map[string]*NSResult{
|
||||
"ns1.": {UDPReachable: true, Authoritative: false},
|
||||
},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(nil))
|
||||
if len(states) != 1 || states[0].Code != CodeLame {
|
||||
t.Errorf("expected lame finding, got %#v", states)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatencyRule(t *testing.T) {
|
||||
rule := &latencyRule{}
|
||||
d := &ObservationData{
|
||||
Probed: []string{"fast.", "slow."},
|
||||
Results: map[string]*NSResult{
|
||||
"fast.": {UDPReachable: true, LatencyMs: 50},
|
||||
"slow.": {UDPReachable: true, LatencyMs: 1000},
|
||||
},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(map[string]any{"latencyThresholdMs": 500}))
|
||||
if len(states) != 1 || states[0].Code != CodeSlowNS || states[0].Subject != "slow." {
|
||||
t.Errorf("expected single slow finding for slow., got %#v", states)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentDelegationRule_Drift(t *testing.T) {
|
||||
rule := &parentDelegationRule{}
|
||||
d := &ObservationData{
|
||||
DeclaredNS: []string{"ns1.example.com.", "ns2.example.com."},
|
||||
ParentNS: []string{"ns1.example.com.", "ns3.example.com."},
|
||||
}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(nil))
|
||||
if len(states) != 1 || states[0].Code != CodeParentDrift {
|
||||
t.Errorf("expected ParentDrift, got %#v", states)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentDelegationRule_QueryFailed(t *testing.T) {
|
||||
rule := &parentDelegationRule{}
|
||||
d := &ObservationData{ParentQueryError: "boom"}
|
||||
states := rule.Evaluate(context.Background(), stubObs{data: d}, mkOpts(nil))
|
||||
if len(states) != 1 || states[0].Code != CodeParentQueryFailed {
|
||||
t.Errorf("expected ParentQueryFailed, got %#v", states)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesRegistry(t *testing.T) {
|
||||
rules := Rules()
|
||||
if len(rules) == 0 {
|
||||
t.Fatal("Rules() returned empty list")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, r := range rules {
|
||||
name := r.Name()
|
||||
if name == "" {
|
||||
t.Error("rule with empty name")
|
||||
}
|
||||
if seen[name] {
|
||||
t.Errorf("duplicate rule name: %s", name)
|
||||
}
|
||||
seen[name] = true
|
||||
if r.Description() == "" {
|
||||
t.Errorf("rule %s has empty description", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue