package health import ( "context" "errors" "testing" "time" ) // fakeChecker is a hand-built Checker for unit tests. Tests construct Set // directly via &Set{Checks: []Checker{...}} — no steward involvement. type fakeChecker struct { name string err error delay time.Duration } func (f *fakeChecker) Name() string { return f.name } func (f *fakeChecker) Check(ctx context.Context) error { if f.delay > 0 { select { case <-time.After(f.delay): case <-ctx.Done(): return ctx.Err() } } return f.err } func TestSetRunAllOK(t *testing.T) { s := &Set{Checks: []Checker{ &fakeChecker{name: "a"}, &fakeChecker{name: "b"}, }} results, allOK := s.Run(context.Background()) if !allOK { t.Fatalf("allOK = false; want true") } if len(results) != 2 { t.Fatalf("len(results) = %d; want 2", len(results)) } for name, err := range results { if err != nil { t.Errorf("results[%q] = %v; want nil", name, err) } } } func TestSetRunAggregatesFailures(t *testing.T) { boom := errors.New("boom") s := &Set{Checks: []Checker{ &fakeChecker{name: "a"}, &fakeChecker{name: "b", err: boom}, &fakeChecker{name: "c"}, }} results, allOK := s.Run(context.Background()) if allOK { t.Fatalf("allOK = true; want false (one check failed)") } if got := results["a"]; got != nil { t.Errorf("results[a] = %v; want nil", got) } if got := results["b"]; !errors.Is(got, boom) { t.Errorf("results[b] = %v; want errors.Is boom", got) } if got := results["c"]; got != nil { t.Errorf("results[c] = %v; want nil", got) } } func TestSetRunEmptyChecksReturnsAllOK(t *testing.T) { s := &Set{} results, allOK := s.Run(context.Background()) if !allOK { t.Fatalf("allOK = false; want true (empty Checks is intentional)") } if len(results) != 0 { t.Fatalf("len(results) = %d; want 0", len(results)) } } func TestSetRunEnforcesPerCheckTimeout(t *testing.T) { // fakeChecker with a delay much larger than the 2s per-check budget. // Use 10s delay; if the timeout works the test returns in ~2s. s := &Set{Checks: []Checker{ &fakeChecker{name: "slow", delay: 10 * time.Second}, }} start := time.Now() results, allOK := s.Run(context.Background()) elapsed := time.Since(start) // The 2s per-check timeout must fire. Allow generous slack for CI but // firmly less than the 10s delay we set. if elapsed >= 5*time.Second { t.Fatalf("Run took %s; per-check timeout did not fire", elapsed) } if allOK { t.Fatalf("allOK = true; want false (timeout should surface as error)") } got := results["slow"] if got == nil { t.Fatalf("results[slow] = nil; want timeout error") } if !errors.Is(got, context.DeadlineExceeded) { t.Errorf("results[slow] = %v; want errors.Is(context.DeadlineExceeded)", got) } }