package lbhealth import ( "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" ) var t0 = time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC) // healthy is a state with every condition met at t0. func healthy() *State { s := &State{} s.SetHosting(true) s.MapLoaded() s.KeyLoaded() s.CertsLoaded() s.EdgeBeat(t0) s.RefreshAttempted(t0) s.SetActivator(func() time.Time { return t0 }) return s } func TestFailingNamesEachCondition(t *testing.T) { if got := healthy().Failing(t0); len(got) != 0 { t.Fatalf("healthy box fails %v", got) } if got := (&State{}).Failing(t0); !reflect.DeepEqual(got, []string{CondHosting, CondForwarding, CondCerts, CondEdge, CondActivator, CondRefresh}) { t.Fatalf("zero state fails %v", got) } var nilState *State if got := nilState.Failing(t0); len(got) != 6 { t.Fatalf("nil state fails %v", got) } cases := []struct { name string spoil func(*State) now time.Time want string }{ {"joining", func(s *State) { s.SetHosting(false) }, t0, CondHosting}, {"map never loaded", func(s *State) { s.mapLoaded.Store(false) }, t0, CondForwarding}, {"key never loaded", func(s *State) { s.keyLoaded.Store(false) }, t0, CondForwarding}, {"no certificate cache", func(s *State) { s.certs.Store(false) }, t0, CondCerts}, {"edge beat stale", func(s *State) { s.EdgeBeat(t0.Add(-61 * time.Second)) }, t0, CondEdge}, {"activator beat stale", func(s *State) { s.SetActivator(func() time.Time { return t0.Add(-61 * time.Second) }) }, t0, CondActivator}, {"no activator", func(s *State) { s.SetActivator(nil) }, t0, CondActivator}, {"refresh stuck", func(s *State) { s.RefreshAttempted(t0.Add(-5*time.Minute - time.Second)) }, t0, CondRefresh}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { s := healthy() c.spoil(s) if got := s.Failing(c.now); !reflect.DeepEqual(got, []string{c.want}) { t.Fatalf("Failing = %v, want [%s]", got, c.want) } }) } // The windows are inclusive: a beat exactly 60s old and a refresh // exactly 5 minutes old still count. s := healthy() s.EdgeBeat(t0.Add(-HeartbeatWindow)) s.RefreshAttempted(t0.Add(-RefreshWindow)) if got := s.Failing(t0); len(got) != 0 { t.Fatalf("boundary fails %v", got) } } // A bucket outage after arming: refreshes keep failing, but each failed // attempt is still a completed attempt, and the latches hold. The box // stays healthy (A2-3, A3-3). func TestBucketOutageAfterArmingStaysHealthy(t *testing.T) { s := healthy() for i := 1; i <= 20; i++ { now := t0.Add(time.Duration(i) * 30 * time.Second) s.RefreshAttempted(now) // a failed attempt: no latch is re-set s.EdgeBeat(now) s.SetActivator(func() time.Time { return now }) if got := s.Failing(now); len(got) != 0 { t.Fatalf("after %d failed refreshes: %v", i, got) } } } func TestServe(t *testing.T) { get := func(s *State, method, target, host string) (*httptest.ResponseRecorder, bool) { w := httptest.NewRecorder() r := httptest.NewRequest(method, target, nil) r.Host = host return w, Serve(w, r, s, t0) } w, ok := get(healthy(), http.MethodGet, Path, "10.0.1.7") if !ok || w.Code != http.StatusOK || w.Body.String() != "ok\n" || w.Header().Get("Cache-Control") != "no-store" { t.Fatalf("healthy GET: ok=%v %d %q %v", ok, w.Code, w.Body.String(), w.Header()) } w, ok = get(healthy(), http.MethodHead, Path, "any.example.com") if !ok || w.Code != http.StatusOK || w.Body.Len() != 0 { t.Fatalf("healthy HEAD: ok=%v %d %q", ok, w.Code, w.Body.String()) } joining := healthy() joining.SetHosting(false) w, ok = get(joining, http.MethodGet, Path, "10.0.1.7") if !ok || w.Code != http.StatusServiceUnavailable || !strings.Contains(w.Body.String(), CondHosting) { t.Fatalf("joining GET: ok=%v %d %q", ok, w.Code, w.Body.String()) } for _, c := range []struct{ method, target string }{ {http.MethodPost, Path}, {http.MethodGet, Path + "/"}, {http.MethodGet, "/.carlos/lb-healthz"}, {http.MethodGet, "/"}, } { if _, ok := get(healthy(), c.method, c.target, "10.0.1.7"); ok { t.Fatalf("%s %s answered", c.method, c.target) } } } func TestNilStateIgnoresUpdates(t *testing.T) { var s *State s.SetHosting(true) s.MapLoaded() s.KeyLoaded() s.CertsLoaded() s.EdgeBeat(t0) s.RefreshAttempted(t0) s.SetActivator(func() time.Time { return t0 }) if s.Hosting() { t.Fatal("nil state hosting") } }