summaryrefslogtreecommitdiff
path: root/internal/apicircuit/apicircuit_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/apicircuit/apicircuit_test.go')
-rw-r--r--internal/apicircuit/apicircuit_test.go38
1 files changed, 38 insertions, 0 deletions
diff --git a/internal/apicircuit/apicircuit_test.go b/internal/apicircuit/apicircuit_test.go
index 5f84c14..b6251a7 100644
--- a/internal/apicircuit/apicircuit_test.go
+++ b/internal/apicircuit/apicircuit_test.go
@@ -4,6 +4,8 @@ import (
"context"
"errors"
"testing"
+
+ "github.com/sony/gobreaker"
)
func TestOpenAITTS_Success(t *testing.T) {
@@ -16,6 +18,42 @@ func TestOpenAITTS_Success(t *testing.T) {
}
}
+// TestBreakerOpensAndShortCircuits verifies the core resilience guarantee: after
+// breakerTripAfterConsecutiveFailures failed calls the breaker opens, and further
+// calls are short-circuited with gobreaker.ErrOpenState without invoking fn. This
+// is what prevents a degraded API from causing cascading load.
+func TestBreakerOpensAndShortCircuits(t *testing.T) {
+ t.Parallel()
+
+ // Use a dedicated breaker (not the package singletons) so this test stays
+ // isolated from the others while still exercising the shared trip policy.
+ cb := newBreaker("test-trip")
+ apiErr := errors.New("upstream failure")
+
+ // Drive enough consecutive failures to trip the breaker open.
+ for i := uint32(0); i < breakerTripAfterConsecutiveFailures; i++ {
+ _, err := runValue(cb, func() (string, error) {
+ return "", apiErr
+ })
+ if !errors.Is(err, apiErr) {
+ t.Fatalf("call %d: got %v, want upstream failure", i, err)
+ }
+ }
+
+ // The breaker must now be open and short-circuit without calling fn.
+ called := false
+ _, err := runValue(cb, func() (string, error) {
+ called = true
+ return "ok", nil
+ })
+ if !errors.Is(err, gobreaker.ErrOpenState) {
+ t.Fatalf("expected ErrOpenState once breaker is open, got %v", err)
+ }
+ if called {
+ t.Fatal("open breaker must short-circuit and not invoke fn")
+ }
+}
+
func TestIsSuccessful_ContextCanceled(t *testing.T) {
t.Parallel()
if !isSuccessful(context.Canceled) {