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]) } }