@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 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 shouldReturnSuccess() {
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

    // When
    Supplier<String> checkedSupplier =
        CircuitBreaker.decorateSupplier(() -> "Hello world", circuitBreaker);

    // Then
    assertThat(checkedSupplier.get()).isEqualTo("Hello world");
  }
  @Test
  public void shouldReturnFailureWithRuntimeException() {
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

    // 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(RuntimeException.class);
  }
  @Test
  public void shouldInvokeAsyncApply() throws ExecutionException, InterruptedException {
    // tag::shouldInvokeAsyncApply[]
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

    // When
    Supplier<String> decoratedSupplier =
        CircuitBreaker.decorateSupplier(
            () -> "This can be any method which returns: 'Hello", circuitBreaker);

    CompletableFuture<String> future =
        CompletableFuture.supplyAsync(decoratedSupplier).thenApply(value -> value + " world'");

    // Then
    assertThat(future.get()).isEqualTo("This can be any method which returns: 'Hello world'");
    // end::shouldInvokeAsyncApply[]
  }
  @Test
  public void shouldInvokeMap() {
    // tag::shouldInvokeMap[]
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");

    // When I decorate my function
    Try.CheckedSupplier<String> decoratedSupplier =
        CircuitBreaker.decorateCheckedSupplier(
            () -> "This can be any method which returns: 'Hello", circuitBreaker);

    // and chain an other function with map
    Try<String> result = Try.of(decoratedSupplier).map(value -> value + " world'");

    // Then the Try Monad returns a Success<String>, if all functions ran successfully.
    assertThat(result.isSuccess()).isTrue();
    assertThat(result.get()).isEqualTo("This can be any method which returns: 'Hello world'");
    // end::shouldInvokeMap[]
  }
  @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
  public void shouldInvokeRecoverFunction() {
    // tag::shouldInvokeRecoverFunction[]
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");

    // When I decorate my function and invoke the decorated function
    Try.CheckedSupplier<String> checkedSupplier =
        CircuitBreaker.decorateCheckedSupplier(
            () -> {
              throw new RuntimeException("BAM!");
            },
            circuitBreaker);
    Try<String> result = Try.of(checkedSupplier).recover(throwable -> "Hello Recovery");

    // Then the function should be a success, because the exception could be recovered
    assertThat(result.isSuccess()).isTrue();
    // and the result must match the result of the recovery function.
    assertThat(result.get()).isEqualTo("Hello Recovery");
    // end::shouldInvokeRecoverFunction[]
  }
  @Test
  public void shouldChainDecoratedFunctions() throws ExecutionException, InterruptedException {
    // tag::shouldChainDecoratedFunctions[]
    // Given
    CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
    CircuitBreaker anotherCircuitBreaker = circuitBreakerRegistry.circuitBreaker("anotherTestName");

    // When I create a Supplier and a Function which are decorated by different CircuitBreakers
    Try.CheckedSupplier<String> decoratedSupplier =
        CircuitBreaker.decorateCheckedSupplier(() -> "Hello", circuitBreaker);

    Try.CheckedFunction<String, String> decoratedFunction =
        CircuitBreaker.decorateCheckedFunction((input) -> input + " world", anotherCircuitBreaker);

    // and I chain a function with map
    Try<String> result = Try.of(decoratedSupplier).mapTry(decoratedFunction::apply);

    // Then
    assertThat(result.isSuccess()).isTrue();
    assertThat(result.get()).isEqualTo("Hello world");
    // end::shouldChainDecoratedFunctions[]
  }