checker-happydeliver/checker/rule_test.go

159 lines
5 KiB
Go

package checker
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
sdk "git.happydns.org/checker-sdk-go/checker"
)
type fakeObs struct {
data HappyDeliverData
err error
}
func (f fakeObs) Get(_ context.Context, _ sdk.ObservationKey, dest any) error {
if f.err != nil {
return f.err
}
raw, err := json.Marshal(f.data)
if err != nil {
return err
}
return json.Unmarshal(raw, dest)
}
func (fakeObs) GetRelated(context.Context, sdk.ObservationKey) ([]sdk.RelatedObservation, error) {
return nil, nil
}
func okData() HappyDeliverData {
return HappyDeliverData{
Phase: "ok",
Scores: map[string]int{
SectionOverall: 90, SectionDNS: 80, SectionAuthentication: 60,
},
Grades: map[string]string{
SectionOverall: "A", SectionDNS: "B", SectionAuthentication: "D",
},
}
}
func TestSectionRuleAboveThreshold(t *testing.T) {
r := NewSectionRule(SectionOverall, "Overall", 70)
states := r.Evaluate(context.Background(), fakeObs{data: okData()}, sdk.CheckerOptions{})
if len(states) != 1 || states[0].Status != sdk.StatusOK {
t.Fatalf("got %+v", states)
}
if !strings.Contains(states[0].Message, "score: 90") {
t.Errorf("message = %q", states[0].Message)
}
if states[0].Meta["score"] != 90 {
t.Errorf("meta score = %v", states[0].Meta["score"])
}
}
func TestSectionRuleBelowThresholdCRIT(t *testing.T) {
r := NewSectionRule(SectionAuthentication, "Authentication", 80)
states := r.Evaluate(context.Background(), fakeObs{data: okData()}, sdk.CheckerOptions{})
if states[0].Status != sdk.StatusCrit {
t.Errorf("status = %v, want CRIT", states[0].Status)
}
}
func TestSectionRuleOptionOverridesDefault(t *testing.T) {
r := NewSectionRule(SectionDNS, "DNS", 70)
// Score is 80, default threshold is 70 (OK), but we raise it to 95 -> CRIT.
states := r.Evaluate(context.Background(), fakeObs{data: okData()},
sdk.CheckerOptions{"min_score_dns": float64(95)})
if states[0].Status != sdk.StatusCrit {
t.Errorf("status = %v, want CRIT", states[0].Status)
}
}
func TestSectionRuleNoReportYet(t *testing.T) {
r := NewSectionRule(SectionOverall, "Overall", 70)
states := r.Evaluate(context.Background(), fakeObs{data: HappyDeliverData{Phase: "send"}}, sdk.CheckerOptions{})
if states[0].Status != sdk.StatusInfo || states[0].Code != "happydeliver.score.unavailable" {
t.Errorf("got %+v", states[0])
}
}
func TestSectionRuleScoreMissing(t *testing.T) {
r := NewSectionRule(SectionContent, "Content", 70)
states := r.Evaluate(context.Background(), fakeObs{data: okData()}, sdk.CheckerOptions{})
if states[0].Status != sdk.StatusInfo || states[0].Code != "happydeliver.score.missing" {
t.Errorf("got %+v", states[0])
}
}
func TestSectionRuleObservationError(t *testing.T) {
r := NewSectionRule(SectionOverall, "Overall", 70)
states := r.Evaluate(context.Background(), fakeObs{err: errors.New("boom")}, sdk.CheckerOptions{})
if states[0].Status != sdk.StatusError {
t.Errorf("status = %v", states[0].Status)
}
if !strings.Contains(states[0].Message, "boom") {
t.Errorf("message = %q", states[0].Message)
}
}
func TestSectionRuleNameAndOptionsDoc(t *testing.T) {
r := NewSectionRule(SectionDNS, "DNS", 70).(*sectionRule)
if r.Name() != "happydeliver.score.dns" {
t.Errorf("Name = %q", r.Name())
}
rwo, ok := any(r).(sdk.CheckRuleWithOptions)
if !ok {
t.Fatal("sectionRule should implement CheckRuleWithOptions")
}
doc := rwo.Options()
if len(doc.UserOpts) != 1 || doc.UserOpts[0].Id != "min_score_dns" {
t.Errorf("doc = %+v", doc)
}
if doc.UserOpts[0].Default != 70.0 {
t.Errorf("default = %v", doc.UserOpts[0].Default)
}
}
func TestLifecycleRulePhases(t *testing.T) {
cases := []struct {
phase string
errMsg string
latency float64
wantSt sdk.Status
wantCode string
}{
{"ok", "", 1.5, sdk.StatusOK, "happydeliver.lifecycle.ok"},
{"allocate", "down", 0, sdk.StatusCrit, "happydeliver.api.unavailable"},
{"send", "auth fail", 0, sdk.StatusCrit, "happydeliver.send.failed"},
{"timeout", "", 0, sdk.StatusWarn, "happydeliver.no_message_received"},
{"wait", "x", 0, sdk.StatusCrit, "happydeliver.wait.failed"},
{"fetch", "x", 0, sdk.StatusCrit, "happydeliver.fetch.failed"},
{"parse", "bad", 0, sdk.StatusCrit, "happydeliver.parse.failed"},
{"weird", "", 0, sdk.StatusInfo, "happydeliver.lifecycle.unknown"},
}
r := NewLifecycleRule()
for _, tc := range cases {
t.Run(tc.phase, func(t *testing.T) {
d := HappyDeliverData{Phase: tc.phase, Error: tc.errMsg, LatencySeconds: tc.latency}
states := r.Evaluate(context.Background(), fakeObs{data: d}, sdk.CheckerOptions{})
if states[0].Status != tc.wantSt {
t.Errorf("status = %v, want %v", states[0].Status, tc.wantSt)
}
if states[0].Code != tc.wantCode {
t.Errorf("code = %q, want %q", states[0].Code, tc.wantCode)
}
})
}
}
func TestLifecycleRuleObservationError(t *testing.T) {
states := NewLifecycleRule().Evaluate(context.Background(), fakeObs{err: errors.New("io")}, sdk.CheckerOptions{})
if states[0].Status != sdk.StatusError {
t.Errorf("got %+v", states[0])
}
}