package extsvc import ( "errors" "testing" "time" ) func TestBreakerLifecycle(t *testing.T) { b := NewBreaker() now := time.Date(2026, 6, 12, 12, 0, 0, 0, time.UTC) b.now = func() time.Time { return now } boom := errors.New("boom") if b.State() != "closed" || !b.Allow() { t.Fatal("fresh breaker must be closed") } // two failures: still closed b.Record(boom) b.Record(boom) if b.State() != "closed" || !b.Allow() { t.Fatal("breaker must stay closed below threshold") } // third consecutive failure opens it b.Record(boom) if b.State() != "open" { t.Fatalf("state = %s, want open", b.State()) } if b.Allow() { t.Fatal("open breaker must reject calls") } // after the cooldown one probe is allowed (half-open) now = now.Add(61 * time.Second) if !b.Allow() { t.Fatal("half-open must allow one probe") } if b.Allow() { t.Fatal("only one probe may be in flight") } if b.State() != "half-open" { t.Fatalf("state = %s, want half-open", b.State()) } // failed probe re-opens for another cooldown b.Record(boom) if b.Allow() { t.Fatal("failed probe must re-open the breaker") } now = now.Add(61 * time.Second) if !b.Allow() { t.Fatal("next probe window") } // successful probe closes it fully b.Record(nil) if b.State() != "closed" || !b.Allow() || !b.Allow() { t.Fatal("successful probe must close the breaker") } } func TestBreakerSuccessResetsCount(t *testing.T) { b := NewBreaker() boom := errors.New("x") b.Record(boom) b.Record(boom) b.Record(nil) // non-consecutive failures never open b.Record(boom) b.Record(boom) if b.State() != "closed" { t.Fatal("interleaved success must reset the failure count") } }