diff --git a/checker/names.go b/checker/names.go deleted file mode 100644 index 61df52e..0000000 --- a/checker/names.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 "strings" - -// JoinRelative treats name as relative to origin, as happyDomain encodes -// service-embedded record owners and subdomains. An empty or "@" name -// resolves to the origin itself; an empty origin returns the trimmed name -// unchanged. A name already suffixed by origin is returned as-is so that -// absolute encodings round-trip safely. Trailing dots are stripped. -func JoinRelative(name, origin string) string { - origin = strings.TrimSuffix(origin, ".") - name = strings.TrimSuffix(name, ".") - if origin == "" { - return name - } - if name == "" || name == "@" { - return origin - } - if name == origin || strings.HasSuffix(name, "."+origin) { - return name - } - return name + "." + origin -} diff --git a/checker/server/healthcheck.go b/checker/server/healthcheck.go deleted file mode 100644 index bb79d22..0000000 --- a/checker/server/healthcheck.go +++ /dev/null @@ -1,81 +0,0 @@ -// 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 server - -import ( - "context" - "flag" - "fmt" - "net" - "net/http" - "strings" - "time" -) - -// healthcheckMode is registered on the default flag set so any consumer that -// calls flag.Parse() before ListenAndServe (the standard pattern in our -// checker mains) gets the behaviour for free. When set, ListenAndServe -// performs a short-lived HTTP probe against /health on the configured listen -// address and exits 0/1 instead of starting the server. This lets the same -// binary act as its own Docker HEALTHCHECK probe for scratch images, where -// no shell, curl or wget is available. -var healthcheckMode = flag.Bool( - "healthcheck", - false, - "probe /health on the server's listen address and exit 0 if healthy, 1 "+ - "otherwise (intended as a Docker HEALTHCHECK for scratch-based images)", -) - -// runHealthcheck performs a GET against http:///health with a short -// timeout. Returns nil on a 2xx response, an error otherwise. A bind address -// like ":8080" or "0.0.0.0:8080" is rewritten to dial the loopback interface -// so the probe targets the local process. -func runHealthcheck(addr string) error { - host, port, err := net.SplitHostPort(normalizeHealthcheckAddr(addr)) - if err != nil { - return fmt.Errorf("invalid listen addr %q: %w", addr, err) - } - if host == "" || host == "0.0.0.0" || host == "::" { - host = "127.0.0.1" - } - url := fmt.Sprintf("http://%s/health", net.JoinHostPort(host, port)) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return err - } - client := &http.Client{Timeout: 2 * time.Second} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode/100 != 2 { - return fmt.Errorf("unhealthy: HTTP %d", resp.StatusCode) - } - return nil -} - -func normalizeHealthcheckAddr(a string) string { - if strings.HasPrefix(a, ":") { - return "127.0.0.1" + a - } - if strings.HasPrefix(a, "[::]:") { - return "[::1]:" + strings.TrimPrefix(a, "[::]:") - } - return a -} diff --git a/checker/server/healthcheck_test.go b/checker/server/healthcheck_test.go deleted file mode 100644 index daa4bc5..0000000 --- a/checker/server/healthcheck_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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 server - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestRunHealthcheck_OK(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - }) - srv := httptest.NewServer(mux) - defer srv.Close() - - addr := strings.TrimPrefix(srv.URL, "http://") - if err := runHealthcheck(addr); err != nil { - t.Fatalf("runHealthcheck(%s) returned error: %v", addr, err) - } -} - -func TestRunHealthcheck_NonOK(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - }) - srv := httptest.NewServer(mux) - defer srv.Close() - - addr := strings.TrimPrefix(srv.URL, "http://") - if err := runHealthcheck(addr); err == nil { - t.Fatalf("runHealthcheck against 503 returned nil; want error") - } -} - -func TestRunHealthcheck_Unreachable(t *testing.T) { - // Reserved-for-documentation port on loopback that nothing should bind. - if err := runHealthcheck("127.0.0.1:1"); err == nil { - t.Fatalf("runHealthcheck against unreachable port returned nil; want error") - } -} - -func TestNormalizeHealthcheckAddr(t *testing.T) { - cases := map[string]string{ - ":8080": "127.0.0.1:8080", - "127.0.0.1:8080": "127.0.0.1:8080", - "0.0.0.0:8080": "0.0.0.0:8080", - "[::1]:8080": "[::1]:8080", - "[::]:8080": "[::1]:8080", - } - for in, want := range cases { - if got := normalizeHealthcheckAddr(in); got != want { - t.Errorf("normalizeHealthcheckAddr(%q) = %q, want %q", in, got, want) - } - } -} diff --git a/checker/server/interactive.go b/checker/server/interactive.go index 27cc260..2ac133c 100644 --- a/checker/server/interactive.go +++ b/checker/server/interactive.go @@ -96,15 +96,14 @@ func (s *Server) handleCheckForm(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleCheckSubmit(w http.ResponseWriter, r *http.Request) { - fields := s.interactive.RenderForm() if err := r.ParseForm(); err != nil { - s.renderCheckForm(w, fields, fmt.Sprintf("invalid form: %v", err)) + s.renderCheckForm(w, s.interactive.RenderForm(), fmt.Sprintf("invalid form: %v", err)) return } opts, err := s.interactive.ParseForm(r) if err != nil { - s.renderCheckForm(w, fields, err.Error()) + s.renderCheckForm(w, s.interactive.RenderForm(), err.Error()) return } @@ -136,7 +135,7 @@ func (s *Server) handleCheckSubmit(w http.ResponseWriter, r *http.Request) { result.States = s.evaluateRules(r.Context(), obs, opts, nil) } - ctx := checker.NewReportContext(raw, related, result.States) + ctx := checker.NewReportContext(raw, related) if reporter, ok := s.provider.(checker.CheckerHTMLReporter); ok { html, rerr := reporter.GetHTMLReport(ctx) diff --git a/checker/server/interactive_test.go b/checker/server/interactive_test.go index 4ce7ade..bc1d338 100644 --- a/checker/server/interactive_test.go +++ b/checker/server/interactive_test.go @@ -355,65 +355,6 @@ func TestCheck_Submit_RunsSiblingAndExposesRelated(t *testing.T) { } } -// interactiveStatesPeekingProvider implements Interactive + HTMLReporter -// and captures the ReportContext.States() seen at GetHTMLReport time. -type interactiveStatesPeekingProvider struct { - key checker.ObservationKey - def *checker.CheckerDefinition - seen *[]checker.CheckState -} - -func (p *interactiveStatesPeekingProvider) Key() checker.ObservationKey { return p.key } -func (p *interactiveStatesPeekingProvider) Collect(ctx context.Context, opts checker.CheckerOptions) (any, error) { - return map[string]string{"ok": "1"}, nil -} -func (p *interactiveStatesPeekingProvider) Definition() *checker.CheckerDefinition { return p.def } -func (p *interactiveStatesPeekingProvider) RenderForm() []checker.CheckerOptionField { - return []checker.CheckerOptionField{{Id: "domain", Type: "string"}} -} -func (p *interactiveStatesPeekingProvider) ParseForm(r *http.Request) (checker.CheckerOptions, error) { - return checker.CheckerOptions{"domain": r.FormValue("domain")}, nil -} -func (p *interactiveStatesPeekingProvider) GetHTMLReport(ctx checker.ReportContext) (string, error) { - if p.seen != nil { - *p.seen = ctx.States() - } - return "

