diff --git a/src/main/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandler.java b/src/main/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandler.java index 4ec7e4c2b..ba431a95b 100644 --- a/src/main/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandler.java +++ b/src/main/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandler.java @@ -14,6 +14,7 @@ import it.pagopa.transactions.commands.handlers.TransactionRequestUserReceiptHandlerCommon; import it.pagopa.transactions.exceptions.AlreadyProcessedException; import it.pagopa.transactions.exceptions.InvalidRequestException; +import it.pagopa.transactions.exceptions.InvalidStatusException; import it.pagopa.transactions.exceptions.ProcessingErrorException; import it.pagopa.transactions.repositories.TransactionsEventStoreRepository; import it.pagopa.transactions.utils.TransactionsUtils; @@ -106,22 +107,33 @@ public Mono> handle(TransactionAddUserReceiptCommand com ) ) .flatMap( - tx -> Mono.error( - new AlreadyProcessedException( - tx.getTransactionId(), - tx.getTransactionAuthorizationRequestData().getPspId(), - tx.getTransactionAuthorizationRequestData() - .getPaymentTypeCode(), - tx.getClientId().name(), - transactionsUtils.isWalletPayment(tx).orElseThrow(), - new UpdateTransactionStatusTracerUtils.GatewayOutcomeResult( - command.getData().addUserReceiptRequest() - .getOutcome() - .getValue(), - Optional.empty() + tx -> { + if (tx.getStatus() == TransactionStatusDto.CLOSURE_REQUESTED) { + return Mono.error( + new InvalidStatusException( + "Error processing closure update request: the transaction is in the state " + + tx.getStatus() ) - ) - ) + ); + } + return Mono.error( + new AlreadyProcessedException( + tx.getTransactionId(), + tx.getTransactionAuthorizationRequestData() + .getPspId(), + tx.getTransactionAuthorizationRequestData() + .getPaymentTypeCode(), + tx.getClientId().name(), + transactionsUtils.isWalletPayment(tx).orElseThrow(), + new UpdateTransactionStatusTracerUtils.GatewayOutcomeResult( + command.getData().addUserReceiptRequest() + .getOutcome() + .getValue(), + Optional.empty() + ) + ) + ); + } ) ); return transaction diff --git a/src/main/java/it/pagopa/transactions/exceptions/InvalidStatusException.java b/src/main/java/it/pagopa/transactions/exceptions/InvalidStatusException.java new file mode 100644 index 000000000..df77a2f48 --- /dev/null +++ b/src/main/java/it/pagopa/transactions/exceptions/InvalidStatusException.java @@ -0,0 +1,7 @@ +package it.pagopa.transactions.exceptions; + +public class InvalidStatusException extends RuntimeException { + public InvalidStatusException(String message) { + super(message); + } +} diff --git a/src/test/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandlerTest.java b/src/test/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandlerTest.java index efd1ebd44..275f22404 100644 --- a/src/test/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandlerTest.java +++ b/src/test/java/it/pagopa/transactions/commands/handlers/v2/TransactionRequestUserReceiptHandlerTest.java @@ -22,6 +22,7 @@ import it.pagopa.transactions.commands.data.AddUserReceiptData; import it.pagopa.transactions.exceptions.AlreadyProcessedException; import it.pagopa.transactions.exceptions.InvalidRequestException; +import it.pagopa.transactions.exceptions.InvalidStatusException; import it.pagopa.transactions.exceptions.ProcessingErrorException; import it.pagopa.transactions.repositories.TransactionsEventStoreRepository; import it.pagopa.transactions.utils.TransactionsUtils; @@ -750,6 +751,69 @@ void shouldReturnMonoErrorForErrorSendingEventOnTheQueue() { assertEquals(Duration.ofSeconds(transientQueueEventsTtlSeconds), durationArgumentCaptor.getValue()); } + @Test + void shouldRejectTransactionInInvalidStateClosureRequested() { + TransactionActivatedEvent transactionActivatedEvent = transactionActivateEvent(); + + TransactionAuthorizationRequestedEvent authorizationRequestedEvent = transactionAuthorizationRequestedEvent(); + + TransactionAuthorizationCompletedEvent authorizationCompletedEvent = transactionAuthorizationCompletedEvent( + new NpgTransactionGatewayAuthorizationData( + OperationResultDto.EXECUTED, + "operationId", + "paymentEnd2EndId", + null, + null + ) + ); + + TransactionClosureRequestedEvent transactionClosureRequestedEvent = transactionClosureRequestedEvent(); + + AddUserReceiptRequestDto addUserReceiptRequest = new AddUserReceiptRequestDto() + .outcome(OK) + .paymentDate(OffsetDateTime.now()) + .addPaymentsItem( + new AddUserReceiptRequestPaymentsInnerDto() + .paymentToken("paymentToken") + .companyName("companyName") + .creditorReferenceId("creditorReferenceId") + .description("description") + .debtor("debtor") + .fiscalCode("fiscalCode") + .officeName("officeName") + ); + + TransactionActivated transaction = transactionActivated(ZonedDateTime.now().toString()); + + AddUserReceiptData addUserReceiptData = new AddUserReceiptData( + transaction.getTransactionId(), + addUserReceiptRequest + ); + + TransactionAddUserReceiptCommand requestStatusCommand = new TransactionAddUserReceiptCommand( + transaction.getPaymentNotices().stream().map(PaymentNotice::rptId).toList(), + addUserReceiptData + ); + + Flux> events = ((Flux) Flux + .just( + transactionActivatedEvent, + authorizationRequestedEvent, + authorizationCompletedEvent, + transactionClosureRequestedEvent + )); + + /* preconditions */ + Mockito.when(eventStoreRepository.findByTransactionIdOrderByCreationDateAsc(TRANSACTION_ID)).thenReturn(events); + + /* test */ + StepVerifier.create(updateStatusHandler.handle(requestStatusCommand)) + .expectErrorMatches(error -> error instanceof InvalidStatusException) + .verify(); + + Mockito.verify(userReceiptDataEventRepository, Mockito.times(0)).save(any()); + } + @Test void shouldRejectTransactionInInvalidState() { TransactionActivatedEvent transactionActivatedEvent = transactionActivateEvent(); 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 02cbe4ec2..93d342b4e 100644 --- a/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java +++ b/src/test/java/it/pagopa/transactions/services/v1/CircuitBreakerTest.java @@ -503,6 +503,33 @@ void shouldOpenCircuitBreakerForNotExcludedExceptionPerformingRetry() { } + @Test + @Order(3) + void shouldOpenCircuitBreakerForInvalidStatusExceptionPerformingRetry() { + Retry retry = retryRegistry.retry("addUserReceipt"); + long expectedFailedCallsWithoutRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt(); + long expectedFailedCallsWithRetryAttempt = retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt() + 1; + + /* + * Preconditions + */ + Mockito.when(transactionsViewRepository.findById(any(String.class))) + .thenReturn(Mono.error(new InvalidStatusException("Error processing request"))); + + StepVerifier + .create( + transactionsService.addUserReceipt("", new AddUserReceiptRequestDto()) + ) + .expectError(InvalidStatusException.class) + .verify(); + assertEquals( + expectedFailedCallsWithoutRetryAttempt, + retry.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt() + ); + assertEquals(expectedFailedCallsWithRetryAttempt, retry.getMetrics().getNumberOfFailedCallsWithRetryAttempt()); + + } + private static CtFaultBean faultBeanWithCode(String faultCode) { CtFaultBean fault = new CtFaultBean(); fault.setFaultCode(faultCode);