@Test
  public void shouldReturnFailureWithCircuitBreakerOpenException() {
    // Given
    // Create a custom configuration for a CircuitBreaker
    CircuitBreakerConfig circuitBreakerConfig =
        CircuitBreakerConfig.custom()
            .ringBufferSizeInClosedState(2)
            .ringBufferSizeInHalfOpenState(2)
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .build();

    // Create a CircuitBreakerRegistry with a custom global configuration
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");

    circuitBreaker.recordFailure(new RuntimeException());
    circuitBreaker.recordFailure(new RuntimeException());
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);

    // When
    Try.CheckedRunnable checkedRunnable =
        CircuitBreaker.decorateCheckedRunnable(
            () -> {
              throw new RuntimeException("BAM!");
            },
            circuitBreaker);
    Try result = Try.run(checkedRunnable);

    // Then
    assertThat(result.isFailure()).isTrue();
    assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class);
  }
  @Test
  public void shouldThrowCircuitBreakerOpenException() {
    // tag::shouldThrowCircuitBreakerOpenException[]
    // Given
    CircuitBreakerConfig circuitBreakerConfig =
        CircuitBreakerConfig.custom()
            .ringBufferSizeInClosedState(2)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .build();
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);

    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("uniqueName");

    // Simulate a failure attempt
    circuitBreaker.recordFailure(new RuntimeException());
    // CircuitBreaker is still CLOSED, because 1 failure is allowed
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
    // Simulate a failure attempt
    circuitBreaker.recordFailure(new RuntimeException());
    // CircuitBreaker is OPEN, because the failure rate is above 50%
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);

    // When I decorate my function and invoke the decorated function
    Try<String> result =
        Try.of(CircuitBreaker.decorateCheckedSupplier(() -> "Hello", circuitBreaker))
            .map(value -> value + " world");

    // Then the call fails, because CircuitBreaker is OPEN
    assertThat(result.isFailure()).isTrue();
    // Exception is CircuitBreakerOpenException
    assertThat(result.failed().get()).isInstanceOf(CircuitBreakerOpenException.class);
    // end::shouldThrowCircuitBreakerOpenException[]
  }
  @Test
  public void shouldNotRecordIOExceptionAsAFailure() {
    // tag::shouldNotRecordIOExceptionAsAFailure[]
    // Given
    CircuitBreakerConfig circuitBreakerConfig =
        CircuitBreakerConfig.custom()
            .ringBufferSizeInClosedState(2)
            .ringBufferSizeInHalfOpenState(2)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .recordFailure(
                throwable ->
                    API.Match(throwable)
                        .of(Case(instanceOf(WebServiceException.class), true), Case($(), false)))
            .build();
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");

    // Simulate a failure attempt
    circuitBreaker.recordFailure(new RuntimeException());
    // CircuitBreaker is still CLOSED, because 1 failure is allowed
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

    // When
    Try.CheckedRunnable checkedRunnable =
        CircuitBreaker.decorateCheckedRunnable(
            () -> {
              throw new SocketTimeoutException("BAM!");
            },
            circuitBreaker);
    Try result = Try.run(checkedRunnable);

    // Then
    assertThat(result.isFailure()).isTrue();
    // CircuitBreaker is still CLOSED, because SocketTimeoutException has not been recorded as a
    // failure
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
    assertThat(result.failed().get()).isInstanceOf(IOException.class);
    // end::shouldNotRecordIOExceptionAsAFailure[]
  }
 @Test(expected = IllegalArgumentException.class)
 public void zeroWaitIntervalShouldFail() {
   CircuitBreakerConfig.custom().waitInterval(0).build();
 }
 @Test(expected = IllegalArgumentException.class)
 public void zeroMaxFailuresShouldFail() {
   CircuitBreakerConfig.custom().maxFailures(0).build();
 }