ok

", nil -} - -// TestCheck_Submit_ThreadsStatesIntoReport verifies that CheckStates -// produced by evaluateRules during POST /check are threaded into the -// ReportContext handed to GetHTMLReport. Without this wiring, the /check -// UI can show states in its own section but the embedded report would -// have to re-derive severity/hints from Data. -func TestCheck_Submit_ThreadsStatesIntoReport(t *testing.T) { - var seen []checker.CheckState - p := &interactiveStatesPeekingProvider{ - key: "test", - def: &checker.CheckerDefinition{ - ID: "test", - Rules: []checker.CheckRule{&dummyRule{name: "rule1", desc: "first"}}, - }, - seen: &seen, - } - srv := New(p) - defer srv.Close() - - rec := postForm(srv.Handler(), "/check", url.Values{"domain": {"example.com"}}) - if rec.Code != http.StatusOK { - t.Fatalf("POST /check = %d, want 200", rec.Code) - } - if len(seen) != 1 { - t.Fatalf("reporter saw %d states, want 1", len(seen)) - } - if seen[0].RuleName != "rule1" { - t.Errorf("state RuleName = %q, want %q", seen[0].RuleName, "rule1") - } - if seen[0].Status != checker.StatusOK { - t.Errorf("state Status = %v, want %v", seen[0].Status, checker.StatusOK) - } -} - func TestCheck_Submit_NoSibling_LeavesRelatedEmpty(t *testing.T) { p := &interactiveProvider{ testProvider: &testProvider{ diff --git a/checker/server/server.go b/checker/server/server.go index 763a600..d875dc2 100644 --- a/checker/server/server.go +++ b/checker/server/server.go @@ -26,13 +26,10 @@ import ( "log" "math" "net/http" - "os" - "os/signal" "runtime" "strings" "sync" "sync/atomic" - "syscall" "time" "git.happydns.org/checker-sdk-go/checker" @@ -46,10 +43,6 @@ const maxRequestBodySize = 1 << 20 // 5 seconds matches the Unix kernel's loadavg cadence. const loadSampleInterval = 5 * time.Second -// shutdownTimeout bounds how long ListenAndServe waits for in-flight -// requests to drain after receiving SIGINT or SIGTERM. -const shutdownTimeout = 10 * time.Second - // EWMA smoothing factors for 1, 5, and 15-minute windows sampled every // loadSampleInterval. Derived as 1 - exp(-interval/window) so that the // steady-state response to a constant InFlight of N converges to N. @@ -172,65 +165,14 @@ func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *h s.mux.HandleFunc(pattern, handler) } -// ListenAndServe starts the HTTP server on the given address and blocks -// until the server stops. +// ListenAndServe starts the HTTP server on the given address. // -// ListenAndServe installs a SIGINT/SIGTERM handler that triggers a graceful -// shutdown: new connections are refused and in-flight requests are given up -// to shutdownTimeout to complete. The background load-average sampler is -// stopped via Close before returning. Callers who need their own signal -// handling or shutdown semantics should use Handler() and run their own -// http.Server instead. -// -// If the consumer's flag.Parse() set the SDK-registered -healthcheck flag, -// ListenAndServe never starts the server: it probes /health on addr and calls -// os.Exit(0) on success or os.Exit(1) on failure. This is what lets a -// scratch-based Docker image use the binary itself as its HEALTHCHECK probe. +// ListenAndServe does not stop the background load-average sampler on return; +// call Close to stop it. This is not required for process-scoped usage but is +// recommended for tests and embedded lifecycles. func (s *Server) ListenAndServe(addr string) error { - if *healthcheckMode { - if err := runHealthcheck(addr); err != nil { - fmt.Fprintln(os.Stderr, "healthcheck failed:", err) - os.Exit(1) - } - os.Exit(0) - } - - srv := &http.Server{Addr: addr, Handler: requestLogger(s.mux)} - - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - defer signal.Stop(sigCh) - - shutdownErr := make(chan error, 1) - go func() { - sig, ok := <-sigCh - if !ok { - shutdownErr <- nil - return - } - log.Printf("checker received %s, shutting down (timeout %s)", sig, shutdownTimeout) - ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) - defer cancel() - shutdownErr <- srv.Shutdown(ctx) - }() - log.Printf("checker listening on %s", addr) - err := srv.ListenAndServe() - signal.Stop(sigCh) - close(sigCh) - - if err == http.ErrServerClosed { - if sErr := <-shutdownErr; sErr != nil { - err = sErr - } else { - err = nil - } - } - - if cErr := s.Close(); cErr != nil && err == nil { - err = cErr - } - return err + return http.ListenAndServe(addr, requestLogger(s.mux)) } // Close stops the background load-average sampler goroutine. It is safe to @@ -415,7 +357,7 @@ func (s *Server) handleReport(w http.ResponseWriter, r *http.Request) { return } - html, err := reporter.GetHTMLReport(checker.NewReportContext(req.Data, req.Related, req.States)) + html, err := reporter.GetHTMLReport(checker.NewReportContext(req.Data, req.Related)) if err != nil { http.Error(w, fmt.Sprintf("failed to generate HTML report: %v", err), http.StatusInternalServerError) return @@ -433,7 +375,7 @@ func (s *Server) handleReport(w http.ResponseWriter, r *http.Request) { return } - metrics, err := reporter.ExtractMetrics(checker.NewReportContext(req.Data, req.Related, req.States), time.Now()) + metrics, err := reporter.ExtractMetrics(checker.NewReportContext(req.Data, req.Related), time.Now()) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": fmt.Sprintf("failed to extract metrics: %v", err), diff --git a/checker/server/server_test.go b/checker/server/server_test.go index 9e0adc4..03aa58f 100644 --- a/checker/server/server_test.go +++ b/checker/server/server_test.go @@ -542,107 +542,6 @@ func (p *relatedPeekingProvider) GetHTMLReport(ctx checker.ReportContext) (strin return "

ok

", nil } -// statesPeekingProvider captures the ReportContext's States slice at -// GetHTMLReport / ExtractMetrics time. -type statesPeekingProvider struct { - base *testProvider - htmlSeen *[]checker.CheckState - metricSeen *[]checker.CheckState -} - -func (p *statesPeekingProvider) Key() checker.ObservationKey { return p.base.Key() } -func (p *statesPeekingProvider) Collect(ctx context.Context, opts checker.CheckerOptions) (any, error) { - return p.base.Collect(ctx, opts) -} -func (p *statesPeekingProvider) Definition() *checker.CheckerDefinition { return p.base.definition } -func (p *statesPeekingProvider) GetHTMLReport(ctx checker.ReportContext) (string, error) { - if p.htmlSeen != nil { - *p.htmlSeen = ctx.States() - } - return "

ok

", nil -} -func (p *statesPeekingProvider) ExtractMetrics(ctx checker.ReportContext, t time.Time) ([]checker.CheckMetric, error) { - if p.metricSeen != nil { - *p.metricSeen = ctx.States() - } - return []checker.CheckMetric{{Name: "m1", Value: 1.0, Timestamp: t}}, nil -} - -// TestServer_Report_States_HTML verifies ExternalReportRequest.States is -// threaded into the ReportContext seen by the HTML reporter. -func TestServer_Report_States_HTML(t *testing.T) { - var seen []checker.CheckState - base := &testProvider{ - key: "test", - definition: &checker.CheckerDefinition{ID: "test-checker", Rules: []checker.CheckRule{}}, - } - srv := New(&statesPeekingProvider{base: base, htmlSeen: &seen}) - defer srv.Close() - - states := []checker.CheckState{ - {Status: checker.StatusCrit, Message: "broken", RuleName: "r1", Code: "bad", Subject: "host.example"}, - } - req := checker.ExternalReportRequest{ - Key: "test", - Data: json.RawMessage(`{}`), - States: states, - } - rec := doRequest(srv.Handler(), "POST", "/report", req, map[string]string{"Accept": "text/html"}) - if rec.Code != http.StatusOK { - t.Fatalf("POST /report = %d, want 200", rec.Code) - } - if len(seen) != 1 || seen[0].RuleName != "r1" || seen[0].Code != "bad" || seen[0].Subject != "host.example" { - t.Errorf("reporter saw states = %+v, want single state {RuleName:r1, Code:bad, Subject:host.example}", seen) - } -} - -// TestServer_Report_States_Metrics verifies the States passthrough on the -// metrics path as well. -func TestServer_Report_States_Metrics(t *testing.T) { - var seen []checker.CheckState - base := &testProvider{ - key: "test", - definition: &checker.CheckerDefinition{ID: "test-checker", Rules: []checker.CheckRule{}}, - } - srv := New(&statesPeekingProvider{base: base, metricSeen: &seen}) - defer srv.Close() - - req := checker.ExternalReportRequest{ - Key: "test", - Data: json.RawMessage(`{}`), - States: []checker.CheckState{{Status: checker.StatusWarn, RuleName: "r1"}}, - } - rec := doRequest(srv.Handler(), "POST", "/report", req, map[string]string{"Accept": "application/json"}) - if rec.Code != http.StatusOK { - t.Fatalf("POST /report = %d, want 200", rec.Code) - } - if len(seen) != 1 || seen[0].RuleName != "r1" { - t.Errorf("reporter saw states = %+v, want single state with RuleName=r1", seen) - } -} - -// TestServer_Report_States_Absent verifies that omitting States in the -// request yields a nil States() slice on the reporter side (graceful -// degradation for hosts that don't thread evaluate→report yet). -func TestServer_Report_States_Absent(t *testing.T) { - seen := []checker.CheckState{{Status: checker.StatusOK}} // non-nil sentinel - base := &testProvider{ - key: "test", - definition: &checker.CheckerDefinition{ID: "test-checker", Rules: []checker.CheckRule{}}, - } - srv := New(&statesPeekingProvider{base: base, htmlSeen: &seen}) - defer srv.Close() - - req := checker.ExternalReportRequest{Key: "test", Data: json.RawMessage(`{}`)} - rec := doRequest(srv.Handler(), "POST", "/report", req, map[string]string{"Accept": "text/html"}) - if rec.Code != http.StatusOK { - t.Fatalf("POST /report = %d, want 200", rec.Code) - } - if seen != nil { - t.Errorf("States() = %+v, want nil when ExternalReportRequest.States is absent", seen) - } -} - func TestServer_Report_BadBody(t *testing.T) { p := &testProvider{ key: "test", diff --git a/checker/types.go b/checker/types.go index 7256766..c218dbb 100644 --- a/checker/types.go +++ b/checker/types.go @@ -298,40 +298,32 @@ type CheckAggregator interface { Aggregate(states []CheckState) CheckState } -// ReportContext carries the primary observation payload, any observations -// produced by other checkers that cover the same discovery entries, and the -// CheckStates produced by this checker's rules for the same observation. -// Hosts build a ReportContext and hand it to reporter methods. +// ReportContext carries both the primary observation payload and any +// observations produced by other checkers that cover the same discovery +// entries. Hosts build a ReportContext and hand it to reporter methods. // -// Reporters use States() to render rule-driven sections (for example a -// "fix these first" list) without re-deriving severity or hints from the -// raw payload. Hosts that have not yet threaded rule output into the -// report pipeline return nil; reporters must treat a nil or empty slice -// as "not provided" and fall back to a data-only rendering. The same -// nil-tolerance applies to Related(key). +// The method set is deliberately tiny: a single primary payload (Data) and +// a query for related observations by key (Related). Hosts return nil from +// Related when there is nothing to relate; reporters must tolerate that. type ReportContext interface { Data() json.RawMessage Related(key ObservationKey) []RelatedObservation - States() []CheckState } -// NewReportContext returns a ReportContext backed by a primary payload, a -// pre-resolved map of related observations by key, and the CheckStates -// produced by the checker's rules on this observation. The SDK's /report -// HTTP handler uses this to wrap ExternalReportRequest contents; hosts and -// tests can use it whenever they already have that material in memory. +// NewReportContext returns a ReportContext backed by a primary payload and +// a pre-resolved map of related observations by key. The SDK's /report HTTP +// handler uses this to wrap ExternalReportRequest contents; hosts and tests +// can use it whenever they already have the related observations in memory. // -// Passing a nil related map or a nil states slice is fine; Related(key) -// and States() will then return nil respectively. Use StaticReportContext -// as a shorthand when both are absent. -func NewReportContext(data json.RawMessage, related map[ObservationKey][]RelatedObservation, states []CheckState) ReportContext { - return fixedReportContext{data: data, related: related, states: states} +// Passing a nil or empty related map is fine; Related(key) will then return +// nil, just like StaticReportContext. +func NewReportContext(data json.RawMessage, related map[ObservationKey][]RelatedObservation) ReportContext { + return fixedReportContext{data: data, related: related} } -// StaticReportContext is a shorthand for NewReportContext(data, nil, nil): -// a ReportContext with a primary payload, no related observations, and no -// rule states. Intended for tests and ad-hoc callers that have no lineage -// or rule output to supply. +// StaticReportContext is a shorthand for NewReportContext(data, nil): a +// ReportContext with a primary payload and no related observations. +// Intended for tests and ad-hoc callers that have no lineage to supply. func StaticReportContext(data json.RawMessage) ReportContext { return fixedReportContext{data: data} } @@ -339,7 +331,6 @@ func StaticReportContext(data json.RawMessage) ReportContext { type fixedReportContext struct { data json.RawMessage related map[ObservationKey][]RelatedObservation - states []CheckState } func (f fixedReportContext) Data() json.RawMessage { return f.data } @@ -349,7 +340,6 @@ func (f fixedReportContext) Related(key ObservationKey) []RelatedObservation { } return f.related[key] } -func (f fixedReportContext) States() []CheckState { return f.states } // CheckerHTMLReporter is an optional interface that observation providers can // implement to render their stored data as a full HTML document (for iframe embedding). @@ -496,20 +486,13 @@ type ExternalEvaluateResponse struct { // Related carries observations produced by other checkers on DiscoveryEntry // records originally published by the target of this report, that is, the // cross-checker lineage that ObservationGetter.GetRelated would expose in -// the in-process path. States carries the CheckStates the host produced by -// evaluating this checker's rules against the same observation, letting -// reporters render rule-driven sections (for example a "fix these first" -// list) without re-deriving severity or hints from Data. -// -// The host composes both fields before making the HTTP request. When both -// are absent, the remote checker receives a context equivalent to -// StaticReportContext (no related observations and no states); the -// reporter then falls back to a data-only rendering. +// the in-process path. The host composes it before making the HTTP request; +// when absent, the remote checker receives a context that reports no +// related observations (equivalent to StaticReportContext). type ExternalReportRequest struct { Key ObservationKey `json:"key"` Data json.RawMessage `json:"data"` Related map[ObservationKey][]RelatedObservation `json:"related,omitempty"` - States []CheckState `json:"states,omitempty"` } // HealthResponse is returned by GET /health on a remote checker endpoint. diff --git a/checker/types_test.go b/checker/types_test.go index 87022ee..8ba89f2 100644 --- a/checker/types_test.go +++ b/checker/types_test.go @@ -17,7 +17,6 @@ package checker import ( "context" "encoding/json" - "reflect" "testing" ) @@ -157,54 +156,6 @@ func TestCheckerDefinition_BuildRulesInfo(t *testing.T) { } } -// 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"})