diff --git a/pom.xml b/pom.xml
index 963df27e0..3f29c80a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -244,6 +244,11 @@
${mock-web-server.version}
test
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ test
+
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8725764ae..4b9358255 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,29 +1,89 @@
resilience4j.retry:
instances:
- getPaymentRequestInfo:
- maxAttempts: 3
- waitDuration: 2s
- enableExponentialBackoff: false
newTransaction:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: false
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
getTransactionInfo:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: false
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
requestTransactionAuthorization:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: false
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
updateTransactionAuthorization:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: false
- activateTransaction:
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
+ cancelTransaction:
+ maxAttempts: 3
+ waitDuration: 2s
+ enableExponentialBackoff: false
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
+ addUserReceipt:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: false
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
resilience4j.circuitbreaker:
configs:
@@ -37,10 +97,38 @@ resilience4j.circuitbreaker:
instances:
transactions-backend:
baseConfig: default
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
node-backend:
baseConfig: default
ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
- it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
ecommerce-db:
baseConfig: default
+ ignoreExceptions:
+ - it.pagopa.transactions.exceptions.UnsatisfiablePspRequestException
+ - it.pagopa.transactions.exceptions.PaymentNoticeAllCCPMismatchException
+ - it.pagopa.transactions.exceptions.TransactionNotFoundException
+ - it.pagopa.transactions.exceptions.AlreadyProcessedException
+ - it.pagopa.transactions.exceptions.NotImplementedException
+ - it.pagopa.transactions.exceptions.InvalidRequestException
+ - it.pagopa.transactions.exceptions.TransactionAmountMismatchException
+ - it.pagopa.transactions.exceptions.NodoErrorException
+ - it.pagopa.transactions.exceptions.InvalidNodoResponseException
diff --git a/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java b/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java
index 350257d21..dd3523be7 100644
--- a/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java
+++ b/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java
@@ -1,28 +1,35 @@
package it.pagopa.transactions.services.v1;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryRegistry;
import it.pagopa.ecommerce.commons.documents.PaymentNotice;
import it.pagopa.ecommerce.commons.documents.PaymentTransferInformation;
import it.pagopa.ecommerce.commons.documents.v1.TransactionActivatedData;
import it.pagopa.ecommerce.commons.documents.v1.TransactionActivatedEvent;
+import it.pagopa.ecommerce.commons.domain.PaymentToken;
import it.pagopa.ecommerce.commons.domain.TransactionId;
import it.pagopa.ecommerce.commons.v1.TransactionTestUtils;
import it.pagopa.generated.transactions.model.CtFaultBean;
-import it.pagopa.generated.transactions.server.model.ClientIdDto;
-import it.pagopa.generated.transactions.server.model.NewTransactionRequestDto;
-import it.pagopa.generated.transactions.server.model.PartyConfigurationFaultDto;
-import it.pagopa.generated.transactions.server.model.PaymentNoticeInfoDto;
+import it.pagopa.generated.transactions.server.model.*;
import it.pagopa.transactions.commands.handlers.v1.TransactionActivateHandler;
-import it.pagopa.transactions.exceptions.InvalidNodoResponseException;
-import it.pagopa.transactions.exceptions.NodoErrorException;
+import it.pagopa.transactions.exceptions.*;
+import it.pagopa.transactions.repositories.TransactionsViewRepository;
+import it.pagopa.transactions.utils.TransactionsUtils;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -31,8 +38,13 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
-import java.util.List;
-import java.util.UUID;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import static it.pagopa.ecommerce.commons.v1.TransactionTestUtils.EMAIL_STRING;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -54,9 +66,307 @@ class CircuitBreakerTest {
@MockBean
private TransactionActivateHandler transactionActivateHandlerV1;
+ @MockBean
+ private TransactionsViewRepository transactionsViewRepository;
+ @MockBean
+ private TransactionsUtils transactionsUtils;
+
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
+ @Autowired
+ private RetryRegistry retryRegistry;
+
+ private static final JsonNode resilience4jConfiguration;
+
+ private static final Map exceptionMapper = Stream.of(
+ new UnsatisfiablePspRequestException(
+ new PaymentToken(""),
+ RequestAuthorizationRequestDto.LanguageEnum.IT,
+ 0
+ ),
+ new PaymentNoticeAllCCPMismatchException("rptId", true, true),
+ new TransactionNotFoundException(""),
+ new AlreadyProcessedException(new TransactionId(TransactionTestUtils.TRANSACTION_ID)),
+ new NotImplementedException(""),
+ new InvalidRequestException(""),
+ new TransactionAmountMismatchException(10, 11),
+ new NodoErrorException(new CtFaultBean()),
+ new InvalidNodoResponseException("")
+ ).collect(Collectors.toMap(exception -> exception.getClass().getCanonicalName(), Function.identity()));
+
+ static {
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ try {
+ resilience4jConfiguration = mapper.readTree(new File("./src/main/resources/application.yml"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Stream getIgnoredExceptionsForRetry(String retryInstanceName) {
+ return StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ resilience4jConfiguration
+ .get("resilience4j.retry")
+ .get("instances")
+ .get(retryInstanceName)
+ .get("ignoreExceptions")
+ .elements(),
+ Spliterator.ORDERED
+ ),
+ false
+ )
+ .map(ignoredException -> {
+ String exceptionName = ignoredException.asText();
+ return Arguments.of(
+ Optional
+ .ofNullable(exceptionMapper.get(exceptionName))
+ .orElseThrow(
+ () -> new RuntimeException(
+ "Missing exception instance in test suite inside map `exceptionMapper` for class: %s"
+ .formatted(exceptionName)
+ )
+ ),
+ retryInstanceName
+ );
+ }
+
+ );
+ }
+
+ private static Stream getIgnoredExceptionForNewTransactionRetry() {
+ return getIgnoredExceptionsForRetry("newTransaction");
+ }
+
+ private static Stream getIgnoredExceptionForGetTransactionInfoRetry() {
+ return getIgnoredExceptionsForRetry("getTransactionInfo");
+ }
+
+ private static Stream getIgnoredExceptionForRequestTransactionAuthorizationRetry() {
+ return getIgnoredExceptionsForRetry("requestTransactionAuthorization");
+ }
+
+ private static Stream getIgnoredExceptionForUpdateTransactionAuthorizationRetry() {
+ return getIgnoredExceptionsForRetry("updateTransactionAuthorization");
+ }
+
+ private static Stream getIgnoredExceptionForCancelTransactionRetry() {
+ return getIgnoredExceptionsForRetry("cancelTransaction");
+ }
+
+ private static Stream getIgnoredExceptionForAddUserReceiptRetry() {
+ return getIgnoredExceptionsForRetry("addUserReceipt");
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForNewTransactionRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_newTransactionRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+ ClientIdDto clientIdDto = ClientIdDto.CHECKOUT;
+ UUID TEST_CCP = UUID.randomUUID();
+ UUID TRANSACTION_ID = UUID.randomUUID();
+
+ NewTransactionRequestDto transactionRequestDto = new NewTransactionRequestDto()
+ .email(EMAIL_STRING)
+ .addPaymentNoticesItem(new PaymentNoticeInfoDto().rptId(TransactionTestUtils.RPT_ID).amount(10));
+
+ TransactionActivatedData transactionActivatedData = new TransactionActivatedData();
+ transactionActivatedData.setEmail(TransactionTestUtils.EMAIL);
+ transactionActivatedData
+ .setPaymentNotices(
+ List.of(
+ new PaymentNotice(
+ TransactionTestUtils.PAYMENT_TOKEN,
+ null,
+ "desc",
+ 0,
+ TEST_CCP.toString(),
+ List.of(new PaymentTransferInformation("77777777777", false, 0, null)),
+ false
+ )
+ )
+ );
+
+ TransactionActivatedEvent transactionActivatedEvent = new TransactionActivatedEvent(
+ new TransactionId(TRANSACTION_ID).value(),
+ transactionActivatedData
+ );
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionActivateHandlerV1.handle(any())).thenReturn(Mono.error(thrownException));
+ StepVerifier
+ .create(
+ transactionsService.newTransaction(
+ transactionRequestDto,
+ clientIdDto,
+ new TransactionId(transactionActivatedEvent.getTransactionId())
+ )
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForGetTransactionInfoRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_getTransactionInfoRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionsViewRepository.findById(any(String.class)))
+ .thenReturn(Mono.error(thrownException));
+
+ StepVerifier
+ .create(
+ transactionsService.getTransactionInfo("transactionId")
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForRequestTransactionAuthorizationRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_requestTransactionAuthorizationRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionsViewRepository.findById(any(String.class)))
+ .thenReturn(Mono.error(thrownException));
+
+ StepVerifier
+ .create(
+ transactionsService.requestTransactionAuthorization(
+ "transactionId",
+ "",
+ new RequestAuthorizationRequestDto()
+ )
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForUpdateTransactionAuthorizationRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_updateTransactionAuthorizationRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionsUtils.reduceEvents(any(), any(), any(), any()))
+ .thenReturn(Mono.error(thrownException));
+
+ StepVerifier
+ .create(
+ transactionsService
+ .updateTransactionAuthorization(UUID.randomUUID(), new UpdateAuthorizationRequestDto())
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForCancelTransactionRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_cancelTransactionRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionsViewRepository.findById(any(String.class))).thenReturn(Mono.error(thrownException));
+
+ StepVerifier
+ .create(
+ transactionsService.cancelTransaction("")
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForAddUserReceiptRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_AddUserReceiptRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionsViewRepository.findById(any(String.class))).thenReturn(Mono.error(thrownException));
+
+ StepVerifier
+ .create(
+ transactionsService.addUserReceipt("", new AddUserReceiptRequestDto())
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
@Test
@Order(1)
void shouldNotOpenCircuitBreakerForNodoErrorException() {
@@ -116,7 +426,11 @@ void shouldNotOpenCircuitBreakerForNodoErrorException() {
@Test
@Order(2)
- void shouldOpenCircuitBreakerForNotExcludedException() {
+ void shouldOpenCircuitBreakerForNotExcludedExceptionPerformingRetry() {
+ Retry retry = retryRegistry.retry("newTransaction");
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt();
+ long expectedFailedCallsWithRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt() + 1;
+
ClientIdDto clientIdDto = ClientIdDto.CHECKOUT;
UUID TEST_CCP = UUID.randomUUID();
UUID TRANSACTION_ID = UUID.randomUUID();
@@ -151,7 +465,7 @@ void shouldOpenCircuitBreakerForNotExcludedException() {
* Preconditions
*/
Mockito.when(transactionActivateHandlerV1.handle(any()))
- .thenReturn(Mono.error(new InvalidNodoResponseException("Invalid response received")));
+ .thenReturn(Mono.error(new RuntimeException("Invalid response received")));
StepVerifier
.create(
@@ -165,6 +479,11 @@ void shouldOpenCircuitBreakerForNotExcludedException() {
.verify();
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("node-backend");
assertEquals(CircuitBreaker.State.OPEN, circuitBreaker.getState());
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ assertEquals(expectedFailedCallsWithRetryAttempt, retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt());
}
diff --git a/src/test/java/it/pagopa/transactions/services/v2/CircuitBreakerTest.java b/src/test/java/it/pagopa/transactions/services/v2/CircuitBreakerTest.java
index b226bd9ca..4c7597f0e 100644
--- a/src/test/java/it/pagopa/transactions/services/v2/CircuitBreakerTest.java
+++ b/src/test/java/it/pagopa/transactions/services/v2/CircuitBreakerTest.java
@@ -1,29 +1,37 @@
package it.pagopa.transactions.services.v2;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryRegistry;
import it.pagopa.ecommerce.commons.documents.PaymentNotice;
import it.pagopa.ecommerce.commons.documents.PaymentTransferInformation;
-import it.pagopa.ecommerce.commons.documents.v1.TransactionActivatedData;
-import it.pagopa.ecommerce.commons.documents.v1.TransactionActivatedEvent;
+import it.pagopa.ecommerce.commons.documents.v2.TransactionActivatedData;
+import it.pagopa.ecommerce.commons.documents.v2.TransactionActivatedEvent;
+import it.pagopa.ecommerce.commons.domain.PaymentToken;
import it.pagopa.ecommerce.commons.domain.TransactionId;
-import it.pagopa.ecommerce.commons.v1.TransactionTestUtils;
+import it.pagopa.ecommerce.commons.v2.TransactionTestUtils;
import it.pagopa.generated.transactions.model.CtFaultBean;
-import it.pagopa.generated.transactions.server.model.ClientIdDto;
-import it.pagopa.generated.transactions.server.model.NewTransactionRequestDto;
-import it.pagopa.generated.transactions.server.model.PartyConfigurationFaultDto;
-import it.pagopa.generated.transactions.server.model.PaymentNoticeInfoDto;
+import it.pagopa.generated.transactions.server.model.RequestAuthorizationRequestDto;
+import it.pagopa.generated.transactions.v2.server.model.ClientIdDto;
+import it.pagopa.generated.transactions.v2.server.model.NewTransactionRequestDto;
+import it.pagopa.generated.transactions.v2.server.model.PartyConfigurationFaultDto;
+import it.pagopa.generated.transactions.v2.server.model.PaymentNoticeInfoDto;
import it.pagopa.transactions.commands.handlers.v2.TransactionActivateHandler;
-import it.pagopa.transactions.exceptions.InvalidNodoResponseException;
-import it.pagopa.transactions.exceptions.NodoErrorException;
-import it.pagopa.transactions.services.v1.TransactionsService;
+import it.pagopa.transactions.exceptions.*;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -32,8 +40,13 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
-import java.util.List;
-import java.util.UUID;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import static it.pagopa.ecommerce.commons.v1.TransactionTestUtils.EMAIL_STRING;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -53,6 +66,132 @@ class CircuitBreakerTest {
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
+ @Autowired
+ private RetryRegistry retryRegistry;
+
+ private static final JsonNode resilience4jConfiguration;
+
+ private static final Map exceptionMapper = Stream.of(
+ new UnsatisfiablePspRequestException(
+ new PaymentToken(""),
+ RequestAuthorizationRequestDto.LanguageEnum.IT,
+ 0
+ ),
+ new PaymentNoticeAllCCPMismatchException("rptId", true, true),
+ new TransactionNotFoundException(""),
+ new AlreadyProcessedException(
+ new TransactionId(it.pagopa.ecommerce.commons.v1.TransactionTestUtils.TRANSACTION_ID)
+ ),
+ new NotImplementedException(""),
+ new InvalidRequestException(""),
+ new TransactionAmountMismatchException(10, 11),
+ new NodoErrorException(new CtFaultBean()),
+ new InvalidNodoResponseException("")
+ ).collect(Collectors.toMap(exception -> exception.getClass().getCanonicalName(), Function.identity()));
+
+ static {
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ try {
+ resilience4jConfiguration = mapper.readTree(new File("./src/main/resources/application.yml"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Stream getIgnoredExceptionsForRetry(String retryInstanceName) {
+ return StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ resilience4jConfiguration
+ .get("resilience4j.retry")
+ .get("instances")
+ .get(retryInstanceName)
+ .get("ignoreExceptions")
+ .elements(),
+ Spliterator.ORDERED
+ ),
+ false
+ )
+ .map(ignoredException -> {
+ String exceptionName = ignoredException.asText();
+ return Arguments.of(
+ Optional
+ .ofNullable(exceptionMapper.get(exceptionName))
+ .orElseThrow(
+ () -> new RuntimeException(
+ "Missing exception instance in test suite inside map `exceptionMapper` for class: %s"
+ )
+ ),
+ retryInstanceName
+ );
+ }
+
+ );
+ }
+
+ private static Stream getIgnoredExceptionForNewTransactionRetry() {
+ return getIgnoredExceptionsForRetry("newTransaction");
+ }
+
+ @ParameterizedTest
+ @MethodSource("getIgnoredExceptionForNewTransactionRetry")
+ @Order(0)
+ void shouldNotPerformRetryForExcludedException_newTransactionRetry(
+ Exception thrownException,
+ String retryInstanceName
+ ) {
+ Retry retry = retryRegistry.retry(retryInstanceName);
+ long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ + 1;
+ ClientIdDto clientIdDto = ClientIdDto.CHECKOUT;
+ UUID TEST_CCP = UUID.randomUUID();
+ UUID TRANSACTION_ID = UUID.randomUUID();
+
+ NewTransactionRequestDto transactionRequestDto = new NewTransactionRequestDto()
+ .email(EMAIL_STRING)
+ .addPaymentNoticesItem(new PaymentNoticeInfoDto().rptId(TransactionTestUtils.RPT_ID).amount(10));
+
+ TransactionActivatedData transactionActivatedData = new TransactionActivatedData();
+ transactionActivatedData.setEmail(it.pagopa.ecommerce.commons.v1.TransactionTestUtils.EMAIL);
+ transactionActivatedData
+ .setPaymentNotices(
+ List.of(
+ new PaymentNotice(
+ it.pagopa.ecommerce.commons.v1.TransactionTestUtils.PAYMENT_TOKEN,
+ null,
+ "desc",
+ 0,
+ TEST_CCP.toString(),
+ List.of(new PaymentTransferInformation("77777777777", false, 0, null)),
+ false
+ )
+ )
+ );
+
+ TransactionActivatedEvent transactionActivatedEvent = new TransactionActivatedEvent(
+ new TransactionId(TRANSACTION_ID).value(),
+ transactionActivatedData
+ );
+
+ /*
+ * Preconditions
+ */
+ Mockito.when(transactionActivateHandlerV2.handle(any())).thenReturn(Mono.error(thrownException));
+ StepVerifier
+ .create(
+ transactionsService.newTransaction(
+ transactionRequestDto,
+ clientIdDto,
+ new TransactionId(transactionActivatedEvent.getTransactionId())
+ )
+ )
+ .expectError(thrownException.getClass())
+ .verify();
+ assertEquals(
+ expectedFailedCallsWithoutRetryAttempt,
+ retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt()
+ );
+ }
+
@Test
@Order(1)
void shouldNotOpenCircuitBreakerForNodoErrorException() {
@@ -147,7 +286,7 @@ void shouldOpenCircuitBreakerForNotExcludedException() {
* Preconditions
*/
Mockito.when(transactionActivateHandlerV2.handle(any()))
- .thenReturn(Mono.error(new InvalidNodoResponseException("Invalid response received")));
+ .thenReturn(Mono.error(new RuntimeException("Invalid response received")));
StepVerifier
.create(