// Copyright 2020-2026 The happyDomain Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package checker import ( "context" "encoding/json" "reflect" "testing" ) // dummyRule is a minimal CheckRule used only by tests in this package. type dummyRule struct { name string desc string } func (r *dummyRule) Name() string { return r.name } func (r *dummyRule) Description() string { return r.desc } func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) []CheckState { return []CheckState{{Status: StatusOK, Message: r.name + " passed"}} } func TestStatus_MarshalJSON(t *testing.T) { tests := []struct { status Status want string }{ {StatusUnknown, `0`}, {StatusOK, `1`}, {StatusInfo, `2`}, {StatusWarn, `3`}, {StatusCrit, `4`}, {StatusError, `5`}, } for _, tt := range tests { got, err := json.Marshal(tt.status) if err != nil { t.Errorf("Marshal(%v) error: %v", tt.status, err) continue } if string(got) != tt.want { t.Errorf("Marshal(%v) = %s, want %s", tt.status, got, tt.want) } } } func TestStatus_UnmarshalJSON(t *testing.T) { tests := []struct { input string want Status }{ {`0`, StatusUnknown}, {`1`, StatusOK}, {`2`, StatusInfo}, {`3`, StatusWarn}, {`4`, StatusCrit}, {`5`, StatusError}, } for _, tt := range tests { var got Status if err := json.Unmarshal([]byte(tt.input), &got); err != nil { t.Errorf("Unmarshal(%s) error: %v", tt.input, err) continue } if got != tt.want { t.Errorf("Unmarshal(%s) = %v, want %v", tt.input, got, tt.want) } } } func TestStatus_RoundTrip(t *testing.T) { for _, s := range []Status{StatusOK, StatusInfo, StatusUnknown, StatusWarn, StatusCrit, StatusError} { data, err := json.Marshal(s) if err != nil { t.Fatalf("Marshal(%v) error: %v", s, err) } var got Status if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("Unmarshal(%s) error: %v", data, err) } if got != s { t.Errorf("round-trip %v: got %v", s, got) } } } func TestStatus_String(t *testing.T) { if got := StatusOK.String(); got != "OK" { t.Errorf("StatusOK.String() = %q, want \"OK\"", got) } if got := Status(99).String(); got != "Status(99)" { t.Errorf("Status(99).String() = %q, want \"Status(99)\"", got) } } func TestCheckTarget_Scope(t *testing.T) { tests := []struct { target CheckTarget want CheckScopeType }{ {CheckTarget{}, CheckScopeUser}, {CheckTarget{UserId: "u1"}, CheckScopeUser}, {CheckTarget{DomainId: "d1"}, CheckScopeDomain}, {CheckTarget{DomainId: "d1", ServiceId: "s1"}, CheckScopeService}, {CheckTarget{ServiceId: "s1"}, CheckScopeService}, } for _, tt := range tests { if got := tt.target.Scope(); got != tt.want { t.Errorf("%+v.Scope() = %v, want %v", tt.target, got, tt.want) } } } func TestCheckTarget_String(t *testing.T) { tests := []struct { target CheckTarget want string }{ {CheckTarget{}, "//"}, {CheckTarget{UserId: "u1"}, "u1//"}, {CheckTarget{UserId: "u1", DomainId: "d1"}, "u1/d1/"}, {CheckTarget{UserId: "u1", DomainId: "d1", ServiceId: "s1"}, "u1/d1/s1"}, // Ensure different targets with different empty fields don't collide. {CheckTarget{DomainId: "d1"}, "/d1/"}, {CheckTarget{ServiceId: "s1"}, "//s1"}, } for _, tt := range tests { if got := tt.target.String(); got != tt.want { t.Errorf("%+v.String() = %q, want %q", tt.target, got, tt.want) } } } func TestCheckerDefinition_BuildRulesInfo(t *testing.T) { d := &CheckerDefinition{ Rules: []CheckRule{&dummyRule{name: "r1", desc: "desc1"}}, } d.BuildRulesInfo() if len(d.RulesInfo) != 1 { t.Fatalf("BuildRulesInfo: got %d rules, want 1", len(d.RulesInfo)) } if d.RulesInfo[0].Name != "r1" || d.RulesInfo[0].Description != "desc1" { t.Errorf("BuildRulesInfo: got %+v, want {Name:r1, Description:desc1}", d.RulesInfo[0]) } } // Compile-time check that fixedReportContext implements ReportContext. var _ ReportContext = fixedReportContext{} func TestStaticReportContext_NoExtras(t *testing.T) { ctx := StaticReportContext(json.RawMessage(`{"k":"v"}`)) if string(ctx.Data()) != `{"k":"v"}` { t.Errorf("Data() = %s, want %s", ctx.Data(), `{"k":"v"}`) } if ctx.Related("any") != nil { t.Error("Related(any) should be nil for StaticReportContext") } if ctx.States() != nil { t.Error("States() should be nil for StaticReportContext") } } func TestNewReportContext_NilStates(t *testing.T) { ctx := NewReportContext(json.RawMessage(`{}`), nil, nil) if ctx.States() != nil { t.Errorf("States() = %v, want nil", ctx.States()) } } func TestNewReportContext_PassesStates(t *testing.T) { states := []CheckState{ {Status: StatusWarn, Message: "heads up", RuleName: "r1"}, {Status: StatusCrit, Message: "fix me", RuleName: "r2", Subject: "host.example"}, } ctx := NewReportContext(json.RawMessage(`{}`), nil, states) got := ctx.States() if !reflect.DeepEqual(got, states) { t.Errorf("States() = %+v, want %+v", got, states) } } func TestNewReportContext_PassesRelated(t *testing.T) { rel := map[ObservationKey][]RelatedObservation{ "other.key": {{CheckerID: "other", Key: "other.key", Ref: "r1"}}, } ctx := NewReportContext(json.RawMessage(`{}`), rel, nil) if got := ctx.Related("other.key"); len(got) != 1 || got[0].CheckerID != "other" { t.Errorf("Related(other.key) = %+v, want one entry with CheckerID=other", got) } if ctx.Related("missing") != nil { t.Error("Related(missing) should be nil") } } func TestRegisterChecker_EmptyIDRejected(t *testing.T) { resetRegistries() RegisterChecker(&CheckerDefinition{ID: "", Name: "bad"}) if len(GetCheckers()) != 0 { t.Error("checker with empty ID should not be registered") } }