From 976d986067b27425d220a07001a64a8231c7ae64 Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 15 Dec 2025 19:13:39 +0100 Subject: [PATCH 01/38] draft --- helm/values-dev.yaml | 2 + helm/values-prod.yaml | 2 + helm/values-uat.yaml | 2 + pom.xml | 2 +- .../client/BizEventCosmosClient.java | 11 ++ .../datastore/client/ReceiptCosmosClient.java | 35 ++++ .../client/impl/BizEventCosmosClientImpl.java | 19 +++ .../client/impl/ReceiptCosmosClientImpl.java | 138 +++++++++++++++ .../datastore/entity/receipt/IOMessage.java | 17 ++ .../entity/receipt/ReceiptError.java | 19 +++ .../exception/IoMessageNotFoundException.java | 26 +++ .../datastore/helpdesk/ReceiptToReviewed.java | 104 ++++++++++++ .../helpdesk/RecoverFailedReceipt.java | 123 ++++++++++++++ .../helpdesk/RecoverFailedReceiptMassive.java | 143 ++++++++++++++++ .../RecoverFailedReceiptScheduled.java | 106 ++++++++++++ .../helpdesk/RecoverNotNotifiedReceipt.java | 113 +++++++++++++ .../RecoverNotNotifiedReceiptMassive.java | 116 +++++++++++++ .../RecoverNotNotifiedReceiptScheduled.java | 84 +++++++++ .../pdf/datastore/model/ProblemJson.java | 32 ++++ .../service/BizEventToReceiptService.java | 8 + .../service/ReceiptCosmosService.java | 71 ++++++++ .../impl/BizEventToReceiptServiceImpl.java | 56 ++++++ .../impl/ReceiptCosmosServiceImpl.java | 136 +++++++++++++++ .../utils/BizEventToReceiptUtils.java | 160 ++++++++++++++++++ 24 files changed, 1524 insertions(+), 1 deletion(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/ReceiptError.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/ProblemJson.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 318c69b3..3d6d52fa 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -92,11 +92,13 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-d-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-d-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-d-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" + LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.uat.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index feccab7c..42df86fc 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -92,11 +92,13 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-p-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-p-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-p-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" + LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 138dc255..21bf48e4 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -92,11 +92,13 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-u-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-u-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-u-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" + LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.uat.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" diff --git a/pom.xml b/pom.xml index aae4a77b..79e351d8 100644 --- a/pom.xml +++ b/pom.xml @@ -260,7 +260,7 @@ westus windows - 11 + 17 diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java index 523b55a3..7f5eca82 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java @@ -1,6 +1,7 @@ package it.gov.pagopa.receipt.pdf.datastore.client; +import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; @@ -14,4 +15,14 @@ public interface BizEventCosmosClient { * This method retrieves a BizEvent document from Cosmos DB using the provided bizEventId. */ BizEvent getBizEventDocument(String bizEventId) throws BizEventNotFoundException; + + /** + * Retrieve all biz-event documents related to a specific cart from CosmosDB database + * + * @param transactionId id that identifies the cart + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return a list of biz-event document + */ + Iterable> getAllBizEventDocument(String transactionId, String continuationToken, Integer pageSize); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java index 7f8c8ab6..315db715 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java @@ -2,7 +2,11 @@ import com.azure.cosmos.models.CosmosItemResponse; import com.azure.cosmos.models.FeedResponse; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; public interface ReceiptCosmosClient { @@ -20,4 +24,35 @@ public interface ReceiptCosmosClient { CosmosItemResponse saveReceipts(Receipt receipt); + ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException; + + /** + * Retrieve the not notified receipt documents with {@link ReceiptStatusType#GENERATED} + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return receipt documents + */ + Iterable> getGeneratedReceiptDocuments(String continuationToken, Integer pageSize); + + /** + * Retrieve the receipt not notified documents with {@link ReceiptStatusType#IO_ERROR_TO_NOTIFY} + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return receipt documents + */ + Iterable> getIOErrorToNotifyReceiptDocuments(String continuationToken, Integer pageSize); + + /** + * Retrieve the failed receipt documents with {@link ReceiptStatusType#INSERTED} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return receipt documents + */ + Iterable> getInsertedReceiptDocuments(String continuationToken, Integer pageSize); + + IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException; + } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java index be5f17e4..11d6fc8c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java @@ -5,6 +5,7 @@ import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; import com.azure.cosmos.models.CosmosQueryRequestOptions; +import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; @@ -65,4 +66,22 @@ public BizEvent getBizEventDocument(String bizEventId) throws BizEventNotFoundEx throw new BizEventNotFoundException("Document not found in the defined container"); } } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getAllBizEventDocument(String transactionId, String continuationToken, Integer pageSize) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + + //Build query + String query = String.format("SELECT * FROM c WHERE c.transactionDetails.transaction.transactionId = '%s'", + transactionId); + + //Query the container + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), BizEvent.class) + .iterableByPage(continuationToken, pageSize); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java index 94c07af6..bd280836 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java @@ -9,9 +9,16 @@ import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; + /** * Client for the CosmosDB database */ @@ -21,6 +28,16 @@ public class ReceiptCosmosClientImpl implements ReceiptCosmosClient { private final String databaseId = System.getenv("COSMOS_RECEIPT_DB_NAME"); private final String containerId = System.getenv("COSMOS_RECEIPT_CONTAINER_NAME"); + private final String containerMessageId = System.getenv().getOrDefault("COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME", "receipts-io-messages-evt"); + private final String containerReceiptErrorId = System.getenv().getOrDefault("COSMOS_RECEIPT_ERROR_CONTAINER_NAME", "receipts-message-errors"); + + private static final String DOCUMENT_NOT_FOUND_ERR_MSG = "Document not found in the defined container"; + + // TODO env var + private final String millisDiff = System.getenv("MAX_DATE_DIFF_MILLIS"); + private final String millisNotifyDif = System.getenv("MAX_DATE_DIFF_NOTIFY_MILLIS"); + private final String numDaysRecoverFailed = System.getenv().getOrDefault("RECOVER_FAILED_MASSIVE_MAX_DAYS", "0"); + private final String numDaysRecoverNotNotified = System.getenv().getOrDefault("RECOVER_NOT_NOTIFIED_MASSIVE_MAX_DAYS", "0"); private final CosmosClient cosmosClient; @@ -105,4 +122,125 @@ public CosmosItemResponse saveReceipts(Receipt receipt) { return cosmosContainer.createItem(receipt); } + /** + * Retrieve receiptError document from CosmosDB database + * + * @param bizEventId BizEvent ID + * @return ReceiptError found + * @throws ReceiptNotFoundException If the document isn't found + */ + @Override + public ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerReceiptErrorId); + + //Build query + String query = "SELECT * FROM c WHERE c.bizEventId = " + "'" + bizEventId + "'"; + + //Query the container + CosmosPagedIterable queryResponse = cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), ReceiptError.class); + + if (queryResponse.iterator().hasNext()) { + return queryResponse.iterator().next(); + } + throw new ReceiptNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getGeneratedReceiptDocuments(String continuationToken, Integer pageSize) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + + //Build query + String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.generated_at >= %s AND ( %s - c.generated_at) >= %s)", + ReceiptStatusType.GENERATED, + OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( + Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(), + OffsetDateTime.now().toInstant().toEpochMilli(), millisNotifyDif); + + //Query the container + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) + .iterableByPage(continuationToken,pageSize); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getIOErrorToNotifyReceiptDocuments(String continuationToken, Integer pageSize) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + + //Build query + // String query = String.format("SELECT * FROM c WHERE c.status = '%s' AND c.generated_at >= %s OFFSET 0 LIMIT %s", + // ReceiptStatusType.IO_ERROR_TO_NOTIFY, + // OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( + // Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(), + // recordsLimitRecoverNotNotified + // ); + String query = String.format("SELECT * FROM c WHERE c.status = '%s' AND c.generated_at >= %s", + ReceiptStatusType.IO_ERROR_TO_NOTIFY, + OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( + Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli() + ); + + //Query the container + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) + .iterableByPage(continuationToken,pageSize); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getInsertedReceiptDocuments(String continuationToken, Integer pageSize) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + + //Build query + String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.inserted_at >= %s " + + "AND ( %s - c.inserted_at) >= %s)", + ReceiptStatusType.INSERTED, + OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( + Long.parseLong(numDaysRecoverFailed)).toInstant().toEpochMilli(), + OffsetDateTime.now().toInstant().toEpochMilli(), millisDiff); + + //Query the container + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) + .iterableByPage(continuationToken,pageSize); + } + + /** + * Retrieve receipt document from CosmosDB database + * + * @param messageId IO Message id + * @return io message document + * @throws IoMessageNotFoundException in case no receipt has been found with the given messageId + */ + @Override + public IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerMessageId); + + //Build query + String query = String.format("SELECT * FROM c WHERE c.messageId = '%s'", messageId); + + //Query the container + CosmosPagedIterable queryResponse = cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), IOMessage.class); + + if (queryResponse.iterator().hasNext()) { + return queryResponse.iterator().next(); + } + throw new IoMessageNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG); + } + } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java new file mode 100644 index 00000000..dbd1a737 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.receipt.pdf.datastore.entity.receipt; + +import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.UserType; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class IOMessage { + + String id; + String messageId; + String eventId; + UserType userType; +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/ReceiptError.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/ReceiptError.java new file mode 100644 index 00000000..942024db --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/ReceiptError.java @@ -0,0 +1,19 @@ +package it.gov.pagopa.receipt.pdf.datastore.entity.receipt; + +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ReceiptError { + + private String id; + private String bizEventId; + private String messagePayload; + private String messageError; + private ReceiptErrorStatusType status; + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java new file mode 100644 index 00000000..a915792d --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +/** Thrown in case no receipt message to IO is found in the CosmosDB container */ +public class IoMessageNotFoundException extends Exception{ + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public IoMessageNotFoundException(String message) { + super(message); + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public IoMessageNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} + + diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java new file mode 100644 index 00000000..6b7b7d70 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java @@ -0,0 +1,104 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.*; +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * Azure Functions with Azure Http trigger. + */ +public class ReceiptToReviewed { + private final Logger logger = LoggerFactory.getLogger(ReceiptToReviewed.class); + private final ReceiptCosmosClient receiptCosmosClient; + + public ReceiptToReviewed() { + this.receiptCosmosClient = ReceiptCosmosClientImpl.getInstance(); + } + + ReceiptToReviewed( + ReceiptCosmosClient receiptCosmosClient) { + this.receiptCosmosClient = receiptCosmosClient; + } + + /** + * This function will be invoked when an Http Trigger occurs + * + * @return response with HttpStatus.OK + */ + @FunctionName("ReceiptToReviewed") + public HttpResponseMessage run( + @HttpTrigger(name = "ReceiptToReviewedFunction", + methods = {HttpMethod.POST}, + route = "receipts-error/{event-id}/reviewed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("event-id") String eventId, + @CosmosDBOutput( + name = "ReceiptErrorDatastore", + databaseName = "db", + containerName = "receipts-message-errors", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding documentdb, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + if (eventId == null || eventId.isBlank()) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid biz-event id") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + String responseMsg; + ReceiptError receiptError; + + try { + receiptError = receiptCosmosClient.getReceiptError(eventId); + } catch (NoSuchElementException | ReceiptNotFoundException e) { + responseMsg = String.format("No receiptError has been found with bizEventId %s", eventId); + logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); + return request + .createResponseBuilder(HttpStatus.NOT_FOUND) + .body(ProblemJson.builder() + .title(HttpStatus.NOT_FOUND.name()) + .detail(responseMsg) + .status(HttpStatus.NOT_FOUND.value()) + .build()) + .build(); + } + + if (!receiptError.getStatus().equals(ReceiptErrorStatusType.TO_REVIEW)) { + responseMsg = String.format("Found receiptError with invalid status %s for bizEventId %s", receiptError.getStatus(), eventId); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(responseMsg) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); + + documentdb.setValue(receiptError); + + responseMsg = String.format("ReceiptError with id %s and bizEventId %s updated to status %s with success", receiptError.getId(), receiptError.getBizEventId(), ReceiptErrorStatusType.REVIEWED); + return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java new file mode 100644 index 00000000..b41941f7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java @@ -0,0 +1,123 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.microsoft.azure.functions.*; +//import com.microsoft.azure.functions.annotation.*; +//import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +//import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +//import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +//import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +//import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +//import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.Optional; +// +///** +// * Azure Functions with Azure Http trigger. +// */ +//public class RecoverFailedReceipt { +// +// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceipt.class); +// +// private final BizEventToReceiptService bizEventToReceiptService; +// private final BizEventCosmosClient bizEventCosmosClient; +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverFailedReceipt(){ +// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); +// } +// +// RecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, +// BizEventCosmosClient bizEventCosmosClient, +// ReceiptCosmosService receiptCosmosService){ +// this.bizEventToReceiptService = bizEventToReceiptService; +// this.bizEventCosmosClient = bizEventCosmosClient; +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked when an Http Trigger occurs. +// *

+// * It recovers the receipt with the specified biz event id that has the following status: +// * - ({@link ReceiptStatusType#INSERTED}) +// * - ({@link ReceiptStatusType#FAILED}) +// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) +// *

+// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. +// * +// * @return response with {@link HttpStatus#OK} if the operation succeeded +// */ +// @FunctionName("RecoverFailedReceipt") +// public HttpResponseMessage run ( +// @HttpTrigger(name = "RecoverFailedReceiptTrigger", +// methods = {HttpMethod.POST}, +// route = "receipts/{event-id}/recover-failed", +// authLevel = AuthorizationLevel.ANONYMOUS) +// HttpRequestMessage> request, +// @BindingName("event-id") String eventId, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding documentdb, +// final ExecutionContext context) { +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// +// if (eventId == null || eventId.isBlank()) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a valid biz-event id") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// Boolean isCart = Boolean.parseBoolean(request.getQueryParameters().getOrDefault( +// "isCart", "false")); +// +// try { +// Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, +// this.bizEventCosmosClient, this.receiptCosmosService, null, logger, isCart); +// +// documentdb.setValue(receipt); +// String responseMsg = String.format("Receipt with eventId %s recovered", eventId); +// return request.createResponseBuilder(HttpStatus.OK) +// .body(responseMsg) +// .build(); +// +// } catch (BizEventNotFoundException exception) { +// String msg = String.format("Unable to retrieve the biz-event with id %s", eventId); +// logger.error(msg, exception); +// return request +// .createResponseBuilder(HttpStatus.NOT_FOUND) +// .body(ProblemJson.builder() +// .title(HttpStatus.NOT_FOUND.name()) +// .detail(msg) +// .status(HttpStatus.NOT_FOUND.value()) +// .build()) +// .build(); +// } catch (PDVTokenizerException | JsonProcessingException e) { +// logger.error(e.getMessage(), e); +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) +// .detail(e.getMessage()) +// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) +// .build()) +// .build(); +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java new file mode 100644 index 00000000..31db480a --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java @@ -0,0 +1,143 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.microsoft.azure.functions.*; +//import com.microsoft.azure.functions.annotation.AuthorizationLevel; +//import com.microsoft.azure.functions.annotation.CosmosDBOutput; +//import com.microsoft.azure.functions.annotation.FunctionName; +//import com.microsoft.azure.functions.annotation.HttpTrigger; +//import it.gov.pagopa.receipt.pdf.helpdesk.client.BizEventCosmosClient; +//import it.gov.pagopa.receipt.pdf.helpdesk.client.impl.BizEventCosmosClientImpl; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.helpdesk.model.MassiveRecoverResult; +//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.BizEventToReceiptService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.BizEventToReceiptServiceImpl; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.List; +//import java.util.NoSuchElementException; +//import java.util.Optional; +// +//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.BizEventToReceiptUtils.massiveRecoverByStatus; +// +///** +// * Azure Functions with Azure Http trigger. +// */ +//public class RecoverFailedReceiptMassive { +// +// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptMassive.class); +// +// private final BizEventToReceiptService bizEventToReceiptService; +// private final BizEventCosmosClient bizEventCosmosClient; +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverFailedReceiptMassive() { +// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); +// } +// +// RecoverFailedReceiptMassive(BizEventToReceiptService bizEventToReceiptService, +// BizEventCosmosClient bizEventCosmosClient, +// ReceiptCosmosService receiptCosmosService) { +// this.bizEventToReceiptService = bizEventToReceiptService; +// this.bizEventCosmosClient = bizEventCosmosClient; +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked when a Http Trigger occurs. +// *

+// * It recovers all the receipts with the specified status that has to be one of: +// * - ({@link ReceiptStatusType#INSERTED}) +// * - ({@link ReceiptStatusType#FAILED}) +// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) +// *

+// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. +// * +// * @return response with {@link HttpStatus#OK} if the operation succeeded +// */ +// @FunctionName("RecoverFailedReceiptMassive") +// public HttpResponseMessage run( +// @HttpTrigger(name = "RecoverFailedReceiptMassiveTrigger", +// methods = {HttpMethod.POST}, +// route = "receipts/recover-failed", +// authLevel = AuthorizationLevel.ANONYMOUS) +// HttpRequestMessage> request, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding> documentdb, +// final ExecutionContext context) { +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// +// // Get named parameter +// String status = request.getQueryParameters().get("status"); +// if (status == null) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a status to recover") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// ReceiptStatusType statusType; +// try { +// statusType = ReceiptStatusType.valueOf(status); +// } catch (IllegalArgumentException e) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a valid status to recover") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// MassiveRecoverResult recoverResult; +// try { +// recoverResult = massiveRecoverByStatus( +// context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); +// } catch (NoSuchElementException e) { +// logger.error("[{}] Unexpected error during recover of failed receipt", context.getFunctionName(), e); +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) +// .detail(e.getMessage()) +// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) +// .build()) +// .build(); +// } +// List receiptList = recoverResult.getReceiptList(); +// int errorCounter = recoverResult.getErrorCounter(); +// +// documentdb.setValue(receiptList); +// if (errorCounter > 0) { +// String msg = String.format("Recovered %s receipts but %s encountered an error.", receiptList.size(), errorCounter); +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title("Partial OK") +// .detail(msg) +// .status(HttpStatus.MULTI_STATUS.value()) +// .build()) +// .build(); +// } +// String responseMsg = String.format("Recovered %s receipts", receiptList.size()); +// return request.createResponseBuilder(HttpStatus.OK) +// .body(responseMsg) +// .build(); +// } +//} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java new file mode 100644 index 00000000..0a8c0f2a --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java @@ -0,0 +1,106 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.microsoft.azure.functions.ExecutionContext; +//import com.microsoft.azure.functions.OutputBinding; +//import com.microsoft.azure.functions.annotation.CosmosDBOutput; +//import com.microsoft.azure.functions.annotation.FunctionName; +//import com.microsoft.azure.functions.annotation.TimerTrigger; +//import it.gov.pagopa.receipt.pdf.helpdesk.client.BizEventCosmosClient; +//import it.gov.pagopa.receipt.pdf.helpdesk.client.impl.BizEventCosmosClientImpl; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.helpdesk.model.MassiveRecoverResult; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.BizEventToReceiptService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.BizEventToReceiptServiceImpl; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.List; +//import java.util.NoSuchElementException; +// +//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.BizEventToReceiptUtils.massiveRecoverByStatus; +// +///** +// * Azure Functions with Timer trigger. +// */ +//public class RecoverFailedReceiptScheduled { +// +// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptScheduled.class); +// +// private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("FAILED_AUTORECOVER_ENABLED", "true")); +// +// private final BizEventToReceiptService bizEventToReceiptService; +// private final BizEventCosmosClient bizEventCosmosClient; +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverFailedReceiptScheduled() { +// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); +// } +// +// RecoverFailedReceiptScheduled(BizEventToReceiptService bizEventToReceiptService, +// BizEventCosmosClient bizEventCosmosClient, +// ReceiptCosmosService receiptCosmosService) { +// this.bizEventToReceiptService = bizEventToReceiptService; +// this.bizEventCosmosClient = bizEventCosmosClient; +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked periodically according to the specified schedule. +// *

+// * It recovers all the receipts with the following status: +// * - ({@link ReceiptStatusType#INSERTED}) +// * - ({@link ReceiptStatusType#FAILED}) +// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) +// *

+// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. +// */ +// @FunctionName("RecoverFailedReceiptScheduled") +// public void run( +// @TimerTrigger(name = "timerInfoFailed", schedule = "%RECOVER_FAILED_CRON%") String timerInfo, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding> documentdb, +// final ExecutionContext context +// ) { +// if (isEnabled) { +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// List receiptList = new ArrayList<>(); +// +// receiptList.addAll(recover(context, ReceiptStatusType.INSERTED)); +// receiptList.addAll(recover(context, ReceiptStatusType.FAILED)); +// receiptList.addAll(recover(context, ReceiptStatusType.NOT_QUEUE_SENT)); +// +// documentdb.setValue(receiptList); +// } +// } +// +// private List recover(ExecutionContext context, ReceiptStatusType statusType) { +// try { +// MassiveRecoverResult recoverResult = massiveRecoverByStatus( +// context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); +// if (recoverResult.getErrorCounter() > 0) { +// logger.error("[{}] Error recovering {} failed receipts for status {}", +// context.getFunctionName(), recoverResult.getErrorCounter(), statusType); +// } +// List idList = recoverResult.getReceiptList().parallelStream().map(Receipt::getId).toList(); +// logger.info("[{}] Recovered {} receipts for status {} with ids: {}", +// context.getFunctionName(), recoverResult.getReceiptList().size(), statusType, idList); +// return recoverResult.getReceiptList(); +// } catch (NoSuchElementException e) { +// logger.error("[{}] Unexpected error during recover of failed receipt for status {}", +// context.getFunctionName(), statusType, e); +// return Collections.emptyList(); +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java new file mode 100644 index 00000000..5731823b --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java @@ -0,0 +1,113 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.microsoft.azure.functions.*; +//import com.microsoft.azure.functions.annotation.*; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.helpdesk.exception.ReceiptNotFoundException; +//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.Collections; +//import java.util.List; +//import java.util.Optional; +// +//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.restoreReceipt; +// +///** +// * Azure Functions with HTTP Trigger. +// */ +//public class RecoverNotNotifiedReceipt { +// +// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceipt.class); +// +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverNotNotifiedReceipt() { +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// } +// +// RecoverNotNotifiedReceipt(ReceiptCosmosService receiptCosmosService) { +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked when a Http Trigger occurs. +// *

+// * It recovers the receipt with the specified biz event id +// *

+// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification +// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the +// * previous step ({@link ReceiptStatusType#GENERATED}). +// * +// * @return response with {@link HttpStatus#OK} if the operation succeeded +// */ +// @FunctionName("RecoverNotNotifiedReceipt") +// public HttpResponseMessage run( +// @HttpTrigger(name = "RecoverNotNotifiedTrigger", +// methods = {HttpMethod.POST}, +// route = "receipts/{event-id}/recover-not-notified", +// authLevel = AuthorizationLevel.ANONYMOUS) +// HttpRequestMessage> request, +// @BindingName("event-id") String eventId, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding> documentReceipts, +// final ExecutionContext context) { +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// +// if (eventId == null || eventId.isBlank()) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a valid biz-event id") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// Receipt receipt; +// try { +// receipt = this.receiptCosmosService.getReceipt(eventId); +// } catch (ReceiptNotFoundException e) { +// String responseMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); +// logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); +// return request +// .createResponseBuilder(HttpStatus.NOT_FOUND) +// .body(ProblemJson.builder() +// .title(HttpStatus.NOT_FOUND.name()) +// .detail(responseMsg) +// .status(HttpStatus.NOT_FOUND.value()) +// .build()) +// .build(); +// } +// +// if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { +// String responseMsg = String.format("The requested receipt with eventId %s is not in the expected status", +// receipt.getEventId()); +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) +// .detail(responseMsg) +// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) +// .build()) +// .build(); +// } +// +// Receipt restoredReceipt = restoreReceipt(receipt); +// +// documentReceipts.setValue(Collections.singletonList(restoredReceipt)); +// String responseMsg = String.format("Receipt with id %s and eventId %s restored in status %s with success", +// receipt.getId(), receipt.getEventId(), ReceiptStatusType.GENERATED); +// return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); +// } +//} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java new file mode 100644 index 00000000..9ac3343c --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java @@ -0,0 +1,116 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.microsoft.azure.functions.*; +//import com.microsoft.azure.functions.annotation.AuthorizationLevel; +//import com.microsoft.azure.functions.annotation.CosmosDBOutput; +//import com.microsoft.azure.functions.annotation.FunctionName; +//import com.microsoft.azure.functions.annotation.HttpTrigger; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.List; +//import java.util.Optional; +// +//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; +// +///** +// * Azure Functions with HTTP Trigger. +// */ +//public class RecoverNotNotifiedReceiptMassive { +// +// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptMassive.class); +// +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverNotNotifiedReceiptMassive() { +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// } +// +// RecoverNotNotifiedReceiptMassive(ReceiptCosmosService receiptCosmosService) { +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked when a Http Trigger occurs. +// *

+// * It recovers all receipt with the provided status. +// *

+// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification +// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the +// * previous step ({@link ReceiptStatusType#GENERATED}). +// * +// * @return response with {@link HttpStatus#OK} if the operation succeeded +// */ +// @FunctionName("RecoverNotNotifiedReceiptMassive") +// public HttpResponseMessage run( +// @HttpTrigger(name = "RecoverNotNotifiedMassiveTrigger", +// methods = {HttpMethod.POST}, +// route = "receipts/recover-not-notified", +// authLevel = AuthorizationLevel.ANONYMOUS) +// HttpRequestMessage> request, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding> documentReceipts, +// final ExecutionContext context) { +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// +// // Get named parameter +// String status = request.getQueryParameters().get("status"); +// if (status == null) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a status to recover") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// ReceiptStatusType statusType; +// try { +// statusType = ReceiptStatusType.valueOf(status); +// } catch (IllegalArgumentException e) { +// return request +// .createResponseBuilder(HttpStatus.BAD_REQUEST) +// .body(ProblemJson.builder() +// .title(HttpStatus.BAD_REQUEST.name()) +// .detail("Please pass a valid status to recover") +// .status(HttpStatus.BAD_REQUEST.value()) +// .build()) +// .build(); +// } +// +// if (!statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !statusType.equals(ReceiptStatusType.GENERATED)) { +// String responseMsg = String.format("The requested status to recover %s is not one of the expected status", +// statusType); +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) +// .detail(responseMsg) +// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) +// .build()) +// .build(); +// } +// +// List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); +// if (receiptList.isEmpty()) { +// return request.createResponseBuilder(HttpStatus.OK).body("No receipts restored").build(); +// } +// +// documentReceipts.setValue(receiptList); +// String msg = String.format("Restored %s receipt with success", receiptList.size()); +// return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); +// } +// +//} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java new file mode 100644 index 00000000..9d7b377d --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java @@ -0,0 +1,84 @@ +//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +// +//import com.microsoft.azure.functions.ExecutionContext; +//import com.microsoft.azure.functions.OutputBinding; +//import com.microsoft.azure.functions.annotation.CosmosDBOutput; +//import com.microsoft.azure.functions.annotation.FunctionName; +//import com.microsoft.azure.functions.annotation.TimerTrigger; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.time.LocalDateTime; +//import java.util.ArrayList; +//import java.util.List; +// +//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; +// +//public class RecoverNotNotifiedReceiptScheduled { +// +// private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("NOT_NOTIFIED_AUTORECOVER_ENABLED", "true")); +// +// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptScheduled.class); +// +// private final ReceiptCosmosService receiptCosmosService; +// +// public RecoverNotNotifiedReceiptScheduled() { +// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); +// } +// +// RecoverNotNotifiedReceiptScheduled(ReceiptCosmosService receiptCosmosService) { +// this.receiptCosmosService = receiptCosmosService; +// } +// +// /** +// * This function will be invoked on a scheduled basis. +// *

+// * It recovers all receipt with the provided status. +// *

+// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification +// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the +// * previous step ({@link ReceiptStatusType#GENERATED}). +// * +// */ +// @FunctionName("RecoverNotNotifiedTimerTriggerProcessor") +// public void processRecoverNotNotifiedScheduledTrigger( +// @TimerTrigger( +// name = "timerInfoNotNotified", +// schedule = "%TRIGGER_NOTIFY_REC_SCHEDULE%" +// ) +// String timerInfo, +// @CosmosDBOutput( +// name = "ReceiptDatastore", +// databaseName = "db", +// containerName = "receipts", +// connection = "COSMOS_RECEIPTS_CONN_STRING") +// OutputBinding> documentReceipts, +// final ExecutionContext context) { +// +// if (isEnabled) { +// +// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); +// +// List receiptList = new ArrayList<>(); +// receiptList.addAll(process(context, ReceiptStatusType.IO_ERROR_TO_NOTIFY)); +// receiptList.addAll(process(context, ReceiptStatusType.GENERATED)); +// +// documentReceipts.setValue(receiptList); +// } +// +// } +// +// private List process(ExecutionContext context, ReceiptStatusType statusType) { +// List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); +// +// List idList = receiptList.parallelStream().map(Receipt::getId).toList(); +// logger.info("[{}] Recovered {} receipts for status {} with ids: {}", +// context.getFunctionName(), receiptList.size(), statusType, idList); +// return receiptList; +// } +// +//} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/ProblemJson.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/ProblemJson.java new file mode 100644 index 00000000..effdd007 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/ProblemJson.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.receipt.pdf.datastore.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +/** + * Object returned as response in case of an error. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProblemJson { + + @JsonProperty("title") + private String title; + + @JsonProperty("status") + @Min(100) + @Max(600) + private Integer status; + + @JsonProperty("detail") + private String detail; + +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java index 9a5e84b9..96f0641d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java @@ -91,6 +91,14 @@ void tokenizeFiscalCodes( */ List getCartBizEvents(CartForReceipt cart); + /** + * Retrieve all events that are associated to the cart with the specified id + * + * @param cartId the id of the cart + * @return a list of biz-events + */ + List getCartBizEventsById(String cartId); + /** * This method saves the provided CartForReceipt object to the datastore. * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java new file mode 100644 index 00000000..752f2440 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -0,0 +1,71 @@ +package it.gov.pagopa.receipt.pdf.datastore.service; + +import com.azure.cosmos.models.FeedResponse; +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; + +/** + * Service that handle the input and output for the {@link ReceiptCosmosClient} + */ +public interface ReceiptCosmosService { + + /** + * Retrieve the receipt with the provided biz-event id + * + * @param eventId the biz-event id + * @return the receipt + * @throws ReceiptNotFoundException if the receipt was not found or the retrieved receipt is null + */ + Receipt getReceipt(String eventId) throws ReceiptNotFoundException; + + /** + * Retrieve the not notified receipt with the provided {@link ReceiptStatusType} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the receipts + * @return receipt documents + */ + Iterable> getNotNotifiedReceiptByStatus( + String continuationToken, + Integer pageSize, + ReceiptStatusType statusType + ); + + /** + * Retrieve the failed receipt with the provided {@link ReceiptStatusType} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the receipts + * @return receipt documents + */ + Iterable> getFailedReceiptByStatus( + String continuationToken, + Integer pageSize, + ReceiptStatusType statusType + ); + + /** + * + * @param messageId + * @return + */ + IOMessage getReceiptMessage(String messageId) throws IoMessageNotFoundException; + + + /** + * Retrieve the cart with the provided id + * + * @param cartId the cart id + * @return the cart + * @throws CartNotFoundException if the cart was not found or the retrieved cart is null + */ + CartForReceipt getCart(String cartId) throws CartNotFoundException; +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index d5ced402..1c5823af 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -2,6 +2,7 @@ import com.azure.core.http.rest.Response; import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.FeedResponse; import com.azure.storage.queue.models.SendMessageResult; import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.HttpStatus; @@ -217,6 +218,42 @@ public void tokenizeFiscalCodes(BizEvent bizEvent, Receipt receipt, EventData ev } } +// /** +// * {@inheritDoc} +// */ +// @Override +// public void tokenizeFiscalCodes(BizEvent bizEvent, Receipt receipt, EventData eventData) throws JsonProcessingException, PDVTokenizerException { +// try { +// eventData.setDebtorFiscalCode( +// bizEvent.getDebtor() != null && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getDebtor().getEntityUniqueIdentifierValue()) ? +// pdvTokenizerService.generateTokenForFiscalCodeWithRetry(bizEvent.getDebtor().getEntityUniqueIdentifierValue()) : +// FISCAL_CODE_ANONYMOUS +// ); +// +// if (isFromAuthenticatedOrigin(bizEvent)) { +// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getUser() != null +// && bizEvent.getTransactionDetails().getUser().getFiscalCode() != null +// && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getTransactionDetails().getUser().getFiscalCode())) { +// eventData.setPayerFiscalCode( +// pdvTokenizerService.generateTokenForFiscalCodeWithRetry( +// bizEvent.getTransactionDetails().getUser().getFiscalCode()) +// ); +// } else if (bizEvent.getPayer() != null && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getPayer().getEntityUniqueIdentifierValue())) { +// eventData.setPayerFiscalCode( +// pdvTokenizerService.generateTokenForFiscalCodeWithRetry( +// bizEvent.getPayer().getEntityUniqueIdentifierValue()) +// ); +// } +// } +// } catch (PDVTokenizerException e) { +// handleTokenizerException(receipt, e.getMessage(), e.getStatusCode()); +// throw e; +// } catch (JsonProcessingException e) { +// handleTokenizerException(receipt, e.getMessage(), ReasonErrorCode.ERROR_PDV_MAPPING.getCode()); +// throw e; +// } +// } + /** * {@inheritDoc} @@ -274,6 +311,25 @@ public List getCartBizEvents(CartForReceipt cart) { return bizEventList; } + /** + * {@inheritDoc} + */ + @Override + public List getCartBizEventsById(String cartId) { + List bizEventList = new ArrayList<>(); + String continuationToken = null; + do { + Iterable> feedResponseIterator = + this.bizEventCosmosClient.getAllBizEventDocument(cartId, continuationToken, 100); + + for (FeedResponse page : feedResponseIterator) { + bizEventList.addAll(page.getResults()); + continuationToken = page.getContinuationToken(); + } + } while (continuationToken != null); + return bizEventList; + } + /** * {@inheritDoc} */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java new file mode 100644 index 00000000..d6f7e0d9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -0,0 +1,136 @@ +package it.gov.pagopa.receipt.pdf.datastore.service.impl; + +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.models.CosmosQueryRequestOptions; +import com.azure.cosmos.models.FeedResponse; +import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartReceiptsCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; + +public class ReceiptCosmosServiceImpl implements ReceiptCosmosService { + + private final ReceiptCosmosClient receiptCosmosClient; + private final CartReceiptsCosmosClient cartReceiptsCosmosClient; + + public ReceiptCosmosServiceImpl() { + this.receiptCosmosClient = ReceiptCosmosClientImpl.getInstance(); + this.cartReceiptsCosmosClient = CartReceiptsCosmosClientImpl.getInstance(); + } + + ReceiptCosmosServiceImpl(ReceiptCosmosClient receiptCosmosClient, CartReceiptsCosmosClient cartReceiptsCosmosClient) { + this.receiptCosmosClient = receiptCosmosClient; + this.cartReceiptsCosmosClient = cartReceiptsCosmosClient; + } + + /** + * {@inheritDoc} + */ + @Override + public Receipt getReceipt(String eventId) throws ReceiptNotFoundException { + Receipt receipt; + try { + receipt = this.receiptCosmosClient.getReceiptDocument(eventId); + } catch (ReceiptNotFoundException e) { + String errorMsg = String.format("Receipt not found with the biz-event id %s", eventId); + throw new ReceiptNotFoundException(errorMsg, e); + } + + if (receipt == null) { + String errorMsg = String.format("Receipt retrieved with the biz-event id %s is null", eventId); + throw new ReceiptNotFoundException(errorMsg); + } + return receipt; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getNotNotifiedReceiptByStatus( + String continuationToken, + Integer pageSize, + ReceiptStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY)) { + return this.receiptCosmosClient.getIOErrorToNotifyReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(ReceiptStatusType.GENERATED)) { + return this.receiptCosmosClient.getGeneratedReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving not notified receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getFailedReceiptByStatus( + String continuationToken, + Integer pageSize, + ReceiptStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(ReceiptStatusType.FAILED) || statusType.equals(ReceiptStatusType.NOT_QUEUE_SENT)) { + return this.receiptCosmosClient.getFailedReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(ReceiptStatusType.INSERTED)) { + return this.receiptCosmosClient.getInsertedReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving failed receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + + @Override + public IOMessage getReceiptMessage(String messageId) throws IoMessageNotFoundException { + IOMessage message; + try { + message = this.receiptCosmosClient.getIoMessage(messageId); + } catch (IoMessageNotFoundException e) { + String errorMsg = String.format("Receipt Message to IO not found with the message id %s", messageId); + throw new IoMessageNotFoundException(errorMsg, e); + } + + if (message == null) { + String errorMsg = String.format("Receipt retrieved with the message id %s is null", messageId); + throw new IoMessageNotFoundException(errorMsg); + } + return message; + } + + @Override + public CartForReceipt getCart(String cartId) throws CartNotFoundException { + CartForReceipt cartForReceipt; + try { + cartForReceipt = this.cartReceiptsCosmosClient.getCartItem(cartId); + } catch (CartNotFoundException e) { + String errorMsg = String.format("Receipt not found with the biz-event id %s", cartId); + throw new CartNotFoundException(errorMsg, e); + } + + if (cartForReceipt == null) { + String errorMsg = String.format("Receipt retrieved with the biz-event id %s is null", cartId); + throw new CartNotFoundException(errorMsg); + } + return cartForReceipt; + } + +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 4931bd72..320fa5e6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -1,6 +1,8 @@ package it.gov.pagopa.receipt.pdf.datastore.utils; +import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.ExecutionContext; +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; @@ -11,13 +13,18 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import org.slf4j.Logger; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,9 +39,134 @@ public class BizEventToReceiptUtils { "UNWANTED_REMITTANCE_INFO", "pagamento multibeneficiario,pagamento bpay").split(",")); private static final String ECOMMERCE = "CHECKOUT"; + private static final List listOrigin; + + static { + listOrigin = Arrays.asList(System.getenv().getOrDefault("LIST_VALID_ORIGINS", "IO,CHECKOUT,WISP,CHECKOUT_CART").split(",")); + } + private BizEventToReceiptUtils() { } + public static Receipt getEvent( + String eventId, + ExecutionContext context, + BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient, + ReceiptCosmosService receiptCosmosService, + Receipt receipt, + Logger logger, + Boolean isCart + ) throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { + + List listCart = null; + BizEvent bizEvent; + + if (isCart) { + listCart = bizEventToReceiptService.getCartBizEventsById(eventId); + bizEvent = listCart.get(0); + } else { + bizEvent = bizEventCosmosClient.getBizEventDocument(eventId); + } + + if (isCart) { + Integer intTotalNotice = Integer.parseInt(bizEvent.getPaymentInfo().getTotalNotice()); + if (!intTotalNotice.equals(listCart.size())) { + return null; + } + for (BizEvent event : listCart) { + if (isBizEventInvalid(event, context, logger)) { + return null; + } + } + } else if (isBizEventInvalid(bizEvent, context, logger)) { + return null; + } + + + if (receipt == null) { + try { + receipt = receiptCosmosService.getReceipt(eventId); + } catch (ReceiptNotFoundException e) { + receipt = BizEventToReceiptUtils.createReceipt(bizEvent, + bizEventToReceiptService, logger); + EventData eventData = receipt.getEventData(); + if (isCart) { + AtomicReference amount = new AtomicReference<>(BigDecimal.ZERO); + List cartItems = new ArrayList<>(); + listCart.forEach(event -> { + BigDecimal amountExtracted = getAmount(bizEvent); + amount.updateAndGet(v -> v.add(amountExtracted)); + cartItems.add( + CartItem.builder() + .payeeName(bizEvent.getCreditor() != null ? + bizEvent.getCreditor().getCompanyName() : null) + .subject(getItemSubject(bizEvent)) + .build()); + }); + + if (!amount.get().equals(BigDecimal.ZERO)) { + eventData.setAmount(formatAmount(amount.get().toString())); + } + + eventData.setCart(cartItems); + } + receipt.setStatus(ReceiptStatusType.FAILED); + } + } + + if (receipt != null && ( + receipt.getStatus().equals(ReceiptStatusType.FAILED) || + receipt.getStatus().equals(ReceiptStatusType.INSERTED) || + receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) + )) { + if (receipt.getEventData() == null || receipt.getEventData().getDebtorFiscalCode() == null) { + tokenizeReceipt(bizEventToReceiptService, isCart ? listCart : Collections.singletonList(bizEvent), receipt); + } + receipt.setStatus(ReceiptStatusType.INSERTED); + bizEventToReceiptService.handleSendMessageToQueue(isCart ? listCart : + Collections.singletonList(bizEvent), receipt); + if (receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT) { + receipt.setInserted_at(System.currentTimeMillis()); + receipt.setReasonErr(null); + receipt.setReasonErrPayer(null); + } + return receipt; + } + return null; + } + + public static void tokenizeReceipt(BizEventToReceiptService service, List bizEvents, Receipt receipt) + throws PDVTokenizerException, JsonProcessingException { + BizEvent firstEvent = bizEvents.get(0); + if (receipt.getEventData() == null) { + EventData eventData = new EventData(); + receipt.setEventData(eventData); + eventData.setTransactionCreationDate( + service.getTransactionCreationDate(firstEvent)); + + AtomicReference amount = new AtomicReference<>(BigDecimal.ZERO); + List cartItems = new ArrayList<>(); + bizEvents.forEach(bizEvent -> { + BigDecimal amountExtracted = getAmount(bizEvent); + amount.updateAndGet(v -> v.add(amountExtracted)); + cartItems.add( + CartItem.builder() + .payeeName(bizEvent.getCreditor() != null ? bizEvent.getCreditor().getCompanyName() : null) + .subject(getItemSubject(bizEvent)) + .build()); + }); + + if (!amount.get().equals(BigDecimal.ZERO)) { + eventData.setAmount(formatAmount(amount.get().toString())); + } + + eventData.setCart(cartItems); + + } + service.tokenizeFiscalCodes(firstEvent, receipt, receipt.getEventData()); + } + /** * Creates a new instance of Receipt, using the tokenizer service to mask the PII, based on * the provided BizEvent @@ -303,4 +435,32 @@ public static boolean isValidChannelOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } + + public static boolean isFromAuthenticatedOrigin(BizEvent bizEvent) { + if (bizEvent.getTransactionDetails() == null) { + return false; + } + + var transactionDetails = bizEvent.getTransactionDetails(); + var transaction = transactionDetails.getTransaction(); + var info = transactionDetails.getInfo(); + var user = transactionDetails.getUser(); + + String origin = (transaction != null) ? transaction.getOrigin() : null; + String clientId = (info != null) ? info.getClientId() : null; + UserType userType = (user != null) ? user.getType() : null; + + boolean originMatches = origin != null && listOrigin.contains(origin); + boolean clientIdMatches = clientId != null && listOrigin.contains(clientId); + + boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); + boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); + boolean isRegisteredUser = UserType.REGISTERED.equals(userType); + + if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { + return false; + } + + return originMatches || clientIdMatches; + } } From 4aef8952c927c507eb63852e80a22ef8a1440562 Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 15 Dec 2025 19:50:48 +0100 Subject: [PATCH 02/38] receiptToReviewed --- .../datastore/helpdesk/ReceiptToReviewed.java | 14 ++++++++----- .../service/ReceiptCosmosService.java | 10 +++++++++ .../impl/ReceiptCosmosServiceImpl.java | 21 +++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java index 6b7b7d70..c67918ed 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java @@ -8,6 +8,8 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,15 +22,17 @@ */ public class ReceiptToReviewed { private final Logger logger = LoggerFactory.getLogger(ReceiptToReviewed.class); - private final ReceiptCosmosClient receiptCosmosClient; + + private final ReceiptCosmosService receiptCosmosService; public ReceiptToReviewed() { - this.receiptCosmosClient = ReceiptCosmosClientImpl.getInstance(); + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); } ReceiptToReviewed( - ReceiptCosmosClient receiptCosmosClient) { - this.receiptCosmosClient = receiptCosmosClient; + ReceiptCosmosService receiptCosmosService + ) { + this.receiptCosmosService = receiptCosmosService; } /** @@ -68,7 +72,7 @@ public HttpResponseMessage run( ReceiptError receiptError; try { - receiptError = receiptCosmosClient.getReceiptError(eventId); + receiptError = receiptCosmosService.getReceiptError(eventId); } catch (NoSuchElementException | ReceiptNotFoundException e) { responseMsg = String.format("No receiptError has been found with bizEventId %s", eventId); logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index 752f2440..ce95ceab 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -5,6 +5,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; @@ -24,6 +25,15 @@ public interface ReceiptCosmosService { */ Receipt getReceipt(String eventId) throws ReceiptNotFoundException; + /** + * Retrieve the receipt error with the provided biz-event id + * + * @param eventId the biz-event id + * @return the receipt error + * @throws ReceiptNotFoundException if the receipt was not found or the retrieved receipt is null + */ + ReceiptError getReceiptError(String eventId) throws ReceiptNotFoundException; + /** * Retrieve the not notified receipt with the provided {@link ReceiptStatusType} status * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index d6f7e0d9..e973802a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -11,6 +11,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; @@ -55,6 +56,26 @@ public Receipt getReceipt(String eventId) throws ReceiptNotFoundException { return receipt; } + /** + * {@inheritDoc} + */ + @Override + public ReceiptError getReceiptError(String eventId) throws ReceiptNotFoundException { + ReceiptError receipt; + try { + receipt = this.receiptCosmosClient.getReceiptError(eventId); + } catch (ReceiptNotFoundException e) { + String errorMsg = String.format("Receipt error not found with the biz-event id %s", eventId); + throw new ReceiptNotFoundException(errorMsg, e); + } + + if (receipt == null) { + String errorMsg = String.format("Receipt error retrieved with the biz-event id %s is null", eventId); + throw new ReceiptNotFoundException(errorMsg); + } + return receipt; + } + /** * {@inheritDoc} */ From b0403d8b614a97e02e2af713737b1054fa693a19 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 10:00:48 +0100 Subject: [PATCH 03/38] added all helpdesk http fn --- .../pdf/datastore/BizEventToReceipt.java | 3 +- .../pdf/datastore/entity/receipt/Receipt.java | 2 + .../helpdesk/RecoverFailedReceipt.java | 248 +++++++-------- .../helpdesk/RecoverFailedReceiptMassive.java | 288 +++++++++--------- .../RecoverFailedReceiptScheduled.java | 214 ++++++------- .../helpdesk/RecoverNotNotifiedReceipt.java | 227 +++++++------- .../RecoverNotNotifiedReceiptMassive.java | 234 +++++++------- .../RecoverNotNotifiedReceiptScheduled.java | 170 ++++++----- .../datastore/model/MassiveRecoverResult.java | 15 + .../service/BizEventToReceiptService.java | 4 +- .../impl/BizEventToReceiptServiceImpl.java | 19 +- .../utils/BizEventToReceiptUtils.java | 40 ++- .../utils/RecoverNotNotifiedReceiptUtils.java | 52 ++++ 13 files changed, 816 insertions(+), 700 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java index dd8eeb77..899e102d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java @@ -19,6 +19,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.createReceipt; @@ -131,7 +132,7 @@ public void processBizEventToReceipt( if (isReceiptStatusValid(receipt)) { // Send biz event as message to queue (to be processed from the other function) - this.bizEventToReceiptService.handleSendMessageToQueue(bizEvent, receipt); + this.bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(bizEvent), receipt); } if (!isReceiptStatusValid(receipt)) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java index f9858cc7..615b7494 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java @@ -23,7 +23,9 @@ public class Receipt { private int numRetry; private ReasonError reasonErr; private ReasonError reasonErrPayer; + private int notificationNumRetry; private long inserted_at; private long generated_at; private long notified_at; + private Boolean isCart; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java index b41941f7..1d07f0aa 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java @@ -1,123 +1,125 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.fasterxml.jackson.core.JsonProcessingException; -//import com.microsoft.azure.functions.*; -//import com.microsoft.azure.functions.annotation.*; -//import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -//import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -//import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -//import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; -//import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; -//import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.Optional; -// -///** -// * Azure Functions with Azure Http trigger. -// */ -//public class RecoverFailedReceipt { -// -// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceipt.class); -// -// private final BizEventToReceiptService bizEventToReceiptService; -// private final BizEventCosmosClient bizEventCosmosClient; -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverFailedReceipt(){ -// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); -// } -// -// RecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, -// BizEventCosmosClient bizEventCosmosClient, -// ReceiptCosmosService receiptCosmosService){ -// this.bizEventToReceiptService = bizEventToReceiptService; -// this.bizEventCosmosClient = bizEventCosmosClient; -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked when an Http Trigger occurs. -// *

-// * It recovers the receipt with the specified biz event id that has the following status: -// * - ({@link ReceiptStatusType#INSERTED}) -// * - ({@link ReceiptStatusType#FAILED}) -// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) -// *

-// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. -// * -// * @return response with {@link HttpStatus#OK} if the operation succeeded -// */ -// @FunctionName("RecoverFailedReceipt") -// public HttpResponseMessage run ( -// @HttpTrigger(name = "RecoverFailedReceiptTrigger", -// methods = {HttpMethod.POST}, -// route = "receipts/{event-id}/recover-failed", -// authLevel = AuthorizationLevel.ANONYMOUS) -// HttpRequestMessage> request, -// @BindingName("event-id") String eventId, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding documentdb, -// final ExecutionContext context) { -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// -// if (eventId == null || eventId.isBlank()) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a valid biz-event id") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// Boolean isCart = Boolean.parseBoolean(request.getQueryParameters().getOrDefault( -// "isCart", "false")); -// -// try { -// Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, -// this.bizEventCosmosClient, this.receiptCosmosService, null, logger, isCart); -// -// documentdb.setValue(receipt); -// String responseMsg = String.format("Receipt with eventId %s recovered", eventId); -// return request.createResponseBuilder(HttpStatus.OK) -// .body(responseMsg) -// .build(); -// -// } catch (BizEventNotFoundException exception) { -// String msg = String.format("Unable to retrieve the biz-event with id %s", eventId); -// logger.error(msg, exception); -// return request -// .createResponseBuilder(HttpStatus.NOT_FOUND) -// .body(ProblemJson.builder() -// .title(HttpStatus.NOT_FOUND.name()) -// .detail(msg) -// .status(HttpStatus.NOT_FOUND.value()) -// .build()) -// .build(); -// } catch (PDVTokenizerException | JsonProcessingException e) { -// logger.error(e.getMessage(), e); -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) -// .detail(e.getMessage()) -// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) -// .build()) -// .build(); -// } -// } -//} \ No newline at end of file +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.*; +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * Azure Functions with Azure Http trigger. + */ +public class RecoverFailedReceipt { + + private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceipt.class); + + private final BizEventToReceiptService bizEventToReceiptService; + private final BizEventCosmosClient bizEventCosmosClient; + private final ReceiptCosmosService receiptCosmosService; + + public RecoverFailedReceipt(){ + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + } + + RecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient, + ReceiptCosmosService receiptCosmosService){ + this.bizEventToReceiptService = bizEventToReceiptService; + this.bizEventCosmosClient = bizEventCosmosClient; + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + *

+ * It recovers the receipt with the specified biz event id that has the following status: + * - ({@link ReceiptStatusType#INSERTED}) + * - ({@link ReceiptStatusType#FAILED}) + * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

+ * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverFailedReceipt") + public HttpResponseMessage run ( + @HttpTrigger(name = "RecoverFailedReceiptTrigger", + methods = {HttpMethod.POST}, + route = "receipts/{event-id}/recover-failed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("event-id") String eventId, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding documentdb, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + if (eventId == null || eventId.isBlank()) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid biz-event id") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + Boolean isCart = Boolean.parseBoolean(request.getQueryParameters().getOrDefault( + "isCart", "false")); + + try { + Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, + this.bizEventCosmosClient, this.receiptCosmosService, null, logger, isCart); + + documentdb.setValue(receipt); + String responseMsg = String.format("Receipt with eventId %s recovered", eventId); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + + } catch (BizEventNotFoundException exception) { + String msg = String.format("Unable to retrieve the biz-event with id %s", eventId); + logger.error(msg, exception); + return request + .createResponseBuilder(HttpStatus.NOT_FOUND) + .body(ProblemJson.builder() + .title(HttpStatus.NOT_FOUND.name()) + .detail(msg) + .status(HttpStatus.NOT_FOUND.value()) + .build()) + .build(); + } catch (PDVTokenizerException | JsonProcessingException e) { + logger.error(e.getMessage(), e); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(e.getMessage()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java index 31db480a..e19e5c35 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java @@ -1,143 +1,145 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.microsoft.azure.functions.*; -//import com.microsoft.azure.functions.annotation.AuthorizationLevel; -//import com.microsoft.azure.functions.annotation.CosmosDBOutput; -//import com.microsoft.azure.functions.annotation.FunctionName; -//import com.microsoft.azure.functions.annotation.HttpTrigger; -//import it.gov.pagopa.receipt.pdf.helpdesk.client.BizEventCosmosClient; -//import it.gov.pagopa.receipt.pdf.helpdesk.client.impl.BizEventCosmosClientImpl; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.helpdesk.model.MassiveRecoverResult; -//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.BizEventToReceiptService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.BizEventToReceiptServiceImpl; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.List; -//import java.util.NoSuchElementException; -//import java.util.Optional; -// -//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.BizEventToReceiptUtils.massiveRecoverByStatus; -// -///** -// * Azure Functions with Azure Http trigger. -// */ -//public class RecoverFailedReceiptMassive { -// -// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptMassive.class); -// -// private final BizEventToReceiptService bizEventToReceiptService; -// private final BizEventCosmosClient bizEventCosmosClient; -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverFailedReceiptMassive() { -// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); -// } -// -// RecoverFailedReceiptMassive(BizEventToReceiptService bizEventToReceiptService, -// BizEventCosmosClient bizEventCosmosClient, -// ReceiptCosmosService receiptCosmosService) { -// this.bizEventToReceiptService = bizEventToReceiptService; -// this.bizEventCosmosClient = bizEventCosmosClient; -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked when a Http Trigger occurs. -// *

-// * It recovers all the receipts with the specified status that has to be one of: -// * - ({@link ReceiptStatusType#INSERTED}) -// * - ({@link ReceiptStatusType#FAILED}) -// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) -// *

-// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. -// * -// * @return response with {@link HttpStatus#OK} if the operation succeeded -// */ -// @FunctionName("RecoverFailedReceiptMassive") -// public HttpResponseMessage run( -// @HttpTrigger(name = "RecoverFailedReceiptMassiveTrigger", -// methods = {HttpMethod.POST}, -// route = "receipts/recover-failed", -// authLevel = AuthorizationLevel.ANONYMOUS) -// HttpRequestMessage> request, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding> documentdb, -// final ExecutionContext context) { -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// -// // Get named parameter -// String status = request.getQueryParameters().get("status"); -// if (status == null) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a status to recover") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// ReceiptStatusType statusType; -// try { -// statusType = ReceiptStatusType.valueOf(status); -// } catch (IllegalArgumentException e) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a valid status to recover") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// MassiveRecoverResult recoverResult; -// try { -// recoverResult = massiveRecoverByStatus( -// context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); -// } catch (NoSuchElementException e) { -// logger.error("[{}] Unexpected error during recover of failed receipt", context.getFunctionName(), e); -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) -// .detail(e.getMessage()) -// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) -// .build()) -// .build(); -// } -// List receiptList = recoverResult.getReceiptList(); -// int errorCounter = recoverResult.getErrorCounter(); -// -// documentdb.setValue(receiptList); -// if (errorCounter > 0) { -// String msg = String.format("Recovered %s receipts but %s encountered an error.", receiptList.size(), errorCounter); -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title("Partial OK") -// .detail(msg) -// .status(HttpStatus.MULTI_STATUS.value()) -// .build()) -// .build(); -// } -// String responseMsg = String.format("Recovered %s receipts", receiptList.size()); -// return request.createResponseBuilder(HttpStatus.OK) -// .body(responseMsg) -// .build(); -// } -//} \ No newline at end of file +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; + +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.massiveRecoverByStatus; + + +/** + * Azure Functions with Azure Http trigger. + */ +public class RecoverFailedReceiptMassive { + + private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptMassive.class); + + private final BizEventToReceiptService bizEventToReceiptService; + private final BizEventCosmosClient bizEventCosmosClient; + private final ReceiptCosmosService receiptCosmosService; + + public RecoverFailedReceiptMassive() { + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + } + + RecoverFailedReceiptMassive(BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient, + ReceiptCosmosService receiptCosmosService) { + this.bizEventToReceiptService = bizEventToReceiptService; + this.bizEventCosmosClient = bizEventCosmosClient; + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked when a Http Trigger occurs. + *

+ * It recovers all the receipts with the specified status that has to be one of: + * - ({@link ReceiptStatusType#INSERTED}) + * - ({@link ReceiptStatusType#FAILED}) + * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

+ * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverFailedReceiptMassive") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverFailedReceiptMassiveTrigger", + methods = {HttpMethod.POST}, + route = "receipts/recover-failed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + // Get named parameter + String status = request.getQueryParameters().get("status"); + if (status == null) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a status to recover") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + ReceiptStatusType statusType; + try { + statusType = ReceiptStatusType.valueOf(status); + } catch (IllegalArgumentException e) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid status to recover") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + MassiveRecoverResult recoverResult; + try { + recoverResult = massiveRecoverByStatus( + context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); + } catch (NoSuchElementException e) { + logger.error("[{}] Unexpected error during recover of failed receipt", context.getFunctionName(), e); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(e.getMessage()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + List receiptList = recoverResult.getReceiptList(); + int errorCounter = recoverResult.getErrorCounter(); + + documentdb.setValue(receiptList); + if (errorCounter > 0) { + String msg = String.format("Recovered %s receipts but %s encountered an error.", receiptList.size(), errorCounter); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title("Partial OK") + .detail(msg) + .status(HttpStatus.MULTI_STATUS.value()) + .build()) + .build(); + } + String responseMsg = String.format("Recovered %s receipts", receiptList.size()); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java index 0a8c0f2a..30332d3d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java @@ -1,106 +1,108 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.microsoft.azure.functions.ExecutionContext; -//import com.microsoft.azure.functions.OutputBinding; -//import com.microsoft.azure.functions.annotation.CosmosDBOutput; -//import com.microsoft.azure.functions.annotation.FunctionName; -//import com.microsoft.azure.functions.annotation.TimerTrigger; -//import it.gov.pagopa.receipt.pdf.helpdesk.client.BizEventCosmosClient; -//import it.gov.pagopa.receipt.pdf.helpdesk.client.impl.BizEventCosmosClientImpl; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.helpdesk.model.MassiveRecoverResult; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.BizEventToReceiptService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.BizEventToReceiptServiceImpl; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.ArrayList; -//import java.util.Collections; -//import java.util.List; -//import java.util.NoSuchElementException; -// -//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.BizEventToReceiptUtils.massiveRecoverByStatus; -// -///** -// * Azure Functions with Timer trigger. -// */ -//public class RecoverFailedReceiptScheduled { -// -// private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptScheduled.class); -// -// private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("FAILED_AUTORECOVER_ENABLED", "true")); -// -// private final BizEventToReceiptService bizEventToReceiptService; -// private final BizEventCosmosClient bizEventCosmosClient; -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverFailedReceiptScheduled() { -// this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); -// } -// -// RecoverFailedReceiptScheduled(BizEventToReceiptService bizEventToReceiptService, -// BizEventCosmosClient bizEventCosmosClient, -// ReceiptCosmosService receiptCosmosService) { -// this.bizEventToReceiptService = bizEventToReceiptService; -// this.bizEventCosmosClient = bizEventCosmosClient; -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked periodically according to the specified schedule. -// *

-// * It recovers all the receipts with the following status: -// * - ({@link ReceiptStatusType#INSERTED}) -// * - ({@link ReceiptStatusType#FAILED}) -// * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) -// *

-// * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. -// */ -// @FunctionName("RecoverFailedReceiptScheduled") -// public void run( -// @TimerTrigger(name = "timerInfoFailed", schedule = "%RECOVER_FAILED_CRON%") String timerInfo, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding> documentdb, -// final ExecutionContext context -// ) { -// if (isEnabled) { -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// List receiptList = new ArrayList<>(); -// -// receiptList.addAll(recover(context, ReceiptStatusType.INSERTED)); -// receiptList.addAll(recover(context, ReceiptStatusType.FAILED)); -// receiptList.addAll(recover(context, ReceiptStatusType.NOT_QUEUE_SENT)); -// -// documentdb.setValue(receiptList); -// } -// } -// -// private List recover(ExecutionContext context, ReceiptStatusType statusType) { -// try { -// MassiveRecoverResult recoverResult = massiveRecoverByStatus( -// context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); -// if (recoverResult.getErrorCounter() > 0) { -// logger.error("[{}] Error recovering {} failed receipts for status {}", -// context.getFunctionName(), recoverResult.getErrorCounter(), statusType); -// } -// List idList = recoverResult.getReceiptList().parallelStream().map(Receipt::getId).toList(); -// logger.info("[{}] Recovered {} receipts for status {} with ids: {}", -// context.getFunctionName(), recoverResult.getReceiptList().size(), statusType, idList); -// return recoverResult.getReceiptList(); -// } catch (NoSuchElementException e) { -// logger.error("[{}] Unexpected error during recover of failed receipt for status {}", -// context.getFunctionName(), statusType, e); -// return Collections.emptyList(); -// } -// } -//} \ No newline at end of file +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.TimerTrigger; + +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.massiveRecoverByStatus; + + +/** + * Azure Functions with Timer trigger. + */ +public class RecoverFailedReceiptScheduled { + + private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptScheduled.class); + + private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("FAILED_AUTORECOVER_ENABLED", "true")); + + private final BizEventToReceiptService bizEventToReceiptService; + private final BizEventCosmosClient bizEventCosmosClient; + private final ReceiptCosmosService receiptCosmosService; + + public RecoverFailedReceiptScheduled() { + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + } + + RecoverFailedReceiptScheduled(BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient, + ReceiptCosmosService receiptCosmosService) { + this.bizEventToReceiptService = bizEventToReceiptService; + this.bizEventCosmosClient = bizEventCosmosClient; + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked periodically according to the specified schedule. + *

+ * It recovers all the receipts with the following status: + * - ({@link ReceiptStatusType#INSERTED}) + * - ({@link ReceiptStatusType#FAILED}) + * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

+ * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. + */ + @FunctionName("RecoverFailedReceiptScheduled") + public void run( + @TimerTrigger(name = "timerInfoFailed", schedule = "%RECOVER_FAILED_CRON%") String timerInfo, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context + ) { + if (isEnabled) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + List receiptList = new ArrayList<>(); + + receiptList.addAll(recover(context, ReceiptStatusType.INSERTED)); + receiptList.addAll(recover(context, ReceiptStatusType.FAILED)); + receiptList.addAll(recover(context, ReceiptStatusType.NOT_QUEUE_SENT)); + + documentdb.setValue(receiptList); + } + } + + private List recover(ExecutionContext context, ReceiptStatusType statusType) { + try { + MassiveRecoverResult recoverResult = massiveRecoverByStatus( + context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); + if (recoverResult.getErrorCounter() > 0) { + logger.error("[{}] Error recovering {} failed receipts for status {}", + context.getFunctionName(), recoverResult.getErrorCounter(), statusType); + } + List idList = recoverResult.getReceiptList().parallelStream().map(Receipt::getId).toList(); + logger.info("[{}] Recovered {} receipts for status {} with ids: {}", + context.getFunctionName(), recoverResult.getReceiptList().size(), statusType, idList); + return recoverResult.getReceiptList(); + } catch (NoSuchElementException e) { + logger.error("[{}] Unexpected error during recover of failed receipt for status {}", + context.getFunctionName(), statusType, e); + return Collections.emptyList(); + } + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java index 5731823b..66a43301 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java @@ -1,113 +1,114 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.microsoft.azure.functions.*; -//import com.microsoft.azure.functions.annotation.*; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.helpdesk.exception.ReceiptNotFoundException; -//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.Collections; -//import java.util.List; -//import java.util.Optional; -// -//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.restoreReceipt; -// -///** -// * Azure Functions with HTTP Trigger. -// */ -//public class RecoverNotNotifiedReceipt { -// -// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceipt.class); -// -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverNotNotifiedReceipt() { -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// } -// -// RecoverNotNotifiedReceipt(ReceiptCosmosService receiptCosmosService) { -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked when a Http Trigger occurs. -// *

-// * It recovers the receipt with the specified biz event id -// *

-// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification -// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the -// * previous step ({@link ReceiptStatusType#GENERATED}). -// * -// * @return response with {@link HttpStatus#OK} if the operation succeeded -// */ -// @FunctionName("RecoverNotNotifiedReceipt") -// public HttpResponseMessage run( -// @HttpTrigger(name = "RecoverNotNotifiedTrigger", -// methods = {HttpMethod.POST}, -// route = "receipts/{event-id}/recover-not-notified", -// authLevel = AuthorizationLevel.ANONYMOUS) -// HttpRequestMessage> request, -// @BindingName("event-id") String eventId, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding> documentReceipts, -// final ExecutionContext context) { -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// -// if (eventId == null || eventId.isBlank()) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a valid biz-event id") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// Receipt receipt; -// try { -// receipt = this.receiptCosmosService.getReceipt(eventId); -// } catch (ReceiptNotFoundException e) { -// String responseMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); -// logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); -// return request -// .createResponseBuilder(HttpStatus.NOT_FOUND) -// .body(ProblemJson.builder() -// .title(HttpStatus.NOT_FOUND.name()) -// .detail(responseMsg) -// .status(HttpStatus.NOT_FOUND.value()) -// .build()) -// .build(); -// } -// -// if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { -// String responseMsg = String.format("The requested receipt with eventId %s is not in the expected status", -// receipt.getEventId()); -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) -// .detail(responseMsg) -// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) -// .build()) -// .build(); -// } -// -// Receipt restoredReceipt = restoreReceipt(receipt); -// -// documentReceipts.setValue(Collections.singletonList(restoredReceipt)); -// String responseMsg = String.format("Receipt with id %s and eventId %s restored in status %s with success", -// receipt.getId(), receipt.getEventId(), ReceiptStatusType.GENERATED); -// return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); -// } -//} +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.restoreReceipt; + + +/** + * Azure Functions with HTTP Trigger. + */ +public class RecoverNotNotifiedReceipt { + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceipt.class); + + private final ReceiptCosmosService receiptCosmosService; + + public RecoverNotNotifiedReceipt() { + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + } + + RecoverNotNotifiedReceipt(ReceiptCosmosService receiptCosmosService) { + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + *

+ * It recovers the receipt with the specified biz event id + *

+ * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link ReceiptStatusType#GENERATED}). + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverNotNotifiedReceipt") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverNotNotifiedTrigger", + methods = {HttpMethod.POST}, + route = "receipts/{event-id}/recover-not-notified", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("event-id") String eventId, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentReceipts, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + if (eventId == null || eventId.isBlank()) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid biz-event id") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + Receipt receipt; + try { + receipt = this.receiptCosmosService.getReceipt(eventId); + } catch (ReceiptNotFoundException e) { + String responseMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); + logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); + return request + .createResponseBuilder(HttpStatus.NOT_FOUND) + .body(ProblemJson.builder() + .title(HttpStatus.NOT_FOUND.name()) + .detail(responseMsg) + .status(HttpStatus.NOT_FOUND.value()) + .build()) + .build(); + } + + if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { + String responseMsg = String.format("The requested receipt with eventId %s is not in the expected status", + receipt.getEventId()); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(responseMsg) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + + Receipt restoredReceipt = restoreReceipt(receipt); + + documentReceipts.setValue(Collections.singletonList(restoredReceipt)); + String responseMsg = String.format("Receipt with id %s and eventId %s restored in status %s with success", + receipt.getId(), receipt.getEventId(), ReceiptStatusType.GENERATED); + return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); + } +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java index 9ac3343c..a259e76c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java @@ -1,116 +1,118 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.microsoft.azure.functions.*; -//import com.microsoft.azure.functions.annotation.AuthorizationLevel; -//import com.microsoft.azure.functions.annotation.CosmosDBOutput; -//import com.microsoft.azure.functions.annotation.FunctionName; -//import com.microsoft.azure.functions.annotation.HttpTrigger; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.helpdesk.model.ProblemJson; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.List; -//import java.util.Optional; -// -//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; -// -///** -// * Azure Functions with HTTP Trigger. -// */ -//public class RecoverNotNotifiedReceiptMassive { -// -// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptMassive.class); -// -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverNotNotifiedReceiptMassive() { -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// } -// -// RecoverNotNotifiedReceiptMassive(ReceiptCosmosService receiptCosmosService) { -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked when a Http Trigger occurs. -// *

-// * It recovers all receipt with the provided status. -// *

-// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification -// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the -// * previous step ({@link ReceiptStatusType#GENERATED}). -// * -// * @return response with {@link HttpStatus#OK} if the operation succeeded -// */ -// @FunctionName("RecoverNotNotifiedReceiptMassive") -// public HttpResponseMessage run( -// @HttpTrigger(name = "RecoverNotNotifiedMassiveTrigger", -// methods = {HttpMethod.POST}, -// route = "receipts/recover-not-notified", -// authLevel = AuthorizationLevel.ANONYMOUS) -// HttpRequestMessage> request, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding> documentReceipts, -// final ExecutionContext context) { -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// -// // Get named parameter -// String status = request.getQueryParameters().get("status"); -// if (status == null) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a status to recover") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// ReceiptStatusType statusType; -// try { -// statusType = ReceiptStatusType.valueOf(status); -// } catch (IllegalArgumentException e) { -// return request -// .createResponseBuilder(HttpStatus.BAD_REQUEST) -// .body(ProblemJson.builder() -// .title(HttpStatus.BAD_REQUEST.name()) -// .detail("Please pass a valid status to recover") -// .status(HttpStatus.BAD_REQUEST.value()) -// .build()) -// .build(); -// } -// -// if (!statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !statusType.equals(ReceiptStatusType.GENERATED)) { -// String responseMsg = String.format("The requested status to recover %s is not one of the expected status", -// statusType); -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) -// .detail(responseMsg) -// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) -// .build()) -// .build(); -// } -// -// List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); -// if (receiptList.isEmpty()) { -// return request.createResponseBuilder(HttpStatus.OK).body("No receipts restored").build(); -// } -// -// documentReceipts.setValue(receiptList); -// String msg = String.format("Restored %s receipt with success", receiptList.size()); -// return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); -// } -// -//} +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; + +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; + + +/** + * Azure Functions with HTTP Trigger. + */ +public class RecoverNotNotifiedReceiptMassive { + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptMassive.class); + + private final ReceiptCosmosService receiptCosmosService; + + public RecoverNotNotifiedReceiptMassive() { + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + } + + RecoverNotNotifiedReceiptMassive(ReceiptCosmosService receiptCosmosService) { + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked when a Http Trigger occurs. + *

+ * It recovers all receipt with the provided status. + *

+ * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link ReceiptStatusType#GENERATED}). + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverNotNotifiedReceiptMassive") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverNotNotifiedMassiveTrigger", + methods = {HttpMethod.POST}, + route = "receipts/recover-not-notified", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentReceipts, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + // Get named parameter + String status = request.getQueryParameters().get("status"); + if (status == null) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a status to recover") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + ReceiptStatusType statusType; + try { + statusType = ReceiptStatusType.valueOf(status); + } catch (IllegalArgumentException e) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid status to recover") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + if (!statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !statusType.equals(ReceiptStatusType.GENERATED)) { + String responseMsg = String.format("The requested status to recover %s is not one of the expected status", + statusType); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(responseMsg) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + + List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); + if (receiptList.isEmpty()) { + return request.createResponseBuilder(HttpStatus.OK).body("No receipts restored").build(); + } + + documentReceipts.setValue(receiptList); + String msg = String.format("Restored %s receipt with success", receiptList.size()); + return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java index 9d7b377d..ac47eb6c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java @@ -1,84 +1,86 @@ -//package it.gov.pagopa.receipt.pdf.datastore.helpdesk; -// -//import com.microsoft.azure.functions.ExecutionContext; -//import com.microsoft.azure.functions.OutputBinding; -//import com.microsoft.azure.functions.annotation.CosmosDBOutput; -//import com.microsoft.azure.functions.annotation.FunctionName; -//import com.microsoft.azure.functions.annotation.TimerTrigger; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.ReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.helpdesk.service.impl.ReceiptCosmosServiceImpl; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.time.LocalDateTime; -//import java.util.ArrayList; -//import java.util.List; -// -//import static it.gov.pagopa.receipt.pdf.helpdesk.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; -// -//public class RecoverNotNotifiedReceiptScheduled { -// -// private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("NOT_NOTIFIED_AUTORECOVER_ENABLED", "true")); -// -// private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptScheduled.class); -// -// private final ReceiptCosmosService receiptCosmosService; -// -// public RecoverNotNotifiedReceiptScheduled() { -// this.receiptCosmosService = new ReceiptCosmosServiceImpl(); -// } -// -// RecoverNotNotifiedReceiptScheduled(ReceiptCosmosService receiptCosmosService) { -// this.receiptCosmosService = receiptCosmosService; -// } -// -// /** -// * This function will be invoked on a scheduled basis. -// *

-// * It recovers all receipt with the provided status. -// *

-// * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification -// * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the -// * previous step ({@link ReceiptStatusType#GENERATED}). -// * -// */ -// @FunctionName("RecoverNotNotifiedTimerTriggerProcessor") -// public void processRecoverNotNotifiedScheduledTrigger( -// @TimerTrigger( -// name = "timerInfoNotNotified", -// schedule = "%TRIGGER_NOTIFY_REC_SCHEDULE%" -// ) -// String timerInfo, -// @CosmosDBOutput( -// name = "ReceiptDatastore", -// databaseName = "db", -// containerName = "receipts", -// connection = "COSMOS_RECEIPTS_CONN_STRING") -// OutputBinding> documentReceipts, -// final ExecutionContext context) { -// -// if (isEnabled) { -// -// logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); -// -// List receiptList = new ArrayList<>(); -// receiptList.addAll(process(context, ReceiptStatusType.IO_ERROR_TO_NOTIFY)); -// receiptList.addAll(process(context, ReceiptStatusType.GENERATED)); -// -// documentReceipts.setValue(receiptList); -// } -// -// } -// -// private List process(ExecutionContext context, ReceiptStatusType statusType) { -// List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); -// -// List idList = receiptList.parallelStream().map(Receipt::getId).toList(); -// logger.info("[{}] Recovered {} receipts for status {} with ids: {}", -// context.getFunctionName(), receiptList.size(), statusType, idList); -// return receiptList; -// } -// -//} +package it.gov.pagopa.receipt.pdf.datastore.helpdesk; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.TimerTrigger; + +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; + + +public class RecoverNotNotifiedReceiptScheduled { + + private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("NOT_NOTIFIED_AUTORECOVER_ENABLED", "true")); + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptScheduled.class); + + private final ReceiptCosmosService receiptCosmosService; + + public RecoverNotNotifiedReceiptScheduled() { + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + } + + RecoverNotNotifiedReceiptScheduled(ReceiptCosmosService receiptCosmosService) { + this.receiptCosmosService = receiptCosmosService; + } + + /** + * This function will be invoked on a scheduled basis. + *

+ * It recovers all receipt with the provided status. + *

+ * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link ReceiptStatusType#GENERATED}). + * + */ + @FunctionName("RecoverNotNotifiedTimerTriggerProcessor") + public void processRecoverNotNotifiedScheduledTrigger( + @TimerTrigger( + name = "timerInfoNotNotified", + schedule = "%TRIGGER_NOTIFY_REC_SCHEDULE%" + ) + String timerInfo, + @CosmosDBOutput( + name = "ReceiptDatastore", + databaseName = "db", + containerName = "receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentReceipts, + final ExecutionContext context) { + + if (isEnabled) { + + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + List receiptList = new ArrayList<>(); + receiptList.addAll(process(context, ReceiptStatusType.IO_ERROR_TO_NOTIFY)); + receiptList.addAll(process(context, ReceiptStatusType.GENERATED)); + + documentReceipts.setValue(receiptList); + } + + } + + private List process(ExecutionContext context, ReceiptStatusType statusType) { + List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); + + List idList = receiptList.parallelStream().map(Receipt::getId).toList(); + logger.info("[{}] Recovered {} receipts for status {} with ids: {}", + context.getFunctionName(), receiptList.size(), statusType, idList); + return receiptList; + } + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java new file mode 100644 index 00000000..b22c2ded --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.receipt.pdf.datastore.model; + +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class MassiveRecoverResult { + + private List receiptList; + private int errorCounter; +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java index 96f0641d..aee025f7 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java @@ -20,10 +20,10 @@ public interface BizEventToReceiptService { /** * Handles sending biz-events as message to queue and updates receipt's status * - * @param bizEvent Biz-event from CosmosDB + * @param bizEventList Biz-event list from CosmosDB * @param receipt Receipt to update */ - void handleSendMessageToQueue(BizEvent bizEvent, Receipt receipt); + void handleSendMessageToQueue(List bizEventList, Receipt receipt); /** * This method handles sending biz-events as messages to the cart queue. diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index 1c5823af..e889a32c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -77,16 +77,10 @@ public BizEventToReceiptServiceImpl(PDVTokenizerServiceRetryWrapper pdvTokenizer * {@inheritDoc} */ @Override - public void handleSendMessageToQueue(BizEvent bizEvent, Receipt receipt) { + public void handleSendMessageToQueue(List bizEventList, Receipt receipt) { //Encode biz-event to base64 string - String messageText = Base64.getMimeEncoder() - .encodeToString( - Objects.requireNonNull( - ObjectMapperUtils.writeValueAsString( - // Keep a list for backwards compatibility - Collections.singletonList(bizEvent) - )).getBytes(StandardCharsets.UTF_8) - ); + String messageText = Base64.getMimeEncoder().encodeToString( + Objects.requireNonNull(ObjectMapperUtils.writeValueAsString(bizEventList)).getBytes(StandardCharsets.UTF_8)); //Add message to the queue int statusCode; @@ -95,7 +89,12 @@ public void handleSendMessageToQueue(BizEvent bizEvent, Receipt receipt) { statusCode = sendMessageResult.getStatusCode(); } catch (Exception e) { statusCode = ReasonErrorCode.ERROR_QUEUE.getCode(); - logger.error("Sending BizEvent with id {} to queue failed", receipt.getEventId(), e); + if (bizEventList.size() == 1) { + logger.error("Sending BizEvent with id {} to queue failed", bizEventList.get(0).getId(), e); + } else { + logger.error("Failed to enqueue cart with id {}", + bizEventList.get(0).getTransactionDetails().getTransaction().getIdTransaction(), e); + } } if (statusCode != HttpStatus.CREATED.value()) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 320fa5e6..e1eea54f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -1,5 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.utils; +import com.azure.cosmos.models.FeedResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.ExecutionContext; import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; @@ -16,6 +17,7 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import org.slf4j.Logger; @@ -124,8 +126,7 @@ public static Receipt getEvent( tokenizeReceipt(bizEventToReceiptService, isCart ? listCart : Collections.singletonList(bizEvent), receipt); } receipt.setStatus(ReceiptStatusType.INSERTED); - bizEventToReceiptService.handleSendMessageToQueue(isCart ? listCart : - Collections.singletonList(bizEvent), receipt); + bizEventToReceiptService.handleSendMessageToQueue(isCart ? listCart : Collections.singletonList(bizEvent), receipt); if (receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT) { receipt.setInserted_at(System.currentTimeMillis()); receipt.setReasonErr(null); @@ -463,4 +464,39 @@ public static boolean isFromAuthenticatedOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } + + public static MassiveRecoverResult massiveRecoverByStatus( + ExecutionContext context, + BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient, + ReceiptCosmosService receiptCosmosService, + Logger logger, + ReceiptStatusType statusType) { + int errorCounter = 0; + List receiptList = new ArrayList<>(); + String continuationToken = null; + do { + Iterable> feedResponseIterator = + receiptCosmosService.getFailedReceiptByStatus(continuationToken, 100, statusType); + + for (FeedResponse page : feedResponseIterator) { + for (Receipt receipt : page.getResults()) { + try { + Receipt restored = getEvent(receipt.getEventId(), context, bizEventToReceiptService, + bizEventCosmosClient, receiptCosmosService, receipt, logger, receipt.getIsCart() != null ? + receipt.getIsCart() : false); + receiptList.add(restored); + } catch (Exception e) { + logger.error(e.getMessage(), e); + errorCounter++; + } + } + continuationToken = page.getContinuationToken(); + } + } while (continuationToken != null); + return MassiveRecoverResult.builder() + .receiptList(receiptList) + .errorCounter(errorCounter) + .build(); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java new file mode 100644 index 00000000..fee9f112 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java @@ -0,0 +1,52 @@ +package it.gov.pagopa.receipt.pdf.datastore.utils; + +import com.azure.cosmos.models.FeedResponse; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; + +import java.util.ArrayList; +import java.util.List; + +public class RecoverNotNotifiedReceiptUtils { + + public static Receipt restoreReceipt(Receipt receipt) { + receipt.setStatus(ReceiptStatusType.GENERATED); + receipt.setNotificationNumRetry(0); + receipt.setNotified_at(0); + + if (receipt.getReasonErr() != null) { + receipt.setReasonErr(null); + } + if (receipt.getReasonErrPayer() != null) { + receipt.setReasonErrPayer(null); + } + return receipt; + } + + public static List receiptMassiveRestore(ReceiptStatusType statusType, ReceiptCosmosService receiptCosmosService) { + + + List receiptList = new ArrayList<>(); + String continuationToken = null; + do { + + Iterable> feedResponseIterator = + receiptCosmosService.getNotNotifiedReceiptByStatus(continuationToken, 100, statusType); + + for (FeedResponse page : feedResponseIterator) { + for (Receipt receipt : page.getResults()) { + Receipt restoredReceipt = restoreReceipt(receipt); + receiptList.add(restoredReceipt); + } + continuationToken = page.getContinuationToken(); + + } + } while (continuationToken != null); + + + return receiptList; + } + + private RecoverNotNotifiedReceiptUtils() {} +} From 621c90fd8830b683547693c6cfe40ed21b90ffac Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 11:00:48 +0100 Subject: [PATCH 04/38] fix helm --- helm/values-dev.yaml | 16 ++++- helm/values-prod.yaml | 15 ++++- helm/values-uat.yaml | 15 ++++- .../{ => http}/ReceiptToReviewed.java | 4 +- .../{ => http}/RecoverFailedReceipt.java | 2 +- .../RecoverFailedReceiptMassive.java | 2 +- .../{ => http}/RecoverNotNotifiedReceipt.java | 2 +- .../RecoverNotNotifiedReceiptMassive.java | 2 +- .../RecoverFailedReceiptScheduled.java | 2 +- .../RecoverNotNotifiedReceiptScheduled.java | 2 +- .../utils/BizEventToReceiptUtils.java | 59 +++++++++---------- 11 files changed, 74 insertions(+), 47 deletions(-) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => http}/ReceiptToReviewed.java (95%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => http}/RecoverFailedReceipt.java (99%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => http}/RecoverFailedReceiptMassive.java (99%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => http}/RecoverNotNotifiedReceipt.java (98%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => http}/RecoverNotNotifiedReceiptMassive.java (98%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => schedule}/RecoverFailedReceiptScheduled.java (98%) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/{ => schedule}/RecoverNotNotifiedReceiptScheduled.java (98%) diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index 3d6d52fa..b6c6db1d 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -85,6 +85,7 @@ microservice-chart: fileConfig: {} envConfig: ENV: "dev" + TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" @@ -92,13 +93,14 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-d-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-d-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-d-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" - COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" + COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" + COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" - LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.uat.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" @@ -112,10 +114,19 @@ microservice-chart: OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + MAX_DATE_DIFF_MILLIS: "1800000" # 30min + MAX_DATE_DIFF_NOTIFY_MILLIS: "1800000" # 30min + TRIGGER_NOTIFY_REC_SCHEDULE: "0 0 */2 * * *" + RECOVER_FAILED_CRON: "0 0 */2 * * *" AZURE_FUNCTIONS_MESH_JAVA_OPTS: "-javaagent:/home/site/wwwroot/jmx_prometheus_javaagent-0.19.0.jar=12345:/home/site/wwwroot/config.yaml -javaagent:/home/site/wwwroot/opentelemetry-javaagent.jar -Xmx768m -XX:+UseG1GC" + FAILED_AUTORECOVER_ENABLED: "false" + NOT_NOTIFIED_AUTORECOVER_ENABLED: "false" + RECOVER_FAILED_MASSIVE_MAX_DAYS: "0" + RECOVER_NOT_NOTIFIED_MASSIVE_MAX_DAYS: "0" ECOMMERCE_FILTER_ENABLED: "false" ENABLE_CART: "true" AUTHENTICATED_CHANNELS: "IO,CHECKOUT,WISP,CHECKOUT_CART" + UNWANTED_REMITTANCE_INFO: "pagamento multibeneficiario,pagamento bpay" envFieldRef: APP_NAME: "metadata.labels['app.kubernetes.io/instance']" APP_VERSION: "metadata.labels['app.kubernetes.io/version']" @@ -123,6 +134,7 @@ microservice-chart: APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-d-connection-string" COSMOS_RECEIPTS_CONN_STRING: "cosmos-receipt-connection-string" RECEIPT_QUEUE_CONN_STRING: "receipts-storage-account-connection-string" + AzureWebJobsStorage: "receipts-storage-account-connection-string" COSMOS_BIZ_EVENT_CONN_STRING: "cosmos-biz-event-d-connection-string" COSMOS_RECEIPT_KEY: "cosmos-receipt-pkey" COSMOS_BIZ_EVENT_KEY: "cosmos-bizevent-pkey" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 42df86fc..71c2c1ab 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -85,6 +85,7 @@ microservice-chart: fileConfig: {} envConfig: ENV: "prod" + TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" @@ -92,13 +93,14 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-p-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-p-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-p-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" - COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" + COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" + COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" - LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" @@ -112,7 +114,15 @@ microservice-chart: OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + MAX_DATE_DIFF_MILLIS: "1800000" # 30min + MAX_DATE_DIFF_NOTIFY_MILLIS: "1800000" # 30min + TRIGGER_NOTIFY_REC_SCHEDULE: "0 0 */2 * * *" + RECOVER_FAILED_CRON: "0 0 */2 * * *" AZURE_FUNCTIONS_MESH_JAVA_OPTS: "-javaagent:/home/site/wwwroot/jmx_prometheus_javaagent-0.19.0.jar=12345:/home/site/wwwroot/config.yaml -Xmx768m -XX:+UseG1GC" + FAILED_AUTORECOVER_ENABLED: "false" + NOT_NOTIFIED_AUTORECOVER_ENABLED: "false" + RECOVER_FAILED_MASSIVE_MAX_DAYS: "0" + RECOVER_NOT_NOTIFIED_MASSIVE_MAX_DAYS: "0" ECOMMERCE_FILTER_ENABLED: "false" ENABLE_CART: "false" AUTHENTICATED_CHANNELS: "IO,CHECKOUT,WISP,CHECKOUT_CART" @@ -124,6 +134,7 @@ microservice-chart: APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-p-connection-string" COSMOS_RECEIPTS_CONN_STRING: "cosmos-receipt-connection-string" RECEIPT_QUEUE_CONN_STRING: "receipts-storage-account-connection-string" + AzureWebJobsStorage: "receipts-storage-account-connection-string" COSMOS_BIZ_EVENT_CONN_STRING: "cosmos-biz-event-p-connection-string" COSMOS_RECEIPT_KEY: "cosmos-receipt-pkey" COSMOS_BIZ_EVENT_KEY: "cosmos-bizevent-pkey" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 21bf48e4..b1d810a3 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -85,6 +85,7 @@ microservice-chart: fileConfig: {} envConfig: ENV: "uat" + TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" @@ -92,13 +93,14 @@ microservice-chart: CART_QUEUE_TOPIC: "pagopa-u-weu-receipts-queue-cart-receipt-waiting-4-gen" COSMOS_RECEIPT_SERVICE_ENDPOINT: "https://pagopa-u-weu-receipts-ds-cosmos-account.documents.azure.com:443/" COSMOS_BIZ_EVENT_SERVICE_ENDPOINT: "https://pagopa-u-weu-bizevents-ds-cosmos-account.documents.azure.com:443/" - COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_DB_NAME: "db" COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" + COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" + COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" COSMOS_BIZ_EVENT_CONTAINER_NAME: "biz-events" - LIST_VALID_ORIGINS: "IO,CHECKOUT,WISP,CHECKOUT_CART" PDV_TOKENIZER_BASE_PATH: "https://api.uat.tokenizer.pdv.pagopa.it/tokenizer/v1" PDV_TOKENIZER_INITIAL_INTERVAL: "200" PDV_TOKENIZER_MULTIPLIER: "2.0" @@ -112,7 +114,15 @@ microservice-chart: OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317" OTEL_LOGS_EXPORTER: none OTEL_TRACES_SAMPLER: "always_on" + MAX_DATE_DIFF_MILLIS: "1800000" # 30min + MAX_DATE_DIFF_NOTIFY_MILLIS: "1800000" # 30min + TRIGGER_NOTIFY_REC_SCHEDULE: "0 0 */2 * * *" + RECOVER_FAILED_CRON: "0 0 */2 * * *" AZURE_FUNCTIONS_MESH_JAVA_OPTS: "-javaagent:/home/site/wwwroot/jmx_prometheus_javaagent-0.19.0.jar=12345:/home/site/wwwroot/config.yaml -Xmx768m -XX:+UseG1GC" + FAILED_AUTORECOVER_ENABLED: "false" + NOT_NOTIFIED_AUTORECOVER_ENABLED: "false" + RECOVER_FAILED_MASSIVE_MAX_DAYS: "0" + RECOVER_NOT_NOTIFIED_MASSIVE_MAX_DAYS: "0" ECOMMERCE_FILTER_ENABLED: "false" ENABLE_CART: "true" AUTHENTICATED_CHANNELS: "IO,CHECKOUT,WISP,CHECKOUT_CART" @@ -124,6 +134,7 @@ microservice-chart: APPLICATIONINSIGHTS_CONNECTION_STRING: "ai-u-connection-string" COSMOS_RECEIPTS_CONN_STRING: "cosmos-receipt-connection-string" RECEIPT_QUEUE_CONN_STRING: "receipts-storage-account-connection-string" + AzureWebJobsStorage: "receipts-storage-account-connection-string" COSMOS_BIZ_EVENT_CONN_STRING: "cosmos-biz-event-u-connection-string" COSMOS_RECEIPT_KEY: "cosmos-receipt-pkey" COSMOS_BIZ_EVENT_KEY: "cosmos-bizevent-pkey" diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java similarity index 95% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java index c67918ed..9c257c73 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/ReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java @@ -1,9 +1,7 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; import com.microsoft.azure.functions.annotation.*; -import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java similarity index 99% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 1d07f0aa..84ef9bac 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.*; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java similarity index 99% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index e19e5c35..990a01ae 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; import com.microsoft.azure.functions.annotation.AuthorizationLevel; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java similarity index 98% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index 66a43301..0bb41539 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; import com.microsoft.azure.functions.annotation.*; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java similarity index 98% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index a259e76c..b4ce48d1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; import com.microsoft.azure.functions.annotation.AuthorizationLevel; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java similarity index 98% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java index 30332d3d..5c93277b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.OutputBinding; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java similarity index 98% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java index ac47eb6c..a7f39f4b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/RecoverNotNotifiedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.helpdesk; +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.OutputBinding; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index e1eea54f..38b11d1a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -41,11 +41,6 @@ public class BizEventToReceiptUtils { "UNWANTED_REMITTANCE_INFO", "pagamento multibeneficiario,pagamento bpay").split(",")); private static final String ECOMMERCE = "CHECKOUT"; - private static final List listOrigin; - - static { - listOrigin = Arrays.asList(System.getenv().getOrDefault("LIST_VALID_ORIGINS", "IO,CHECKOUT,WISP,CHECKOUT_CART").split(",")); - } private BizEventToReceiptUtils() { } @@ -437,33 +432,33 @@ public static boolean isValidChannelOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } - public static boolean isFromAuthenticatedOrigin(BizEvent bizEvent) { - if (bizEvent.getTransactionDetails() == null) { - return false; - } - - var transactionDetails = bizEvent.getTransactionDetails(); - var transaction = transactionDetails.getTransaction(); - var info = transactionDetails.getInfo(); - var user = transactionDetails.getUser(); - - String origin = (transaction != null) ? transaction.getOrigin() : null; - String clientId = (info != null) ? info.getClientId() : null; - UserType userType = (user != null) ? user.getType() : null; - - boolean originMatches = origin != null && listOrigin.contains(origin); - boolean clientIdMatches = clientId != null && listOrigin.contains(clientId); - - boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); - boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); - boolean isRegisteredUser = UserType.REGISTERED.equals(userType); - - if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { - return false; - } - - return originMatches || clientIdMatches; - } +// public static boolean isFromAuthenticatedOrigin(BizEvent bizEvent) { +// if (bizEvent.getTransactionDetails() == null) { +// return false; +// } +// +// var transactionDetails = bizEvent.getTransactionDetails(); +// var transaction = transactionDetails.getTransaction(); +// var info = transactionDetails.getInfo(); +// var user = transactionDetails.getUser(); +// +// String origin = (transaction != null) ? transaction.getOrigin() : null; +// String clientId = (info != null) ? info.getClientId() : null; +// UserType userType = (user != null) ? user.getType() : null; +// +// boolean originMatches = origin != null && AUTHENTICATED_CHANNELS.contains(origin); +// boolean clientIdMatches = clientId != null && AUTHENTICATED_CHANNELS.contains(clientId); +// +// boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); +// boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); +// boolean isRegisteredUser = UserType.REGISTERED.equals(userType); +// +// if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { +// return false; +// } +// +// return originMatches || clientIdMatches; +// } public static MassiveRecoverResult massiveRecoverByStatus( ExecutionContext context, From d142f5cb1646108e6a05e38b36606d7b9820bafc Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 11:15:11 +0100 Subject: [PATCH 05/38] imported unit tests --- .../datastore/entity/cart/CartForReceipt.java | 4 + .../helpdesk/http/ReceiptToReviewedTest.java | 164 +++++ .../http/RecoverFailedReceiptMassiveTest.java | 293 ++++++++ .../http/RecoverFailedReceiptTest.java | 634 ++++++++++++++++++ .../RecoverNotNotifiedReceiptMassiveTest.java | 277 ++++++++ .../http/RecoverNotNotifiedReceiptTest.java | 249 +++++++ .../RecoverFailedReceiptScheduledTest.java | 189 ++++++ ...ecoverNotNotifiedReceiptScheduledTest.java | 123 ++++ 8 files changed, 1933 insertions(+) create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java index 6ff841f5..60a4f12c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java @@ -6,6 +6,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.Set; + @AllArgsConstructor @NoArgsConstructor @Builder(toBuilder = true) @@ -17,8 +19,10 @@ public class CartForReceipt { private String version; private Payload payload; private CartStatusType status; + private Set cartPaymentId; private int numRetry; private int notificationNumRetry; + private Integer totalNotice; private ReasonError reasonErr; private long inserted_at; private long generated_at; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java new file mode 100644 index 00000000..55f34d54 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -0,0 +1,164 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ReceiptToReviewedTest { + + private static final String BIZ_EVENT_ID = "valid_biz_event_id"; + private final ExecutionContext executionContextMock = mock(ExecutionContext.class); + private ReceiptToReviewed function; + @Mock + private ReceiptCosmosClient receiptCosmosClient; + @Captor + private ArgumentCaptor receiptErrorCaptor; + @Mock + HttpRequestMessage> request; + @SuppressWarnings("unchecked") + OutputBinding documentdb = (OutputBinding) spy(OutputBinding.class); + + @Test + void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFoundException { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + + ReceiptError receiptError = ReceiptError.builder() + .bizEventId(BIZ_EVENT_ID) + .status(ReceiptErrorStatusType.TO_REVIEW) + .build(); + when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); + + function = spy(new ReceiptToReviewed(receiptCosmosClient)); + + // test execution + AtomicReference responseMessage = new AtomicReference<>(); + assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); + assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); + + verify(documentdb).setValue(receiptErrorCaptor.capture()); + ReceiptError captured = receiptErrorCaptor.getValue(); + assertEquals(BIZ_EVENT_ID, captured.getBizEventId()); + assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); + } + + @Test + void requestWithValidCartSaveReceiptErrorInReviewed() throws ReceiptNotFoundException, CartNotFoundException { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + + ReceiptError receiptError = ReceiptError.builder() + .bizEventId(BIZ_EVENT_ID) + .status(ReceiptErrorStatusType.TO_REVIEW) + .build(); + when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); + + function = spy(new ReceiptToReviewed(receiptCosmosClient)); + + // test execution + AtomicReference responseMessage = new AtomicReference<>(); + assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); + assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); + + verify(documentdb).setValue(receiptErrorCaptor.capture()); + ReceiptError captured = receiptErrorCaptor.getValue(); + assertEquals(BIZ_EVENT_ID, captured.getBizEventId()); + assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); + } + + @Test + void requestWithValidBizEventIdButReceiptNotFound() throws ReceiptNotFoundException { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + + when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + function = spy(new ReceiptToReviewed(receiptCosmosClient)); + + // test execution + AtomicReference responseMessage = new AtomicReference<>(); + assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock))); + assertEquals(HttpStatus.NOT_FOUND, responseMessage.get().getStatus()); + + verifyNoInteractions(documentdb); + } + + @Test + void requestWithValidBizEventIdButReceiptWrongStatusReturnsInternalServerError() throws ReceiptNotFoundException { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + + when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(ReceiptError.builder() + .bizEventId(BIZ_EVENT_ID) + .status(ReceiptErrorStatusType.REQUEUED) + .build()); + + function = spy(new ReceiptToReviewed(receiptCosmosClient)); + + // test execution + AtomicReference responseMessage = new AtomicReference<>(); + assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock))); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, responseMessage.get().getStatus()); + + verifyNoInteractions(documentdb); + } + + @Test + void requestWithoutEventIdReturnsBadRequest() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + + function = spy(new ReceiptToReviewed(receiptCosmosClient)); + + // test execution + AtomicReference responseMessage = new AtomicReference<>(); + assertDoesNotThrow(() -> responseMessage.set(function.run(request, null, documentdb, executionContextMock))); + assertEquals(HttpStatus.BAD_REQUEST, responseMessage.get().getStatus()); + + verifyNoInteractions(documentdb); + } + + private CartForReceipt generateCart() { + CartForReceipt cart = new CartForReceipt(); + cart.setId("1"); + cart.setStatus(CartStatusType.FAILED); + cart.setTotalNotice(1); + cart.setCartPaymentId(new HashSet<>(new ArrayList<>( + List.of(new String[]{"valid_biz_event_id"})))); + return cart; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java new file mode 100644 index 00000000..304e9150 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -0,0 +1,293 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.azure.cosmos.models.ModelBridgeInternal; +import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RecoverFailedReceiptMassiveTest { + + private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; + private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; + private final String EVENT_ID = "a valid id"; + + @Mock + private ExecutionContext contextMock; + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + @Mock + private BizEventCosmosClientImpl bizEventCosmosClientMock; + @Mock + private BizEventToReceiptService bizEventToReceiptServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Captor + private ArgumentCaptor> receiptCaptor; + + @Spy + private OutputBinding> documentdb; + + private AutoCloseable closeable; + + private RecoverFailedReceiptMassive sut; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + sut = spy(new RecoverFailedReceiptMassive(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); + + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) + .thenReturn(Collections.singletonList(ModelBridgeInternal + .createFeedResponse(Collections.singletonList(createFailedReceipt()), + Collections.emptyMap()))); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent(EVENT_ID)); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertEquals(1, receiptCaptor.getValue().size()); + Receipt captured = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptMassiveFailMissingStatusParam() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentdb, never()).setValue(receiptCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptMassiveFailInvalidStatusParam() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", "INVALID_STATUS")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentdb, never()).setValue(receiptCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptMassivePartialOK() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); + + List receiptList = new ArrayList<>(); + receiptList.add(createFailedReceipt()); + Receipt receipt = createFailedReceipt(); + receipt.setEventData(null); + receiptList.add(receipt); + + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) + .thenReturn(Collections.singletonList(ModelBridgeInternal + .createFeedResponse(receiptList, Collections.emptyMap()))); + + doThrow(PDVTokenizerException.class) + .when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); + + when(bizEventCosmosClientMock.getBizEventDocument(anyString())) + .thenReturn(generateValidBizEvent(EVENT_ID)) + .thenReturn(generateValidBizEvent("a valid id 2")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.MULTI_STATUS.value(), problemJson.getStatus()); + assertEquals("Partial OK", problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertEquals(1, receiptCaptor.getValue().size()); + Receipt captured = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptMassiveFailNoSuchElementInIterator() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); + + Iterable iterableMock = mock(Iterable.class); + Iterator iteratorMock = mock(Iterator.class); + when(iterableMock.iterator()).thenReturn(iteratorMock); + when(iteratorMock.hasNext()).thenThrow(new NoSuchElementException("")); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) + .thenReturn(iterableMock); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentdb, never()).setValue(receiptCaptor.capture()); + } + + private BizEvent generateValidBizEvent(String eventId){ + BizEvent item = new BizEvent(); + + Payer payer = new Payer(); + payer.setEntityUniqueIdentifierValue("AAAAAA00A00A000P"); + Debtor debtor = new Debtor(); + debtor.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); + + TransactionDetails transactionDetails = new TransactionDetails(); + Transaction transaction = new Transaction(); + transaction.setCreationDate(String.valueOf(LocalDateTime.now())); + transactionDetails.setTransaction(transaction); + + PaymentInfo paymentInfo = new PaymentInfo(); + paymentInfo.setTotalNotice("1"); + + item.setEventStatus(BizEventStatusType.DONE); + item.setId(eventId); + item.setPayer(payer); + item.setDebtor(debtor); + item.setTransactionDetails(transactionDetails); + item.setPaymentInfo(paymentInfo); + + return item; + } + + private Receipt createFailedReceipt() { + Receipt receipt = new Receipt(); + + receipt.setId("a valid id"); + receipt.setEventId("a valid id"); + receipt.setVersion("1"); + + receipt.setStatus(ReceiptStatusType.FAILED); + EventData eventData = new EventData(); + eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + receipt.setEventData(eventData); + + CartItem item = new CartItem(); + List cartItems = Collections.singletonList(item); + eventData.setCart(cartItems); + + return receipt; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java new file mode 100644 index 00000000..6383bba5 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -0,0 +1,634 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.azure.core.http.rest.Response; +import com.azure.cosmos.models.FeedResponse; +import com.azure.storage.queue.models.SendMessageResult; +import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptQueueClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.PDVTokenizerServiceRetryWrapper; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RecoverFailedReceiptTest { + + private final String PAYER_FISCAL_CODE = "AAAAAA00A00A000D"; + private final String DEBTOR_FISCAL_CODE = "AAAAAA00A00A000P"; + private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; + private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; + private final String EVENT_ID = "a valid id"; + + public static final String HTTP_MESSAGE_ERROR = "an error occured"; + + @Mock + private ExecutionContext contextMock; + @Mock + private PDVTokenizerServiceRetryWrapper pdvTokenizerServiceMock; + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + @Mock + private ReceiptQueueClient queueClientMock; + @Mock + private BizEventCosmosClientImpl bizEventCosmosClientMock; + + @Mock + private ReceiptCosmosClientImpl receiptCosmosClient; + + @Mock + private HttpRequestMessage> requestMock; + + @Captor + private ArgumentCaptor receiptCaptor; + + @Spy + private OutputBinding documentdb; + + private AutoCloseable closeable; + + private RecoverFailedReceipt sut; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + BizEventToReceiptServiceImpl receiptService = new BizEventToReceiptServiceImpl( + pdvTokenizerServiceMock, queueClientMock, bizEventCosmosClientMock, receiptCosmosClient); + sut = spy(new RecoverFailedReceipt(receiptService, bizEventCosmosClientMock, receiptCosmosServiceMock)); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + @SneakyThrows + void requestOnValidBizEventShouldCreateRequest() { + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent("1")); + + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + @SneakyThrows + void requestOnValidCartShouldCreateRequest() { + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); + + FeedResponse feedResponseMock = mock(FeedResponse.class); + List receiptList = Collections.singletonList(generateValidBizEvent("1")); + when(feedResponseMock.getResults()).thenReturn(receiptList); + doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) + .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); + + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + @SneakyThrows + void requestOnValidBizEventTransactionDetailsShouldCreateRequest() { + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) + .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEventWithTDetails("1")); + + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + + @Test + void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException { + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent("1")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + void requestOnValidCartAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException { + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + FeedResponse feedResponseMock = mock(FeedResponse.class); + List receiptList = Collections.singletonList(generateValidBizEvent("1")); + when(feedResponseMock.getResults()).thenReturn(receiptList); + doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) + .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + @SneakyThrows + void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken() { + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + Receipt receipt = createFailedReceipt(); + receipt.setEventData(null); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent("1")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(receiptCaptor.capture()); + Receipt captured = receiptCaptor.getValue(); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + void requestWithMissingBizEventOnRequestIdShouldReturnBitFound() throws BizEventNotFoundException { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenThrow(BizEventNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); + assertNotNull(response.getBody()); + } + + @Test + void recoverFailedReceiptFailMissingEventId() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, null, documentdb, contextMock)); + + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + } + + @Test + void runDiscardedWithEventNotDONE() throws BizEventNotFoundException { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateNotDoneBizEvent()); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verifyNoInteractions(receiptCosmosServiceMock); + verifyNoInteractions(queueClientMock); + } + + @Test + void generateAnonymousDebtorBizEvent() throws BizEventNotFoundException { + BizEvent bizEvent = generateAnonymDebtorBizEvent(); + bizEvent.setPayer(null); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(bizEvent); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verifyNoInteractions(receiptCosmosServiceMock); + verifyNoInteractions(queueClientMock); + } + + @Test + void runDiscardedWithEventNull() throws BizEventNotFoundException { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(null); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verifyNoInteractions(receiptCosmosServiceMock); + verifyNoInteractions(queueClientMock); + } + + @Test + void runDiscardedWithCartEventWithInvalidTotalNotice() throws BizEventNotFoundException { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent("invalid string")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verifyNoInteractions(receiptCosmosServiceMock); + verifyNoInteractions(queueClientMock); + } + + @Test + void runDiscardedWithInvalidCartAmounts() throws BizEventNotFoundException { + BizEvent bizEvent = generateValidBizEvent(null); + bizEvent.getTransactionDetails().getTransaction().setAmount(10); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(bizEvent); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verifyNoInteractions(receiptCosmosServiceMock); + verifyNoInteractions(queueClientMock); + + } + + @Test + @SneakyThrows + void errorTokenizingFiscalCodes() { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateValidBizEvent("1")); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + lenient().when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenThrow(new PDVTokenizerException(HTTP_MESSAGE_ERROR, org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR)); + + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verifyNoInteractions(queueClientMock); + } + + @Test + @SneakyThrows + void errorAddingMessageToQueue() { + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) + .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + + Response queueResponse = mock(Response.class); + when(queueResponse.getStatusCode()).thenReturn(HttpStatus.FORBIDDEN.value()); + when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + + Receipt receipt = createFailedReceipt(); + receipt.setEventData(null); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateValidBizEvent("1")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + } + + private BizEvent generateValidBizEvent(String totalNotice){ + BizEvent item = new BizEvent(); + + Payer payer = new Payer(); + payer.setEntityUniqueIdentifierValue(PAYER_FISCAL_CODE); + Debtor debtor = new Debtor(); + debtor.setEntityUniqueIdentifierValue(DEBTOR_FISCAL_CODE); + + TransactionDetails transactionDetails = new TransactionDetails(); + Transaction transaction = new Transaction(); + transaction.setCreationDate(String.valueOf(LocalDateTime.now())); + transaction.setAmount(10000); + transactionDetails.setTransaction(transaction); + transactionDetails.setOrigin("INFO"); + + PaymentInfo paymentInfo = new PaymentInfo(); + paymentInfo.setTotalNotice(totalNotice); + paymentInfo.setAmount("100.0"); + + item.setEventStatus(BizEventStatusType.DONE); + item.setId(EVENT_ID); + item.setPayer(payer); + item.setDebtor(debtor); + item.setTransactionDetails(transactionDetails); + item.setPaymentInfo(paymentInfo); + + return item; + } + + private BizEvent generateValidBizEventWithTDetails(String totalNotice){ + BizEvent item = new BizEvent(); + + Debtor debtor = new Debtor(); + debtor.setEntityUniqueIdentifierValue(DEBTOR_FISCAL_CODE); + + TransactionDetails transactionDetails = new TransactionDetails(); + transactionDetails.setInfo(InfoTransaction.builder().clientId("IO").build()); + Transaction transaction = new Transaction(); + transaction.setCreationDate(String.valueOf(LocalDateTime.now())); + transactionDetails.setTransaction(transaction); + transactionDetails.setUser(User.builder().fiscalCode(PAYER_FISCAL_CODE).build()); + + PaymentInfo paymentInfo = new PaymentInfo(); + paymentInfo.setTotalNotice(totalNotice); + + item.setEventStatus(BizEventStatusType.DONE); + item.setId(EVENT_ID); + item.setDebtor(debtor); + item.setTransactionDetails(transactionDetails); + item.setPaymentInfo(paymentInfo); + + return item; + } + + private Receipt createFailedReceipt() { + Receipt receipt = new Receipt(); + + receipt.setId("a valid id"); + receipt.setEventId("a valid id"); + + receipt.setVersion("1"); + + receipt.setStatus(ReceiptStatusType.FAILED); + EventData eventData = new EventData(); + eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + receipt.setEventData(eventData); + + CartItem item = new CartItem(); + List cartItems = Collections.singletonList(item); + eventData.setCart(cartItems); + + return receipt; + } + + private BizEvent generateAnonymDebtorBizEvent(){ + BizEvent item = new BizEvent(); + + Payer payer = new Payer(); + payer.setEntityUniqueIdentifierValue(PAYER_FISCAL_CODE); + Debtor debtor = new Debtor(); + debtor.setEntityUniqueIdentifierValue("ANONIMO"); + + TransactionDetails transactionDetails = new TransactionDetails(); + Transaction transaction = new Transaction(); + transaction.setCreationDate(String.valueOf(LocalDateTime.now())); + transactionDetails.setTransaction(transaction); + + PaymentInfo paymentInfo = new PaymentInfo(); + paymentInfo.setTotalNotice("1"); + + item.setEventStatus(BizEventStatusType.DONE); + item.setId(EVENT_ID); + item.setPayer(payer); + item.setDebtor(debtor); + item.setTransactionDetails(transactionDetails); + item.setPaymentInfo(paymentInfo); + + return item; + } + + private BizEvent generateNotDoneBizEvent(){ + BizEvent item = new BizEvent(); + item.setEventStatus(BizEventStatusType.NA); + return item; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java new file mode 100644 index 00000000..14333fa4 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java @@ -0,0 +1,277 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.azure.cosmos.models.FeedResponse; +import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RecoverNotNotifiedReceiptMassiveTest { + + private static final String EVENT_ID = "eventId"; + + private final ExecutionContext executionContextMock = mock(ExecutionContext.class); + + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Spy + private OutputBinding> documentReceipts; + + @Captor + private ArgumentCaptor> receiptCaptor; + + private RecoverNotNotifiedReceiptMassive sut; + + private AutoCloseable closeable; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + sut = spy(new RecoverNotNotifiedReceiptMassive(receiptCosmosServiceMock)); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + void recoverNotNotifiedReceiptMassiveForIOErrorToNotifySuccess() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); + + FeedResponse feedResponseMock = mock(FeedResponse.class); + List receiptList = getReceiptList(ReceiptStatusType.IO_ERROR_TO_NOTIFY); + when(feedResponseMock.getResults()).thenReturn(receiptList); + when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) + .thenReturn(Collections.singletonList(feedResponseMock)); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts).setValue(receiptCaptor.capture()); + + assertEquals(receiptList.size(), receiptCaptor.getValue().size()); + Receipt captured1 = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); + assertEquals(EVENT_ID, captured1.getEventId()); + assertEquals(0, captured1.getNotificationNumRetry()); + assertNull(captured1.getReasonErr()); + assertNull(captured1.getReasonErrPayer()); + Receipt captured2 = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); + assertEquals(EVENT_ID, captured2.getEventId()); + assertEquals(0, captured2.getNotificationNumRetry()); + assertNull(captured2.getReasonErr()); + assertNull(captured2.getReasonErrPayer()); + } + + @Test + void recoverNotNotifiedReceiptMassiveForGeneratedSuccess() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.GENERATED.name())); + + FeedResponse feedResponseMock = mock(FeedResponse.class); + List receiptList = getReceiptList(ReceiptStatusType.GENERATED); + when(feedResponseMock.getResults()).thenReturn(receiptList); + when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) + .thenReturn(Collections.singletonList(feedResponseMock)); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts).setValue(receiptCaptor.capture()); + + assertEquals(receiptList.size(), receiptCaptor.getValue().size()); + Receipt captured1 = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); + assertEquals(EVENT_ID, captured1.getEventId()); + assertEquals(0, captured1.getNotificationNumRetry()); + assertNull(captured1.getReasonErr()); + assertNull(captured1.getReasonErrPayer()); + Receipt captured2 = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); + assertEquals(EVENT_ID, captured2.getEventId()); + assertEquals(0, captured2.getNotificationNumRetry()); + assertNull(captured2.getReasonErr()); + assertNull(captured2.getReasonErrPayer()); + } + + @Test + void recoverNotNotifiedReceiptMassiveSuccessWithNoReceiptUpdated() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); + + FeedResponse feedResponseMock = mock(FeedResponse.class); + when(feedResponseMock.getResults()).thenReturn(Collections.emptyList()); + when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) + .thenReturn(Collections.singletonList(feedResponseMock)); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailMissingQueryParam() { + when(requestMock.getQueryParameters()).thenReturn(Collections.emptyMap()); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailInvalidStatusType() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", "INVALID_STATUS")); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailInvalidRestoreStatusRequested() { + when(requestMock.getQueryParameters()) + .thenReturn(Collections.singletonMap("status", ReceiptStatusType.FAILED.name())); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + + + private Receipt buildReceipt(ReceiptStatusType statusType) { + return Receipt.builder() + .eventId(EVENT_ID) + .status(statusType) + .reasonErr(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .reasonErrPayer(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .numRetry(0) + .notificationNumRetry(6) + .inserted_at(0) + .generated_at(0) + .notified_at(0) + .build(); + } + + private List getReceiptList(ReceiptStatusType statusType) { + List receiptList = new ArrayList<>(); + Receipt receipt1 = buildReceipt(statusType); + Receipt receipt2 = buildReceipt(statusType); + receiptList.add(receipt1); + receiptList.add(receipt2); + return receiptList; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java new file mode 100644 index 00000000..d033443d --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -0,0 +1,249 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RecoverNotNotifiedReceiptTest { + + private static final String EVENT_ID = "eventId"; + + private final ExecutionContext executionContextMock = mock(ExecutionContext.class); + + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Spy + private OutputBinding> documentReceipts; + + @Captor + private ArgumentCaptor> receiptCaptor; + + private RecoverNotNotifiedReceipt sut; + + private AutoCloseable closeable; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + sut = spy(new RecoverNotNotifiedReceipt(receiptCosmosServiceMock)); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + void recoverNotNotifiedReceiptSuccess() throws ReceiptNotFoundException { + Receipt receipt = buildReceipt(); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts).setValue(receiptCaptor.capture()); + + assertEquals(1, receiptCaptor.getValue().size()); + Receipt captured = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(0, captured.getNotificationNumRetry()); + assertNull(captured.getReasonErr()); + assertNull(captured.getReasonErrPayer()); + } + + @Test + void recoverNotNotifiedCartReceiptSuccess() throws ReceiptNotFoundException, CartNotFoundException { + Receipt receipt = buildReceipt(); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts).setValue(receiptCaptor.capture()); + + assertEquals(1, receiptCaptor.getValue().size()); + Receipt captured = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured.getStatus()); + assertEquals(EVENT_ID, captured.getEventId()); + assertEquals(0, captured.getNotificationNumRetry()); + assertNull(captured.getReasonErr()); + assertNull(captured.getReasonErrPayer()); + } + + @Test + void recoverReceiptFailForMissingEventId() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, "", documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailReceiptNotFound() throws ReceiptNotFoundException { + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailReceiptInInsertedButOnlyGenerated() throws ReceiptNotFoundException { + Receipt receipt = new Receipt(); + receipt.setStatus(ReceiptStatusType.INSERTED); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + @Test + void recoverReceiptFailReceiptInInsertedButOnlyIOErrorToNotify() throws ReceiptNotFoundException { + Receipt receipt = new Receipt(); + receipt.setStatus(ReceiptStatusType.INSERTED); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + ProblemJson problemJson = (ProblemJson) response.getBody(); + assertNotNull(problemJson); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); + assertNotNull(problemJson.getDetail()); + + verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + } + + private Receipt buildReceipt() { + return Receipt.builder() + .eventId(EVENT_ID) + .status(ReceiptStatusType.IO_ERROR_TO_NOTIFY) + .reasonErr(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .reasonErrPayer(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .numRetry(0) + .notificationNumRetry(6) + .inserted_at(0) + .generated_at(0) + .notified_at(0) + .build(); + } + + private CartForReceipt generateCart() { + CartForReceipt cart = new CartForReceipt(); + cart.setId("1"); + cart.setStatus(CartStatusType.FAILED); + cart.setTotalNotice(1); + cart.setCartPaymentId(new HashSet<>(new ArrayList<>( + List.of(new String[]{"eventId"})))); + return cart; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java new file mode 100644 index 00000000..76c77fd9 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -0,0 +1,189 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.azure.cosmos.models.ModelBridgeInternal; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(SystemStubsExtension.class) +class RecoverFailedReceiptScheduledTest { + + private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; + private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; + private final String EVENT_ID_1 = "a valid id 1"; + private final String EVENT_ID_2 = "a valid id 2"; + private final String EVENT_ID_3 = "a valid id 3"; + + @Mock + private ExecutionContext contextMock; + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + @Mock + private BizEventCosmosClientImpl bizEventCosmosClientMock; + @Mock + private BizEventToReceiptService bizEventToReceiptServiceMock; + + @Captor + private ArgumentCaptor> receiptCaptor; + + @Spy + private OutputBinding> documentdb; + + @SystemStub + private EnvironmentVariables environment; + + private AutoCloseable closeable; + + private RecoverFailedReceiptScheduled sut; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException { + sut = spy(new RecoverFailedReceiptScheduled(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.FAILED))) + .thenReturn(Collections.singletonList(ModelBridgeInternal + .createFeedResponse(Collections.singletonList( + createFailedReceipt(EVENT_ID_1, ReceiptStatusType.FAILED)), + Collections.emptyMap()))); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.INSERTED))) + .thenReturn(Collections.singletonList(ModelBridgeInternal + .createFeedResponse(Collections.singletonList( + createFailedReceipt(EVENT_ID_2, ReceiptStatusType.INSERTED)), + Collections.emptyMap()))); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.NOT_QUEUE_SENT))) + .thenReturn(Collections.singletonList(ModelBridgeInternal + .createFeedResponse(Collections.singletonList( + createFailedReceipt(EVENT_ID_3, ReceiptStatusType.NOT_QUEUE_SENT)), + Collections.emptyMap()))); + + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_1)) + .thenReturn(generateValidBizEvent(EVENT_ID_1)); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_2)) + .thenReturn(generateValidBizEvent(EVENT_ID_2)); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_3)) + .thenReturn(generateValidBizEvent(EVENT_ID_3)); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertEquals(3, receiptCaptor.getValue().size()); + + Receipt captured = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + + captured = receiptCaptor.getValue().get(1); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + + captured = receiptCaptor.getValue().get(2); + assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); + assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertNotNull(captured.getEventData().getCart()); + assertEquals(1, captured.getEventData().getCart().size()); + } + + @Test + void recoverFailedReceiptScheduledDisabled() throws BizEventNotFoundException { + environment.set("FAILED_AUTORECOVER_ENABLED", "false"); + sut = spy(new RecoverFailedReceiptScheduled(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); + + assertEquals("false", System.getenv("FAILED_AUTORECOVER_ENABLED")); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb, never()).setValue(any()); + verify(receiptCosmosServiceMock, never()).getFailedReceiptByStatus(any(), any(), any()); + verify(bizEventCosmosClientMock, never()).getBizEventDocument(anyString()); + + } + + private BizEvent generateValidBizEvent(String eventId) { + BizEvent item = new BizEvent(); + + Payer payer = new Payer(); + payer.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); + Debtor debtor = new Debtor(); + debtor.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); + + TransactionDetails transactionDetails = new TransactionDetails(); + Transaction transaction = new Transaction(); + transaction.setCreationDate(String.valueOf(LocalDateTime.now())); + transactionDetails.setTransaction(transaction); + + PaymentInfo paymentInfo = new PaymentInfo(); + paymentInfo.setTotalNotice("1"); + + item.setEventStatus(BizEventStatusType.DONE); + item.setId(eventId); + item.setPayer(payer); + item.setDebtor(debtor); + item.setTransactionDetails(transactionDetails); + item.setPaymentInfo(paymentInfo); + + return item; + } + + private Receipt createFailedReceipt(String id, ReceiptStatusType statusType) { + Receipt receipt = new Receipt(); + + receipt.setId(id); + receipt.setEventId(id); + receipt.setVersion("1"); + + receipt.setStatus(statusType); + EventData eventData = new EventData(); + eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + receipt.setEventData(eventData); + + CartItem item = new CartItem(); + List cartItems = Collections.singletonList(item); + eventData.setCart(cartItems); + + return receipt; + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java new file mode 100644 index 00000000..303c67e7 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java @@ -0,0 +1,123 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.azure.cosmos.models.FeedResponse; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class RecoverNotNotifiedReceiptScheduledTest { + + private static final String EVENT_ID = "eventId"; + + private final ExecutionContext executionContextMock = mock(ExecutionContext.class); + + @Mock + private ReceiptCosmosService receiptCosmosServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Spy + private OutputBinding> documentReceipts; + + @Captor + private ArgumentCaptor> receiptCaptor; + + private RecoverNotNotifiedReceiptScheduled sut; + + private AutoCloseable closeable; + + @BeforeEach + public void openMocks() { + closeable = MockitoAnnotations.openMocks(this); + sut = spy(new RecoverNotNotifiedReceiptScheduled(receiptCosmosServiceMock)); + } + + @AfterEach + public void releaseMocks() throws Exception { + closeable.close(); + } + + @Test + public void scheduledTriggerShouldReturnAllValidReceiptsProcessed() { + FeedResponse feedResponseMock = mock(FeedResponse.class); + List receiptList = getReceiptList(ReceiptStatusType.IO_ERROR_TO_NOTIFY); + when(feedResponseMock.getResults()).thenReturn(receiptList); + when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), eq(ReceiptStatusType.IO_ERROR_TO_NOTIFY))) + .thenReturn(Collections.singletonList(feedResponseMock)); + + FeedResponse feedResponseGenMock = mock(FeedResponse.class); + List receiptGenList = getReceiptList(ReceiptStatusType.GENERATED); + when(feedResponseGenMock.getResults()).thenReturn(receiptGenList); + when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), eq(ReceiptStatusType.GENERATED))) + .thenReturn(Collections.singletonList(feedResponseGenMock)); + + sut.processRecoverNotNotifiedScheduledTrigger("info", documentReceipts, executionContextMock); + + verify(documentReceipts).setValue(receiptCaptor.capture()); + + assertEquals(receiptList.size()+receiptGenList.size(), receiptCaptor.getValue().size()); + Receipt captured1 = receiptCaptor.getValue().get(0); + assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); + assertEquals(EVENT_ID, captured1.getEventId()); + assertEquals(0, captured1.getNotificationNumRetry()); + assertNull(captured1.getReasonErr()); + assertNull(captured1.getReasonErrPayer()); + Receipt captured2 = receiptCaptor.getValue().get(1); + assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); + assertEquals(EVENT_ID, captured2.getEventId()); + assertEquals(0, captured2.getNotificationNumRetry()); + assertNull(captured2.getReasonErr()); + assertNull(captured2.getReasonErrPayer()); + } + + + private Receipt buildReceipt(ReceiptStatusType statusType) { + return Receipt.builder() + .eventId(EVENT_ID) + .status(statusType) + .reasonErr(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .reasonErrPayer(ReasonError.builder() + .code(500) + .message("error message") + .build()) + .numRetry(0) + .notificationNumRetry(6) + .inserted_at(0) + .generated_at(0) + .notified_at(0) + .build(); + } + + private List getReceiptList(ReceiptStatusType statusType) { + List receiptList = new ArrayList<>(); + Receipt receipt1 = buildReceipt(statusType); + Receipt receipt2 = buildReceipt(statusType); + receiptList.add(receipt1); + receiptList.add(receipt2); + return receiptList; + } + +} \ No newline at end of file From ce857fcbdbe2f2811386be45ce6db6a286028f93 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 12:46:04 +0100 Subject: [PATCH 06/38] fix unit tests --- .../utils/BizEventToReceiptUtils.java | 77 ++++++++++++++++++- .../helpdesk/http/ReceiptToReviewedTest.java | 22 +++--- .../http/RecoverFailedReceiptTest.java | 18 ++++- .../BizEventToReceiptServiceImplTest.java | 5 +- 4 files changed, 105 insertions(+), 17 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 38b11d1a..7adfc58f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -76,7 +76,7 @@ public static Receipt getEvent( return null; } } - } else if (isBizEventInvalid(bizEvent, context, logger)) { + } else if (isBizEventInvalidHelpDesk(bizEvent, context, logger)) { return null; } @@ -251,6 +251,81 @@ public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext cont return false; } + /** + * Checks if the instance of Biz Event is in status DONE and contains all the required information to process + * in the receipt generation + * + * @param bizEvent BizEvent to validate + * @param context Function context + * @param logger Function logger + * @return boolean to determine if the proposed event is invalid + */ + public static boolean isBizEventInvalidHelpDesk(BizEvent bizEvent, ExecutionContext context, Logger logger) { + + if (bizEvent == null) { + logger.error("[{}] event is null", context.getFunctionName()); + return true; + } + + if (!BizEventStatusType.DONE.equals(bizEvent.getEventStatus()) && !BizEventStatusType.INGESTED.equals(bizEvent.getEventStatus())) { + logger.error("[{}] event with id {} discarded because in status {}", + context.getFunctionName(), bizEvent.getId(), bizEvent.getEventStatus()); + return true; + } + + if (!hasValidFiscalCode(bizEvent)) { + logger.error("[{}] event with id {} discarded because debtor's and payer's identifiers are missing or not valid", + context.getFunctionName(), bizEvent.getId()); + return true; + } + + if (Boolean.TRUE.equals(ECOMMERCE_FILTER_ENABLED) + && bizEvent.getTransactionDetails() != null + && bizEvent.getTransactionDetails().getInfo() != null + && ECOMMERCE.equals(bizEvent.getTransactionDetails().getInfo().getClientId()) + ) { + logger.error("[{}] event with id {} discarded because from e-commerce {}", + context.getFunctionName(), bizEvent.getId(), bizEvent.getTransactionDetails().getInfo().getClientId()); + return true; + } + + if (!isCartMod1(bizEvent)) { + logger.error("[{}] event with id {} contain either an invalid amount value," + + " or it is a legacy cart element", + context.getFunctionName(), bizEvent.getId()); + return true; + } + + + if (bizEvent.getPaymentInfo() != null) { + String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); + + if (totalNotice != null) { + int intTotalNotice; + + try { + intTotalNotice = Integer.parseInt(totalNotice); + + } catch (NumberFormatException e) { + logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", + context.getFunctionName(), bizEvent.getId(), + totalNotice, + e); + return true; + } + + if (intTotalNotice > 1) { + logger.error("[{}] event with id {} discarded because is part of a payment cart ({} total notice)", + context.getFunctionName(), bizEvent.getId(), + intTotalNotice); + return true; + } + } + } + + return false; + } + private static boolean hasValidFiscalCode(BizEvent bizEvent) { boolean isValidDebtor = false; boolean isValidPayer = false; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index 55f34d54..17d65992 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -1,13 +1,13 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -34,7 +34,7 @@ class ReceiptToReviewedTest { private final ExecutionContext executionContextMock = mock(ExecutionContext.class); private ReceiptToReviewed function; @Mock - private ReceiptCosmosClient receiptCosmosClient; + private ReceiptCosmosServiceImpl receiptCosmosService; @Captor private ArgumentCaptor receiptErrorCaptor; @Mock @@ -53,9 +53,9 @@ void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFound .bizEventId(BIZ_EVENT_ID) .status(ReceiptErrorStatusType.TO_REVIEW) .build(); - when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); + when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); - function = spy(new ReceiptToReviewed(receiptCosmosClient)); + function = spy(new ReceiptToReviewed(receiptCosmosService)); // test execution AtomicReference responseMessage = new AtomicReference<>(); @@ -79,9 +79,9 @@ void requestWithValidCartSaveReceiptErrorInReviewed() throws ReceiptNotFoundExce .bizEventId(BIZ_EVENT_ID) .status(ReceiptErrorStatusType.TO_REVIEW) .build(); - when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); + when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); - function = spy(new ReceiptToReviewed(receiptCosmosClient)); + function = spy(new ReceiptToReviewed(receiptCosmosService)); // test execution AtomicReference responseMessage = new AtomicReference<>(); @@ -101,9 +101,9 @@ void requestWithValidBizEventIdButReceiptNotFound() throws ReceiptNotFoundExcept return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(request).createResponseBuilder(any(HttpStatus.class)); - when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - function = spy(new ReceiptToReviewed(receiptCosmosClient)); + function = spy(new ReceiptToReviewed(receiptCosmosService)); // test execution AtomicReference responseMessage = new AtomicReference<>(); @@ -120,12 +120,12 @@ void requestWithValidBizEventIdButReceiptWrongStatusReturnsInternalServerError() return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(request).createResponseBuilder(any(HttpStatus.class)); - when(receiptCosmosClient.getReceiptError(BIZ_EVENT_ID)).thenReturn(ReceiptError.builder() + when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(ReceiptError.builder() .bizEventId(BIZ_EVENT_ID) .status(ReceiptErrorStatusType.REQUEUED) .build()); - function = spy(new ReceiptToReviewed(receiptCosmosClient)); + function = spy(new ReceiptToReviewed(receiptCosmosService)); // test execution AtomicReference responseMessage = new AtomicReference<>(); @@ -142,7 +142,7 @@ void requestWithoutEventIdReturnsBadRequest() { return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(request).createResponseBuilder(any(HttpStatus.class)); - function = spy(new ReceiptToReviewed(receiptCosmosClient)); + function = spy(new ReceiptToReviewed(receiptCosmosService)); // test execution AtomicReference responseMessage = new AtomicReference<>(); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 6383bba5..4e911227 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -4,8 +4,10 @@ import com.azure.cosmos.models.FeedResponse; import com.azure.storage.queue.models.SendMessageResult; import com.microsoft.azure.functions.*; +import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptQueueClient; import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartQueueClientImpl; import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; @@ -57,15 +59,17 @@ class RecoverFailedReceiptTest { @Mock private ReceiptCosmosService receiptCosmosServiceMock; @Mock + private CartReceiptsCosmosClient cartReceiptsCosmosClientMock; + @Mock private ReceiptQueueClient queueClientMock; @Mock private BizEventCosmosClientImpl bizEventCosmosClientMock; - @Mock private ReceiptCosmosClientImpl receiptCosmosClient; - @Mock private HttpRequestMessage> requestMock; + @Mock + private CartQueueClientImpl cartQueueClientMock; @Captor private ArgumentCaptor receiptCaptor; @@ -81,7 +85,13 @@ class RecoverFailedReceiptTest { public void openMocks() { closeable = MockitoAnnotations.openMocks(this); BizEventToReceiptServiceImpl receiptService = new BizEventToReceiptServiceImpl( - pdvTokenizerServiceMock, queueClientMock, bizEventCosmosClientMock, receiptCosmosClient); + pdvTokenizerServiceMock, + receiptCosmosClient, + cartReceiptsCosmosClientMock, + bizEventCosmosClientMock, + queueClientMock, + cartQueueClientMock + ); sut = spy(new RecoverFailedReceipt(receiptService, bizEventCosmosClientMock, receiptCosmosServiceMock)); } @@ -425,7 +435,7 @@ void runDiscardedWithEventNull() throws BizEventNotFoundException { } @Test - void runDiscardedWithCartEventWithInvalidTotalNotice() throws BizEventNotFoundException { + void runDiscardedEventWithInvalidTotalNotice() throws BizEventNotFoundException { when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) .thenReturn(generateValidBizEvent("invalid string")); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImplTest.java index f5a48a24..91d140b3 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImplTest.java @@ -41,6 +41,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl.FISCAL_CODE_ANONYMOUS; @@ -120,7 +121,9 @@ void run_KO_handleSendMessageToQueue() { Receipt receipt = new Receipt(); - assertDoesNotThrow(() -> sut.handleSendMessageToQueue(any(), receipt)); + List events = Collections.singletonList(new BizEvent()); + + assertDoesNotThrow(() -> sut.handleSendMessageToQueue(events, receipt)); assertNotNull(receipt); assertEquals(ReceiptStatusType.NOT_QUEUE_SENT, receipt.getStatus()); From f50ace34046ccc32afade997489e09464cc8ed63 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 12:47:42 +0100 Subject: [PATCH 07/38] fix unit tests --- .../java/it/gov/pagopa/receipt/pdf/datastore/HealthTest.java | 2 +- src/test/java/it/gov/pagopa/receipt/pdf/datastore/InfoTest.java | 2 +- .../pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java | 2 +- .../helpdesk/http/RecoverFailedReceiptMassiveTest.java | 2 +- .../pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java | 2 +- .../helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java | 2 +- .../datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java | 2 +- .../pdf/datastore/{util => utils}/HttpResponseMessageMock.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename src/test/java/it/gov/pagopa/receipt/pdf/datastore/{util => utils}/HttpResponseMessageMock.java (97%) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/HealthTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/HealthTest.java index e62a3f06..4dd9aec8 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/HealthTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/HealthTest.java @@ -4,7 +4,7 @@ import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/InfoTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/InfoTest.java index b22a6ae4..032bb158 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/InfoTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/InfoTest.java @@ -5,7 +5,7 @@ import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import it.gov.pagopa.receipt.pdf.datastore.model.AppInfo; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index 17d65992..11f4b6eb 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -8,7 +8,7 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java index 304e9150..88560876 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -14,7 +14,7 @@ import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 4e911227..9149399a 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -22,7 +22,7 @@ import it.gov.pagopa.receipt.pdf.datastore.service.PDVTokenizerServiceRetryWrapper; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java index 14333fa4..837ea378 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java @@ -7,7 +7,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index d033443d..1f9ba1f2 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -10,7 +10,7 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.util.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/util/HttpResponseMessageMock.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/utils/HttpResponseMessageMock.java similarity index 97% rename from src/test/java/it/gov/pagopa/receipt/pdf/datastore/util/HttpResponseMessageMock.java rename to src/test/java/it/gov/pagopa/receipt/pdf/datastore/utils/HttpResponseMessageMock.java index c9cae12c..09423389 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/util/HttpResponseMessageMock.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/utils/HttpResponseMessageMock.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.receipt.pdf.datastore.util; +package it.gov.pagopa.receipt.pdf.datastore.utils; import com.microsoft.azure.functions.HttpResponseMessage; From e3aece880585ccd1c18488084bf007a5cd250d7f Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 16 Dec 2025 13:15:42 +0100 Subject: [PATCH 08/38] optimized ReceiptCosmosClient --- .../datastore/client/ReceiptCosmosClient.java | 30 ++- .../client/impl/ReceiptCosmosClientImpl.java | 173 ++++++------------ 2 files changed, 83 insertions(+), 120 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java index 315db715..42fc0342 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java @@ -20,12 +20,31 @@ public interface ReceiptCosmosClient { */ Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundException; + /** + * Retrieve receiptError document from CosmosDB database + * + * @param bizEventId BizEvent ID + * @return ReceiptError found + * @throws ReceiptNotFoundException If the document isn't found + */ + ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException; + + /** + * Retrieve failed receipt documents from CosmosDB database + * + * @param continuationToken Paged query continuation token + * @return receipt documents + */ Iterable> getFailedReceiptDocuments(String continuationToken, Integer pageSize); + /** + * Save Receipts on CosmosDB database + * + * @param receipt Receipts to save + * @return receipt documents + */ CosmosItemResponse saveReceipts(Receipt receipt); - ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException; - /** * Retrieve the not notified receipt documents with {@link ReceiptStatusType#GENERATED} * @@ -53,6 +72,13 @@ public interface ReceiptCosmosClient { */ Iterable> getInsertedReceiptDocuments(String continuationToken, Integer pageSize); + /** + * Retrieve receipt document from CosmosDB database + * + * @param messageId IO Message id + * @return io message document + * @throws IoMessageNotFoundException in case no receipt has been found with the given messageId + */ IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java index bd280836..e5f18d44 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java @@ -18,6 +18,8 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; +import java.util.Iterator; +import java.util.Optional; /** * Client for the CosmosDB database @@ -33,7 +35,6 @@ public class ReceiptCosmosClientImpl implements ReceiptCosmosClient { private static final String DOCUMENT_NOT_FOUND_ERR_MSG = "Document not found in the defined container"; - // TODO env var private final String millisDiff = System.getenv("MAX_DATE_DIFF_MILLIS"); private final String millisNotifyDif = System.getenv("MAX_DATE_DIFF_NOTIFY_MILLIS"); private final String numDaysRecoverFailed = System.getenv().getOrDefault("RECOVER_FAILED_MASSIVE_MAX_DAYS", "0"); @@ -68,84 +69,38 @@ public static ReceiptCosmosClientImpl getInstance() { */ @Override public Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundException { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); - - //Build query - String query = String.format("SELECT * FROM c WHERE c.eventId = '%s'", eventId); - - //Query the container - CosmosPagedIterable queryResponse = cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class); - - if (queryResponse.iterator().hasNext()) { - return queryResponse.iterator().next(); - } - throw new ReceiptNotFoundException("Document not found in the defined container"); + return getDocumentByFilter(containerId, "eventId", eventId, Receipt.class) + .orElseThrow(() -> new ReceiptNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } /** - * Retrieve failed receipt documents from CosmosDB database - * - * @param continuationToken Paged query continuation token - * @return receipt documents + * {@inheritDoc} */ @Override - public Iterable> getFailedReceiptDocuments(String continuationToken, Integer pageSize) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); - - //Build query - String query = "SELECT *CosmosPagedIterable FROM c WHERE c.status = 'FAILED'"; - - //Query the container - return cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) - .iterableByPage(continuationToken, pageSize); - + public ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException { + return getDocumentByFilter(containerReceiptErrorId, "bizEventId", bizEventId, ReceiptError.class) + .orElseThrow(() -> new ReceiptNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } /** - * Save Receipts on CosmosDB database - * - * @param receipt Receipts to save - * @return receipt documents + * {@inheritDoc} */ @Override - public CosmosItemResponse saveReceipts(Receipt receipt) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); - - return cosmosContainer.createItem(receipt); + public Iterable> getFailedReceiptDocuments(String continuationToken, Integer pageSize) { + String query = "SELECT * FROM c WHERE c.status = 'FAILED'"; + return executePagedQuery(containerId, query, Receipt.class, continuationToken, pageSize); } /** - * Retrieve receiptError document from CosmosDB database - * - * @param bizEventId BizEvent ID - * @return ReceiptError found - * @throws ReceiptNotFoundException If the document isn't found + * {@inheritDoc} */ @Override - public ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException { + public CosmosItemResponse saveReceipts(Receipt receipt) { CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerReceiptErrorId); - - //Build query - String query = "SELECT * FROM c WHERE c.bizEventId = " + "'" + bizEventId + "'"; - - //Query the container - CosmosPagedIterable queryResponse = cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), ReceiptError.class); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); - if (queryResponse.iterator().hasNext()) { - return queryResponse.iterator().next(); - } - throw new ReceiptNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG); + return cosmosContainer.createItem(receipt); } /** @@ -153,20 +108,14 @@ public ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundEx */ @Override public Iterable> getGeneratedReceiptDocuments(String continuationToken, Integer pageSize) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + OffsetDateTime currentDateTime = OffsetDateTime.now(); + long now = currentDateTime.toInstant().toEpochMilli(); + long daysAgo = currentDateTime.truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(); - //Build query - String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.generated_at >= %s AND ( %s - c.generated_at) >= %s)", - ReceiptStatusType.GENERATED, - OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( - Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(), - OffsetDateTime.now().toInstant().toEpochMilli(), millisNotifyDif); + String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.generated_at >= %s AND ( %s - c.generated_at) >= %s)", + ReceiptStatusType.GENERATED, daysAgo, now, millisNotifyDif); - //Query the container - return cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) - .iterableByPage(continuationToken,pageSize); + return executePagedQuery(containerId, query, Receipt.class, continuationToken, pageSize); } /** @@ -174,26 +123,12 @@ public Iterable> getGeneratedReceiptDocuments(String conti */ @Override public Iterable> getIOErrorToNotifyReceiptDocuments(String continuationToken, Integer pageSize) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + long daysAgo = OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(); - //Build query - // String query = String.format("SELECT * FROM c WHERE c.status = '%s' AND c.generated_at >= %s OFFSET 0 LIMIT %s", - // ReceiptStatusType.IO_ERROR_TO_NOTIFY, - // OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( - // Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(), - // recordsLimitRecoverNotNotified - // ); String query = String.format("SELECT * FROM c WHERE c.status = '%s' AND c.generated_at >= %s", - ReceiptStatusType.IO_ERROR_TO_NOTIFY, - OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( - Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli() - ); + ReceiptStatusType.IO_ERROR_TO_NOTIFY, daysAgo); - //Query the container - return cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) - .iterableByPage(continuationToken,pageSize); + return executePagedQuery(containerId, query, Receipt.class, continuationToken, pageSize); } /** @@ -201,46 +136,48 @@ public Iterable> getIOErrorToNotifyReceiptDocuments(String */ @Override public Iterable> getInsertedReceiptDocuments(String continuationToken, Integer pageSize) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + OffsetDateTime currentDateTime = OffsetDateTime.now(); + long now = currentDateTime.toInstant().toEpochMilli(); + long daysAgo = currentDateTime.truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverFailed)).toInstant().toEpochMilli(); - //Build query - String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.inserted_at >= %s " + - "AND ( %s - c.inserted_at) >= %s)", - ReceiptStatusType.INSERTED, - OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays( - Long.parseLong(numDaysRecoverFailed)).toInstant().toEpochMilli(), - OffsetDateTime.now().toInstant().toEpochMilli(), millisDiff); + String query = String.format( + "SELECT * FROM c WHERE (c.status = '%s' AND c.inserted_at >= %s AND ( %s - c.inserted_at) >= %s)", + ReceiptStatusType.INSERTED, daysAgo, now, millisDiff); - //Query the container - return cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class) - .iterableByPage(continuationToken,pageSize); + return executePagedQuery(containerId, query, Receipt.class, continuationToken, pageSize); } /** - * Retrieve receipt document from CosmosDB database - * - * @param messageId IO Message id - * @return io message document - * @throws IoMessageNotFoundException in case no receipt has been found with the given messageId + * {@inheritDoc} */ @Override public IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException { + return getDocumentByFilter(containerMessageId, "messageId", messageId, IOMessage.class) + .orElseThrow(() -> new IoMessageNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); + } + + /** + * PRIVATE METHODS + */ + + private Optional getDocumentByFilter(String containerId, String propertyName, String propertyValue, Class classType) { CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerMessageId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); - //Build query - String query = String.format("SELECT * FROM c WHERE c.messageId = '%s'", messageId); + String query = String.format("SELECT * FROM c WHERE c.%s = '%s'", propertyName, propertyValue); - //Query the container - CosmosPagedIterable queryResponse = cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), IOMessage.class); + // use stream() to convert iterable and find first element + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), classType) + .stream() + .findFirst(); + } - if (queryResponse.iterator().hasNext()) { - return queryResponse.iterator().next(); - } - throw new IoMessageNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG); + private Iterable> executePagedQuery(String containerName, String query, Class classType, String continuationToken, Integer pageSize) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + return cosmosDatabase.getContainer(containerName) + .queryItems(query, new CosmosQueryRequestOptions(), classType) + .iterableByPage(continuationToken, pageSize); } } From 111f101a1ccf35104b31b1e5f00de0ee059352fb Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 18 Dec 2025 12:22:52 +0100 Subject: [PATCH 09/38] RecoverFailedReceipt --- .../BizEventBadRequestException.java | 31 ++++ .../exception/BizEventException.java | 59 ++++++ .../exception/BizEventNotFoundException.java | 9 +- .../exception/UnableToQueueException.java | 24 --- .../helpdesk/http/RecoverFailedReceipt.java | 68 ++++--- .../utils/BizEventToReceiptUtils.java | 173 +++--------------- .../impl/ReceiptCosmosClientImplTest.java | 12 +- .../http/RecoverFailedReceiptTest.java | 9 +- 8 files changed, 178 insertions(+), 207 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventException.java delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/UnableToQueueException.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java new file mode 100644 index 00000000..c6209d79 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java @@ -0,0 +1,31 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +import com.microsoft.azure.functions.HttpStatus; +import lombok.Getter; + +/** + * Thrown in case no receipt is found in the CosmosDB container + */ +public class BizEventBadRequestException extends BizEventException { + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public BizEventBadRequestException(String message) { + super(message, HttpStatus.BAD_REQUEST); + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public BizEventBadRequestException(String message, Throwable cause) { + super(message, cause, HttpStatus.BAD_REQUEST); + } +} + + diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventException.java new file mode 100644 index 00000000..a8486eb6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventException.java @@ -0,0 +1,59 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +import com.microsoft.azure.functions.HttpStatus; +import lombok.Getter; + +/** + * Thrown in case of BizEvent exception + */ +@Getter +public class BizEventException extends Exception { + + private final HttpStatus httpStatus; + + /** + * Constructs new exception with provided message + * + * @param message Detail message + */ + public BizEventException(String message) { + super(message); + this.httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + } + + /** + * Constructs new exception with provided message and HTTP status code + * + * @param message Detail message + * @param httpStatus HTTP status code + */ + public BizEventException(String message, HttpStatus httpStatus) { + super(message); + this.httpStatus = httpStatus; + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public BizEventException(String message, Throwable cause) { + super(message, cause); + this.httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + } + + /** + * Constructs new exception with provided message, cause and HTTP status code + * + * @param message Detail message + * @param cause Exception thrown + * @param httpStatus HTTP status code + */ + public BizEventException(String message, Throwable cause, HttpStatus httpStatus) { + super(message, cause); + this.httpStatus = httpStatus; + } +} + + diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java index 809222ec..8ff4a4cf 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java @@ -1,9 +1,12 @@ package it.gov.pagopa.receipt.pdf.datastore.exception; +import com.microsoft.azure.functions.HttpStatus; +import lombok.Getter; + /** * Thrown in case no receipt is found in the CosmosDB container */ -public class BizEventNotFoundException extends Exception { +public class BizEventNotFoundException extends BizEventException { /** * Constructs new exception with provided message and cause @@ -11,7 +14,7 @@ public class BizEventNotFoundException extends Exception { * @param message Detail message */ public BizEventNotFoundException(String message) { - super(message); + super(message, HttpStatus.NOT_FOUND); } /** @@ -21,7 +24,7 @@ public BizEventNotFoundException(String message) { * @param cause Exception thrown */ public BizEventNotFoundException(String message, Throwable cause) { - super(message, cause); + super(message, cause, HttpStatus.NOT_FOUND); } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/UnableToQueueException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/UnableToQueueException.java deleted file mode 100644 index 11be2753..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/UnableToQueueException.java +++ /dev/null @@ -1,24 +0,0 @@ -package it.gov.pagopa.receipt.pdf.datastore.exception; - -public class UnableToQueueException extends Exception { - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - */ - public UnableToQueueException(String message) { - super(message); - } - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - * @param cause Exception thrown - */ - public UnableToQueueException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 84ef9bac..2602ab6b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -7,8 +7,10 @@ import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; @@ -48,6 +50,15 @@ public RecoverFailedReceipt(){ /** * This function will be invoked when an Http Trigger occurs. + * The function is responsible for retrieving receipts that are in a FAILED, INSERTED and NOT_QUEUE_SENT state. + * For a single receipt, the function should: + * - try to retrieve the biz event -> if it doesn't find it, error + * - check that it's a valid biz: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error + * - check that it's not a cart biz: BizEventToReceiptUtils.getTotalNotice() == 1 -> if cart, error + * - check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error + * - recreate the receipt from the biz: BizEventToReceiptUtils.createReceipt() + * - if everything is OK, it updates the receipt on the cosmos. BizEventToReceiptService.handleSaveReceipt() + * - if everything is OK, send it to the queue BizEventToReceiptService.handleSendMessageToQueue() *

* It recovers the receipt with the specified biz event id that has the following status: * - ({@link ReceiptStatusType#INSERTED}) @@ -75,39 +86,36 @@ public HttpResponseMessage run ( final ExecutionContext context) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - if (eventId == null || eventId.isBlank()) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid biz-event id") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - - Boolean isCart = Boolean.parseBoolean(request.getQueryParameters().getOrDefault( - "isCart", "false")); - try { Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, - this.bizEventCosmosClient, this.receiptCosmosService, null, logger, isCart); + this.bizEventCosmosClient, this.receiptCosmosService, null, logger); - documentdb.setValue(receipt); - String responseMsg = String.format("Receipt with eventId %s recovered", eventId); - return request.createResponseBuilder(HttpStatus.OK) - .body(responseMsg) - .build(); - - } catch (BizEventNotFoundException exception) { + if (BizEventToReceiptUtils.isReceiptStatusValid(receipt)) { + documentdb.setValue(receipt); + String responseMsg = String.format("Receipt with eventId %s recovered", eventId); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + } else { + documentdb.setValue(receipt); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(receipt.getReasonErr().getMessage()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + } catch (BizEventNotFoundException | BizEventBadRequestException exception) { String msg = String.format("Unable to retrieve the biz-event with id %s", eventId); logger.error(msg, exception); return request - .createResponseBuilder(HttpStatus.NOT_FOUND) + .createResponseBuilder(exception.getHttpStatus()) .body(ProblemJson.builder() - .title(HttpStatus.NOT_FOUND.name()) + .title(exception.getHttpStatus().name()) .detail(msg) - .status(HttpStatus.NOT_FOUND.value()) + .status(exception.getHttpStatus().value()) .build()) .build(); } catch (PDVTokenizerException | JsonProcessingException e) { @@ -120,6 +128,16 @@ public HttpResponseMessage run ( .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) .build()) .build(); + } catch (ReceiptNotFoundException e) { + logger.error(e.getMessage(), e); + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail(e.getMessage()) + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); } } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 7adfc58f..2eb50086 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -14,6 +14,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; @@ -52,115 +53,42 @@ public static Receipt getEvent( BizEventCosmosClient bizEventCosmosClient, ReceiptCosmosService receiptCosmosService, Receipt receipt, - Logger logger, - Boolean isCart - ) throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { + Logger logger + ) throws BizEventNotFoundException, BizEventBadRequestException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { - List listCart = null; - BizEvent bizEvent; + BizEvent bizEvent = bizEventCosmosClient.getBizEventDocument(eventId); - if (isCart) { - listCart = bizEventToReceiptService.getCartBizEventsById(eventId); - bizEvent = listCart.get(0); - } else { - bizEvent = bizEventCosmosClient.getBizEventDocument(eventId); + if (isBizEventInvalid(bizEvent, context, logger)) { + throw new BizEventBadRequestException("BizEvent not valid"); } - if (isCart) { - Integer intTotalNotice = Integer.parseInt(bizEvent.getPaymentInfo().getTotalNotice()); - if (!intTotalNotice.equals(listCart.size())) { - return null; - } - for (BizEvent event : listCart) { - if (isBizEventInvalid(event, context, logger)) { - return null; - } - } - } else if (isBizEventInvalidHelpDesk(bizEvent, context, logger)) { - return null; + if (!hasValidTotalNotice(bizEvent, context, logger)) { + throw new BizEventBadRequestException("BizEvent has not a valid total notice"); } - if (receipt == null) { - try { - receipt = receiptCosmosService.getReceipt(eventId); - } catch (ReceiptNotFoundException e) { - receipt = BizEventToReceiptUtils.createReceipt(bizEvent, - bizEventToReceiptService, logger); - EventData eventData = receipt.getEventData(); - if (isCart) { - AtomicReference amount = new AtomicReference<>(BigDecimal.ZERO); - List cartItems = new ArrayList<>(); - listCart.forEach(event -> { - BigDecimal amountExtracted = getAmount(bizEvent); - amount.updateAndGet(v -> v.add(amountExtracted)); - cartItems.add( - CartItem.builder() - .payeeName(bizEvent.getCreditor() != null ? - bizEvent.getCreditor().getCompanyName() : null) - .subject(getItemSubject(bizEvent)) - .build()); - }); - - if (!amount.get().equals(BigDecimal.ZERO)) { - eventData.setAmount(formatAmount(amount.get().toString())); - } - - eventData.setCart(cartItems); - } - receipt.setStatus(ReceiptStatusType.FAILED); - } + receipt = receiptCosmosService.getReceipt(eventId); } + // check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error if (receipt != null && ( receipt.getStatus().equals(ReceiptStatusType.FAILED) || - receipt.getStatus().equals(ReceiptStatusType.INSERTED) || - receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) + receipt.getStatus().equals(ReceiptStatusType.INSERTED) || + receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) )) { - if (receipt.getEventData() == null || receipt.getEventData().getDebtorFiscalCode() == null) { - tokenizeReceipt(bizEventToReceiptService, isCart ? listCart : Collections.singletonList(bizEvent), receipt); - } - receipt.setStatus(ReceiptStatusType.INSERTED); - bizEventToReceiptService.handleSendMessageToQueue(isCart ? listCart : Collections.singletonList(bizEvent), receipt); - if (receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT) { - receipt.setInserted_at(System.currentTimeMillis()); - receipt.setReasonErr(null); - receipt.setReasonErrPayer(null); - } - return receipt; - } - return null; - } + // recreate the receipt from the biz + receipt = createReceipt(bizEvent, bizEventToReceiptService, logger); + if (isReceiptStatusValid(receipt)) { + bizEventToReceiptService.handleSaveReceipt(receipt); - public static void tokenizeReceipt(BizEventToReceiptService service, List bizEvents, Receipt receipt) - throws PDVTokenizerException, JsonProcessingException { - BizEvent firstEvent = bizEvents.get(0); - if (receipt.getEventData() == null) { - EventData eventData = new EventData(); - receipt.setEventData(eventData); - eventData.setTransactionCreationDate( - service.getTransactionCreationDate(firstEvent)); - - AtomicReference amount = new AtomicReference<>(BigDecimal.ZERO); - List cartItems = new ArrayList<>(); - bizEvents.forEach(bizEvent -> { - BigDecimal amountExtracted = getAmount(bizEvent); - amount.updateAndGet(v -> v.add(amountExtracted)); - cartItems.add( - CartItem.builder() - .payeeName(bizEvent.getCreditor() != null ? bizEvent.getCreditor().getCompanyName() : null) - .subject(getItemSubject(bizEvent)) - .build()); - }); - - if (!amount.get().equals(BigDecimal.ZERO)) { - eventData.setAmount(formatAmount(amount.get().toString())); - } - - eventData.setCart(cartItems); + if (isReceiptStatusValid(receipt)) { + bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(bizEvent), receipt); + return receipt; + } + } } - service.tokenizeFiscalCodes(firstEvent, receipt, receipt.getEventData()); + return receipt; } /** @@ -251,52 +179,7 @@ public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext cont return false; } - /** - * Checks if the instance of Biz Event is in status DONE and contains all the required information to process - * in the receipt generation - * - * @param bizEvent BizEvent to validate - * @param context Function context - * @param logger Function logger - * @return boolean to determine if the proposed event is invalid - */ - public static boolean isBizEventInvalidHelpDesk(BizEvent bizEvent, ExecutionContext context, Logger logger) { - - if (bizEvent == null) { - logger.error("[{}] event is null", context.getFunctionName()); - return true; - } - - if (!BizEventStatusType.DONE.equals(bizEvent.getEventStatus()) && !BizEventStatusType.INGESTED.equals(bizEvent.getEventStatus())) { - logger.error("[{}] event with id {} discarded because in status {}", - context.getFunctionName(), bizEvent.getId(), bizEvent.getEventStatus()); - return true; - } - - if (!hasValidFiscalCode(bizEvent)) { - logger.error("[{}] event with id {} discarded because debtor's and payer's identifiers are missing or not valid", - context.getFunctionName(), bizEvent.getId()); - return true; - } - - if (Boolean.TRUE.equals(ECOMMERCE_FILTER_ENABLED) - && bizEvent.getTransactionDetails() != null - && bizEvent.getTransactionDetails().getInfo() != null - && ECOMMERCE.equals(bizEvent.getTransactionDetails().getInfo().getClientId()) - ) { - logger.error("[{}] event with id {} discarded because from e-commerce {}", - context.getFunctionName(), bizEvent.getId(), bizEvent.getTransactionDetails().getInfo().getClientId()); - return true; - } - - if (!isCartMod1(bizEvent)) { - logger.error("[{}] event with id {} contain either an invalid amount value," + - " or it is a legacy cart element", - context.getFunctionName(), bizEvent.getId()); - return true; - } - - + private static boolean hasValidTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { if (bizEvent.getPaymentInfo() != null) { String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); @@ -311,19 +194,18 @@ public static boolean isBizEventInvalidHelpDesk(BizEvent bizEvent, ExecutionCont context.getFunctionName(), bizEvent.getId(), totalNotice, e); - return true; + return false; } if (intTotalNotice > 1) { logger.error("[{}] event with id {} discarded because is part of a payment cart ({} total notice)", context.getFunctionName(), bizEvent.getId(), intTotalNotice); - return true; + return false; } } } - - return false; + return true; } private static boolean hasValidFiscalCode(BizEvent bizEvent) { @@ -553,8 +435,7 @@ public static MassiveRecoverResult massiveRecoverByStatus( for (Receipt receipt : page.getResults()) { try { Receipt restored = getEvent(receipt.getEventId(), context, bizEventToReceiptService, - bizEventCosmosClient, receiptCosmosService, receipt, logger, receipt.getIsCart() != null ? - receipt.getIsCart() : false); + bizEventCosmosClient, receiptCosmosService, receipt, logger); receiptList.add(restored); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java index e6fef9c1..2df788c1 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import java.util.Iterator; +import java.util.stream.Stream; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; @@ -37,20 +38,17 @@ void runOk() throws ReceiptNotFoundException { CosmosDatabase mockDatabase = mock(CosmosDatabase.class); CosmosContainer mockContainer = mock(CosmosContainer.class); - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); receipt.setId(RECEIPT_ID); - when(mockIterator.hasNext()).thenReturn(true); - when(mockIterator.next()).thenReturn(receipt); + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); when(mockIterable.iterator()).thenReturn(mockIterator); - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn( - mockIterable - ); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) + .thenReturn(mockIterable); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); when(mockClient.getDatabase(any())).thenReturn(mockDatabase); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 9149399a..9bbaee56 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -390,6 +390,7 @@ void runDiscardedWithEventNotDONE() throws BizEventNotFoundException { verifyNoInteractions(queueClientMock); } + @Test void generateAnonymousDebtorBizEvent() throws BizEventNotFoundException { BizEvent bizEvent = generateAnonymDebtorBizEvent(); @@ -406,7 +407,7 @@ void generateAnonymousDebtorBizEvent() throws BizEventNotFoundException { // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); verifyNoInteractions(receiptCosmosServiceMock); @@ -484,7 +485,11 @@ void runDiscardedWithInvalidCartAmounts() throws BizEventNotFoundException { @SneakyThrows void errorTokenizingFiscalCodes() { when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateValidBizEvent("1")); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + Receipt receipt = createFailedReceipt(); + receipt.setEventData(null); + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + lenient().when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) .thenThrow(new PDVTokenizerException(HTTP_MESSAGE_ERROR, org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR)); From 44d520916708d8a0478e307acfcf7ea5cd9fdf91 Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 18 Dec 2025 16:27:49 +0100 Subject: [PATCH 10/38] RecoverFailedReceiptMassive --- .../http/RecoverFailedReceiptMassive.java | 2 +- .../utils/BizEventToReceiptUtils.java | 9 +++- .../http/RecoverFailedReceiptMassiveTest.java | 47 +++++++++++++++---- .../http/RecoverFailedReceiptTest.java | 2 +- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index 990a01ae..8ea84c77 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -53,7 +53,7 @@ public RecoverFailedReceiptMassive() { } /** - * This function will be invoked when a Http Trigger occurs. + * This function will be invoked when an Http Trigger occurs. *

* It recovers all the receipts with the specified status that has to be one of: * - ({@link ReceiptStatusType#INSERTED}) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 2eb50086..b92d6bf5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -128,6 +128,7 @@ public static Receipt createReceipt(BizEvent bizEvent, BizEventToReceiptService eventData.setCart(cartItems); receipt.setEventData(eventData); + receipt.setStatus(ReceiptStatusType.INSERTED); return receipt; } @@ -436,7 +437,12 @@ public static MassiveRecoverResult massiveRecoverByStatus( try { Receipt restored = getEvent(receipt.getEventId(), context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, receipt, logger); - receiptList.add(restored); + if (!isReceiptStatusValid(restored)) { + errorCounter++; + } + else { + receiptList.add(restored); + } } catch (Exception e) { logger.error(e.getMessage(), e); errorCounter++; @@ -445,6 +451,7 @@ public static MassiveRecoverResult massiveRecoverByStatus( continuationToken = page.getContinuationToken(); } } while (continuationToken != null); + return MassiveRecoverResult.builder() .receiptList(receiptList) .errorCounter(errorCounter) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java index 88560876..5a6ffc25 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -1,6 +1,7 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.azure.cosmos.models.ModelBridgeInternal; +import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.*; import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; @@ -72,7 +73,7 @@ public void releaseMocks() throws Exception { } @Test - void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException { + void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { when(requestMock.getQueryParameters()) .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); @@ -84,6 +85,18 @@ void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException { when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) .thenReturn(generateValidBizEvent(EVENT_ID)); + Answer successAnswer = invocation -> { + // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData + EventData eventDataArg = invocation.getArgument(2); + + // simulate tokenization + eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + return null; + }; + + doAnswer(successAnswer).when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); + doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); @@ -166,27 +179,43 @@ void recoverFailedReceiptMassivePartialOK() { .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); List receiptList = new ArrayList<>(); - receiptList.add(createFailedReceipt()); - Receipt receipt = createFailedReceipt(); - receipt.setEventData(null); - receiptList.add(receipt); + Receipt receipt1 = createFailedReceipt(); + receiptList.add(receipt1); + + Receipt receipt2 = createFailedReceipt(); + receipt2.setStatus(ReceiptStatusType.INSERTED); + receipt2.setEventData(null); + receiptList.add(receipt2); when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) .thenReturn(Collections.singletonList(ModelBridgeInternal .createFeedResponse(receiptList, Collections.emptyMap()))); - doThrow(PDVTokenizerException.class) + Answer successAnswer = invocation -> { + // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData + EventData eventDataArg = invocation.getArgument(2); + + // simulate tokenization + eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + return null; + }; + + doThrow(PDVTokenizerException.class) // 1. first call: error + .doAnswer(successAnswer) // 2. next call: success .when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); + String bizOk = "a valid id 2"; when(bizEventCosmosClientMock.getBizEventDocument(anyString())) .thenReturn(generateValidBizEvent(EVENT_ID)) - .thenReturn(generateValidBizEvent("a valid id 2")); + .thenReturn(generateValidBizEvent(bizOk)); doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); @@ -203,8 +232,8 @@ void recoverFailedReceiptMassivePartialOK() { verify(documentdb).setValue(receiptCaptor.capture()); assertEquals(1, receiptCaptor.getValue().size()); Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); +// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(bizOk, captured.getEventId()); assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); assertNotNull(captured.getEventData().getCart()); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 9bbaee56..d5915998 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -569,7 +569,7 @@ private BizEvent generateValidBizEvent(String totalNotice){ return item; } - private BizEvent generateValidBizEventWithTDetails(String totalNotice){ + private BizEvent generateValidBizEventWithTDetails(String totalNotice) { BizEvent item = new BizEvent(); Debtor debtor = new Debtor(); From 836dfdec40b9bfdc49b46b00f694147245e6f7ce Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 19 Dec 2025 10:22:52 +0100 Subject: [PATCH 11/38] Schedule --- .../helpdesk/http/RecoverFailedReceipt.java | 3 +- .../http/RecoverNotNotifiedReceipt.java | 2 +- .../RecoverNotNotifiedReceiptMassive.java | 5 +- .../service/ReceiptCosmosService.java | 3 + .../impl/ReceiptCosmosServiceImpl.java | 6 +- .../utils/RecoverNotNotifiedReceiptUtils.java | 12 +- .../http/RecoverFailedReceiptTest.java | 407 +++++++++--------- .../http/RecoverNotNotifiedReceiptTest.java | 2 +- .../RecoverFailedReceiptScheduledTest.java | 19 +- 9 files changed, 239 insertions(+), 220 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 2602ab6b..a244255e 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -90,14 +90,13 @@ public HttpResponseMessage run ( Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, this.bizEventCosmosClient, this.receiptCosmosService, null, logger); + documentdb.setValue(receipt); if (BizEventToReceiptUtils.isReceiptStatusValid(receipt)) { - documentdb.setValue(receipt); String responseMsg = String.format("Receipt with eventId %s recovered", eventId); return request.createResponseBuilder(HttpStatus.OK) .body(responseMsg) .build(); } else { - documentdb.setValue(receipt); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) .body(ProblemJson.builder() diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index 0bb41539..c1b153a3 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -91,7 +91,7 @@ public HttpResponseMessage run( .build(); } - if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { + if (receipt != null && !receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { String responseMsg = String.format("The requested receipt with eventId %s is not in the expected status", receipt.getEventId()); return request diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index b4ce48d1..aecb1be0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -39,7 +39,7 @@ public RecoverNotNotifiedReceiptMassive() { } /** - * This function will be invoked when a Http Trigger occurs. + * This function will be invoked when an Http Trigger occurs. *

* It recovers all receipt with the provided status. *

@@ -93,8 +93,7 @@ public HttpResponseMessage run( } if (!statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !statusType.equals(ReceiptStatusType.GENERATED)) { - String responseMsg = String.format("The requested status to recover %s is not one of the expected status", - statusType); + String responseMsg = String.format("The requested status to recover %s is not one of the expected status", statusType); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) .body(ProblemJson.builder() diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index ce95ceab..7912813a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -11,6 +11,9 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + /** * Service that handle the input and output for the {@link ReceiptCosmosClient} */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index e973802a..6267b0db 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -18,6 +18,8 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; @@ -49,10 +51,6 @@ public Receipt getReceipt(String eventId) throws ReceiptNotFoundException { throw new ReceiptNotFoundException(errorMsg, e); } - if (receipt == null) { - String errorMsg = String.format("Receipt retrieved with the biz-event id %s is null", eventId); - throw new ReceiptNotFoundException(errorMsg); - } return receipt; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java index fee9f112..23efd229 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java @@ -15,22 +15,17 @@ public static Receipt restoreReceipt(Receipt receipt) { receipt.setNotificationNumRetry(0); receipt.setNotified_at(0); - if (receipt.getReasonErr() != null) { - receipt.setReasonErr(null); - } - if (receipt.getReasonErrPayer() != null) { - receipt.setReasonErrPayer(null); - } + receipt.setReasonErr(null); + receipt.setReasonErrPayer(null); + return receipt; } public static List receiptMassiveRestore(ReceiptStatusType statusType, ReceiptCosmosService receiptCosmosService) { - List receiptList = new ArrayList<>(); String continuationToken = null; do { - Iterable> feedResponseIterator = receiptCosmosService.getNotNotifiedReceiptByStatus(continuationToken, 100, statusType); @@ -44,7 +39,6 @@ public static List receiptMassiveRestore(ReceiptStatusType statusType, } } while (continuationToken != null); - return receiptList; } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index d5915998..225a5e96 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -1,8 +1,9 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.azure.core.http.rest.Response; -import com.azure.cosmos.models.FeedResponse; +import com.azure.cosmos.models.CosmosItemResponse; import com.azure.storage.queue.models.SendMessageResult; +import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.*; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptQueueClient; @@ -23,6 +24,7 @@ import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import it.gov.pagopa.receipt.pdf.datastore.utils.ObjectMapperUtils; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -100,134 +102,134 @@ public void releaseMocks() throws Exception { closeable.close(); } - @Test - @SneakyThrows - void requestOnValidBizEventShouldCreateRequest() { - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent("1")); + // TODO Gioele questo scenario è cambiato +// @Test +// @SneakyThrows +// void requestOnValidBizEventShouldCreateRequest() { +// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) +// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); +// +// Response queueResponse = mock(Response.class); +// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); +// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); +// +// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) +// .thenReturn(generateValidBizEvent("1")); +// +// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); +// +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); +// +// // test execution +// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); +// +// // test assertion +// assertNotNull(response); +// assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); +// assertNotNull(response.getBody()); +// +// verify(documentdb).setValue(receiptCaptor.capture()); +// Receipt captured = receiptCaptor.getValue(); +// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); +// assertEquals(EVENT_ID, captured.getEventId()); +// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); +// assertNotNull(captured.getEventData().getCart()); +// assertEquals(1, captured.getEventData().getCart().size()); +// } + +// @Test +// @SneakyThrows +// void requestOnValidCartShouldCreateRequest() { +// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) +// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); +// +// Response queueResponse = mock(Response.class); +// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); +// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); +// +// when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); +// +// FeedResponse feedResponseMock = mock(FeedResponse.class); +// List receiptList = Collections.singletonList(generateValidBizEvent("1")); +// when(feedResponseMock.getResults()).thenReturn(receiptList); +// doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) +// .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); +// +// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); +// +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); +// +// // test execution +// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); +// +// // test assertion +// assertNotNull(response); +// assertEquals(HttpStatus.OK, response.getStatus()); +// assertNotNull(response.getBody()); +// +// verify(documentdb).setValue(receiptCaptor.capture()); +// Receipt captured = receiptCaptor.getValue(); +// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); +// assertEquals(EVENT_ID, captured.getEventId()); +// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); +// assertNotNull(captured.getEventData().getCart()); +// assertEquals(1, captured.getEventData().getCart().size()); +// } + +// @Test +// @SneakyThrows +// void requestOnValidBizEventTransactionDetailsShouldCreateRequest() { +// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) +// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); +// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) +// .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); +// +// Response queueResponse = mock(Response.class); +// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); +// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); +// +// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) +// .thenReturn(generateValidBizEventWithTDetails("1")); +// +// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); +// +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); +// +// // test execution +// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); +// +// // test assertion +// assertNotNull(response); +// assertEquals(HttpStatus.OK, response.getStatus()); +// assertNotNull(response.getBody()); +// +// verify(documentdb).setValue(receiptCaptor.capture()); +// Receipt captured = receiptCaptor.getValue(); +// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); +// assertEquals(EVENT_ID, captured.getEventId()); +// assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); +// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); +// assertNotNull(captured.getEventData().getCart()); +// assertEquals(1, captured.getEventData().getCart().size()); +// } - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - } @Test - @SneakyThrows - void requestOnValidCartShouldCreateRequest() { + void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - - when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); - - FeedResponse feedResponseMock = mock(FeedResponse.class); - List receiptList = Collections.singletonList(generateValidBizEvent("1")); - when(feedResponseMock.getResults()).thenReturn(receiptList); - doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) - .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); - - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - } - - @Test - @SneakyThrows - void requestOnValidBizEventTransactionDetailsShouldCreateRequest() { - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) - .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); - - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEventWithTDetails("1")); - - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - } - - - @Test - void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException { when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) .thenReturn(generateValidBizEvent("1")); @@ -241,67 +243,67 @@ void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoun // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - } - - @Test - void requestOnValidCartAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException { - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); - - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - - FeedResponse feedResponseMock = mock(FeedResponse.class); - List receiptList = Collections.singletonList(generateValidBizEvent("1")); - when(feedResponseMock.getResults()).thenReturn(receiptList); - doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) - .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); assertNotNull(response.getBody()); verify(documentdb).setValue(receiptCaptor.capture()); Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(ReceiptStatusType.FAILED, captured.getStatus()); assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); assertNotNull(captured.getEventData().getCart()); assertEquals(1, captured.getEventData().getCart().size()); } +// @Test +// void requestOnValidCartAndFailedReceiptShouldResend() throws ReceiptNotFoundException { +// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); +// +// Response queueResponse = mock(Response.class); +// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); +// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); +// +// FeedResponse feedResponseMock = mock(FeedResponse.class); +// List receiptList = Collections.singletonList(generateValidBizEvent("1")); +// when(feedResponseMock.getResults()).thenReturn(receiptList); +// doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) +// .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); +// +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); +// +// when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); +// +// // test execution +// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); +// +// // test assertion +// assertNotNull(response); +// assertEquals(HttpStatus.OK, response.getStatus()); +// assertNotNull(response.getBody()); +// +// verify(documentdb).setValue(receiptCaptor.capture()); +// Receipt captured = receiptCaptor.getValue(); +// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); +// assertEquals(EVENT_ID, captured.getEventId()); +// assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); +// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); +// assertNotNull(captured.getEventData().getCart()); +// assertEquals(1, captured.getEventData().getCart().size()); +// } + + // TODO Gioele @Test @SneakyThrows void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken() { when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); +// Response queueResponse = mock(Response.class); +// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); +// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); Receipt receipt = createFailedReceipt(); receipt.setEventData(null); @@ -320,12 +322,12 @@ void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); assertNotNull(response.getBody()); verify(documentdb).setValue(receiptCaptor.capture()); Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); + assertEquals(ReceiptStatusType.FAILED, captured.getStatus()); assertEquals(EVENT_ID, captured.getEventId()); assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); assertNotNull(captured.getEventData().getCart()); @@ -333,8 +335,9 @@ void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken } @Test - void requestWithMissingBizEventOnRequestIdShouldReturnBitFound() throws BizEventNotFoundException { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenThrow(BizEventNotFoundException.class); + void requestWithMissingBizEventOnRequestIdShouldReturnNotFound() throws BizEventNotFoundException { + BizEventNotFoundException exception = new BizEventNotFoundException("BizEvent not found"); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenThrow(exception); doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; @@ -383,7 +386,7 @@ void runDiscardedWithEventNotDONE() throws BizEventNotFoundException { // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); verifyNoInteractions(receiptCosmosServiceMock); @@ -428,7 +431,7 @@ void runDiscardedWithEventNull() throws BizEventNotFoundException { // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); verifyNoInteractions(receiptCosmosServiceMock); @@ -450,36 +453,36 @@ void runDiscardedEventWithInvalidTotalNotice() throws BizEventNotFoundException // test assertion assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); verifyNoInteractions(receiptCosmosServiceMock); verifyNoInteractions(queueClientMock); } - @Test - void runDiscardedWithInvalidCartAmounts() throws BizEventNotFoundException { - BizEvent bizEvent = generateValidBizEvent(null); - bizEvent.getTransactionDetails().getTransaction().setAmount(10); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(bizEvent); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verifyNoInteractions(receiptCosmosServiceMock); - verifyNoInteractions(queueClientMock); - - } +// @Test +// void runDiscardedWithInvalidCartAmounts() throws BizEventNotFoundException { +// BizEvent bizEvent = generateValidBizEvent(null); +// bizEvent.getTransactionDetails().getTransaction().setAmount(10); +// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) +// .thenReturn(bizEvent); +// +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); +// +// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); +// +// // test assertion +// assertNotNull(response); +// assertEquals(HttpStatus.OK, response.getStatus()); +// assertNotNull(response.getBody()); +// +// verifyNoInteractions(receiptCosmosServiceMock); +// verifyNoInteractions(queueClientMock); +// +// } @Test @SneakyThrows @@ -518,8 +521,10 @@ void errorTokenizingFiscalCodes() { @Test @SneakyThrows void errorAddingMessageToQueue() { - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); + CosmosItemResponse cosmosResponse = mock(CosmosItemResponse.class); + when(cosmosResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); + + when(receiptCosmosClient.saveReceipts(any(Receipt.class))).thenReturn(cosmosResponse); Response queueResponse = mock(Response.class); when(queueResponse.getStatusCode()).thenReturn(HttpStatus.FORBIDDEN.value()); @@ -537,7 +542,11 @@ void errorAddingMessageToQueue() { }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); // test execution - assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); } private BizEvent generateValidBizEvent(String totalNotice){ diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index 1f9ba1f2..3110e79e 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -92,7 +92,7 @@ void recoverNotNotifiedReceiptSuccess() throws ReceiptNotFoundException { } @Test - void recoverNotNotifiedCartReceiptSuccess() throws ReceiptNotFoundException, CartNotFoundException { + void recoverNotNotifiedCartReceiptSuccess() throws ReceiptNotFoundException { Receipt receipt = buildReceipt(); when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java index 76c77fd9..f73ebbb8 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -1,6 +1,7 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; import com.azure.cosmos.models.ModelBridgeInternal; +import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; @@ -11,6 +12,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import org.junit.jupiter.api.AfterEach; @@ -18,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; +import org.mockito.stubbing.Answer; import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; import uk.org.webcompere.systemstubs.jupiter.SystemStub; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; @@ -72,13 +75,15 @@ public void releaseMocks() throws Exception { } @Test - void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException { + void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { sut = spy(new RecoverFailedReceiptScheduled(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.FAILED))) .thenReturn(Collections.singletonList(ModelBridgeInternal .createFeedResponse(Collections.singletonList( createFailedReceipt(EVENT_ID_1, ReceiptStatusType.FAILED)), Collections.emptyMap()))); + when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.INSERTED))) .thenReturn(Collections.singletonList(ModelBridgeInternal .createFeedResponse(Collections.singletonList( @@ -97,6 +102,18 @@ void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException { when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_3)) .thenReturn(generateValidBizEvent(EVENT_ID_3)); + Answer successAnswer = invocation -> { + // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData + EventData eventDataArg = invocation.getArgument(2); + + // simulate tokenization + eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + return null; + }; + + doAnswer(successAnswer).when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); + // test execution assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); From 0bb271ab2027e9416e02d0fce3a8b097793a325c Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 19 Dec 2025 10:53:36 +0100 Subject: [PATCH 12/38] added unit tests for ReceiptCosmosServiceImpl --- .../impl/ReceiptCosmosServiceImpl.java | 8 - .../impl/ReceiptCosmosServiceImplTest.java | 213 ++++++++++++++++++ 2 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index 6267b0db..56c1e9db 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -1,8 +1,5 @@ package it.gov.pagopa.receipt.pdf.datastore.service.impl; -import com.azure.cosmos.CosmosContainer; -import com.azure.cosmos.CosmosDatabase; -import com.azure.cosmos.models.CosmosQueryRequestOptions; import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; @@ -18,11 +15,6 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; - public class ReceiptCosmosServiceImpl implements ReceiptCosmosService { private final ReceiptCosmosClient receiptCosmosClient; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java new file mode 100644 index 00000000..86eb919d --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java @@ -0,0 +1,213 @@ +package it.gov.pagopa.receipt.pdf.datastore.service.impl; + +import com.azure.cosmos.models.FeedResponse; +import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) +class ReceiptCosmosServiceImplTest { + + private static final String CONTINUATION_TOKEN = "continuationToken"; + private static final int PAGE_SIZE = 100; + + @Mock + private ReceiptCosmosClientImpl receiptCosmosClient; + @Mock + private CartReceiptsCosmosClient cartReceiptsCosmosClient; + + @InjectMocks + private ReceiptCosmosServiceImpl sut; + + @Test + void getReceipt_OK() throws ReceiptNotFoundException { + doReturn(new Receipt()).when(receiptCosmosClient).getReceiptDocument(anyString()); + + assertDoesNotThrow(() -> sut.getReceipt(anyString())); + } + + @Test + void getReceipt_KO() throws ReceiptNotFoundException { + doThrow(ReceiptNotFoundException.class).when(receiptCosmosClient).getReceiptDocument(anyString()); + ReceiptNotFoundException e = assertThrows(ReceiptNotFoundException.class, () -> sut.getReceipt(anyString())); + + assertNotNull(e); + } + + @Test + void getReceiptError_OK() throws ReceiptNotFoundException { + doReturn(new ReceiptError()).when(receiptCosmosClient).getReceiptError(anyString()); + + assertDoesNotThrow(() -> sut.getReceiptError(anyString())); + } + + @Test + void getReceiptError_KO_throwsReceiptNotFound() throws ReceiptNotFoundException { + doThrow(ReceiptNotFoundException.class).when(receiptCosmosClient).getReceiptError(anyString()); + ReceiptNotFoundException e = assertThrows(ReceiptNotFoundException.class, () -> sut.getReceiptError(anyString())); + + assertNotNull(e); + } + + @Test + void getReceiptError_KO_nullReceipt() throws ReceiptNotFoundException { + doReturn(null).when(receiptCosmosClient).getReceiptError(anyString()); + ReceiptNotFoundException e = assertThrows(ReceiptNotFoundException.class, () -> sut.getReceiptError(anyString())); + + assertNotNull(e); + } + + @Test + void getNotNotifiedReceiptByStatus_KO_nullStatus() { + assertThrows(IllegalArgumentException.class, () -> sut.getNotNotifiedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, null)); + } + + @Test + void getNotNotifiedReceiptByStatus_KO_unexpectedStatus() { + assertThrows(IllegalStateException.class, () -> sut.getNotNotifiedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.FAILED)); + } + + @Test + void getNotNotifiedReceiptByStatus_OK_IOErrorToNotify() { + @SuppressWarnings("unchecked") + Iterable> expectedIterable = mock(Iterable.class); + when(receiptCosmosClient.getIOErrorToNotifyReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE)).thenReturn(expectedIterable); + + Iterable> actualIterable = sut.getNotNotifiedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.IO_ERROR_TO_NOTIFY); + + assertNotNull(actualIterable); + assertEquals(expectedIterable, actualIterable); + verify(receiptCosmosClient).getIOErrorToNotifyReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); + verify(receiptCosmosClient, never()).getGeneratedReceiptDocuments(any(), any()); + } + + @Test + void getNotNotifiedReceiptByStatus_OK_Generated() { + @SuppressWarnings("unchecked") + Iterable> expectedIterable = mock(Iterable.class); + when(receiptCosmosClient.getGeneratedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE)).thenReturn(expectedIterable); + + Iterable> actualIterable = sut.getNotNotifiedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.GENERATED); + + assertNotNull(actualIterable); + assertEquals(expectedIterable, actualIterable); + verify(receiptCosmosClient).getGeneratedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); + verify(receiptCosmosClient, never()).getIOErrorToNotifyReceiptDocuments(any(), any()); + } + + @Test + void getFailedReceiptByStatus_KO_nullStatus() { + assertThrows(IllegalArgumentException.class, () -> sut.getFailedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, null)); + } + + @Test + void getFailedReceiptByStatus_KO_unexpectedStatus() { + assertThrows(IllegalStateException.class, () -> sut.getFailedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.GENERATED)); + } + + @Test + void getFailedReceiptByStatus_OK_Failed() { + @SuppressWarnings("unchecked") + Iterable> expectedIterable = mock(Iterable.class); + when(receiptCosmosClient.getFailedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE)).thenReturn(expectedIterable); + + Iterable> actualIterable = sut.getFailedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.FAILED); + + assertNotNull(actualIterable); + assertEquals(expectedIterable, actualIterable); + verify(receiptCosmosClient).getFailedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); + verify(receiptCosmosClient, never()).getInsertedReceiptDocuments(any(), any()); + } + + @Test + void getFailedReceiptByStatus_OK_NotQueueSent() { + @SuppressWarnings("unchecked") + Iterable> expectedIterable = mock(Iterable.class); + when(receiptCosmosClient.getFailedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE)).thenReturn(expectedIterable); + + Iterable> actualIterable = sut.getFailedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.NOT_QUEUE_SENT); + + assertNotNull(actualIterable); + assertEquals(expectedIterable, actualIterable); + verify(receiptCosmosClient).getFailedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); + verify(receiptCosmosClient, never()).getInsertedReceiptDocuments(any(), any()); + } + + @Test + void getFailedReceiptByStatus_OK_Inserted() { + @SuppressWarnings("unchecked") + Iterable> expectedIterable = mock(Iterable.class); + when(receiptCosmosClient.getInsertedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE)).thenReturn(expectedIterable); + + Iterable> actualIterable = sut.getFailedReceiptByStatus(CONTINUATION_TOKEN, PAGE_SIZE, ReceiptStatusType.INSERTED); + + assertNotNull(actualIterable); + assertEquals(expectedIterable, actualIterable); + verify(receiptCosmosClient).getInsertedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); + verify(receiptCosmosClient, never()).getFailedReceiptDocuments(any(), any()); + } + + @Test + void getReceiptMessage_OK() throws IoMessageNotFoundException { + doReturn(new IOMessage()).when(receiptCosmosClient).getIoMessage(anyString()); + + assertDoesNotThrow(() -> sut.getReceiptMessage(anyString())); + } + + @Test + void getReceiptMessage_KO_NotFound() throws IoMessageNotFoundException { + doThrow(IoMessageNotFoundException.class).when(receiptCosmosClient).getIoMessage(anyString()); + IoMessageNotFoundException e = assertThrows(IoMessageNotFoundException.class, () -> sut.getReceiptMessage(anyString())); + + assertNotNull(e); + } + + @Test + void getReceiptMessage_KO_Null() throws IoMessageNotFoundException { + doReturn(null).when(receiptCosmosClient).getIoMessage(anyString()); + IoMessageNotFoundException e = assertThrows(IoMessageNotFoundException.class, () -> sut.getReceiptMessage(anyString())); + + assertNotNull(e); + } + + @Test + void getCart_OK() throws CartNotFoundException { + doReturn(new CartForReceipt()).when(cartReceiptsCosmosClient).getCartItem(anyString()); + + assertDoesNotThrow(() -> sut.getCart(anyString())); + } + + @Test + void getCart_KO_NotFound() throws CartNotFoundException { + doThrow(CartNotFoundException.class).when(cartReceiptsCosmosClient).getCartItem(anyString()); + CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); + + assertNotNull(e); + } + + @Test + void getCart_KO_Null() throws CartNotFoundException { + doReturn(null).when(cartReceiptsCosmosClient).getCartItem(anyString()); + CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); + + assertNotNull(e); + } +} \ No newline at end of file From fbcbfded2635e6270df8c3aadc5bd69eec9f198f Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 19 Dec 2025 10:54:45 +0100 Subject: [PATCH 13/38] removed code not used --- .../exception/IoMessageNotFoundException.java | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java deleted file mode 100644 index a915792d..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java +++ /dev/null @@ -1,26 +0,0 @@ -package it.gov.pagopa.receipt.pdf.datastore.exception; - -/** Thrown in case no receipt message to IO is found in the CosmosDB container */ -public class IoMessageNotFoundException extends Exception{ - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - */ - public IoMessageNotFoundException(String message) { - super(message); - } - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - * @param cause Exception thrown - */ - public IoMessageNotFoundException(String message, Throwable cause) { - super(message, cause); - } -} - - From cb31975d7d55577ee4e702eafc05047ef67f0ed7 Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 19 Dec 2025 12:04:17 +0100 Subject: [PATCH 14/38] added unit tests --- .../datastore/client/ReceiptCosmosClient.java | 2 +- .../exception/IoMessageNotFoundException.java | 26 ++++ .../impl/BizEventCosmosClientImplTest.java | 38 ++++- .../impl/ReceiptCosmosClientImplTest.java | 138 +++++++++++++++++- 4 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java index 42fc0342..64ca083f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java @@ -79,6 +79,6 @@ public interface ReceiptCosmosClient { * @return io message document * @throws IoMessageNotFoundException in case no receipt has been found with the given messageId */ - IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException; + IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException, IoMessageNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java new file mode 100644 index 00000000..a915792d --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +/** Thrown in case no receipt message to IO is found in the CosmosDB container */ +public class IoMessageNotFoundException extends Exception{ + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public IoMessageNotFoundException(String message) { + super(message); + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public IoMessageNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} + + diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java index 2228942f..7910c3cf 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java @@ -3,21 +3,29 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.models.CosmosQueryRequestOptions; +import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import java.util.Iterator; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verify; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; class BizEventCosmosClientImplTest { + @Mock + private Iterable> feedResponseIterable; + @Test void testSingletonConnectionError() throws Exception { String mockKey = "mockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeyMK=="; @@ -99,4 +107,30 @@ void getBizEventDocumentError() { Assertions.assertThrows(BizEventNotFoundException.class, () -> client.getBizEventDocument("1")); } + + @Test + void getAllBizEventDocument_Success() { + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + + CosmosContainer mockContainer = mock(CosmosContainer.class); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + + Iterator mockIterator = mock(Iterator.class); + BizEvent bizEvent = new BizEvent(); + + when(mockIterator.hasNext()).thenReturn(false); + when(mockIterator.next()).thenReturn(bizEvent); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.iterator()).thenReturn(mockIterator); + when(mockContainer.queryItems(anyString(), any(CosmosQueryRequestOptions.class), eq(BizEvent.class))).thenReturn(mockIterable); + + BizEventCosmosClientImpl client = new BizEventCosmosClientImpl(mockClient); + + Assertions.assertDoesNotThrow(() -> client.getAllBizEventDocument("1", "asdf", 100)); + + } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java index 2df788c1..e4644866 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java @@ -3,15 +3,22 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.Iterator; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -47,8 +54,7 @@ void runOk() throws ReceiptNotFoundException { when(mockIterable.iterator()).thenReturn(mockIterator); - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) - .thenReturn(mockIterable); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); when(mockClient.getDatabase(any())).thenReturn(mockDatabase); @@ -87,7 +93,7 @@ void runKo() { } @Test - void runOk_FailedQueryClient() throws ReceiptNotFoundException { + void runOk_FailedQueryClient() { String RECEIPT_ID = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -115,7 +121,133 @@ void runOk_FailedQueryClient() throws ReceiptNotFoundException { ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); Assertions.assertDoesNotThrow(() -> client.getFailedReceiptDocuments(null, 100)); + } + + @Test + void getGeneratedReceiptDocuments_Success() { + String RECEIPT_ID = "a valid receipt id"; + + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + CosmosContainer mockContainer = mock(CosmosContainer.class); + + Iterator mockIterator = mock(Iterator.class); + Receipt receipt = new Receipt(); + receipt.setId(RECEIPT_ID); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); + + when(mockIterable.iterator()).thenReturn(mockIterator); + + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) + .thenReturn(mockIterable); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + + ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + + Assertions.assertDoesNotThrow(() -> client.getGeneratedReceiptDocuments("scsdf", 100)); + } + + @Test + void getIOErrorToNotifyReceiptDocuments_Success() { + String RECEIPT_ID = "a valid receipt id"; + + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + CosmosContainer mockContainer = mock(CosmosContainer.class); + + Iterator mockIterator = mock(Iterator.class); + Receipt receipt = new Receipt(); + receipt.setId(RECEIPT_ID); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); + + when(mockIterable.iterator()).thenReturn(mockIterator); + + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) + .thenReturn(mockIterable); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + + Assertions.assertDoesNotThrow(() -> client.getIOErrorToNotifyReceiptDocuments("scsdf", 100)); + } + + @Test + void getInsertedReceiptDocuments_Success() { + String RECEIPT_ID = "a valid receipt id"; + + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + CosmosContainer mockContainer = mock(CosmosContainer.class); + + Iterator mockIterator = mock(Iterator.class); + Receipt receipt = new Receipt(); + receipt.setId(RECEIPT_ID); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); + + when(mockIterable.iterator()).thenReturn(mockIterator); + + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) + .thenReturn(mockIterable); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + + Assertions.assertDoesNotThrow(() -> client.getInsertedReceiptDocuments("scsdf", 100)); + } + + @Test + void getIoMessage_Success() { + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + CosmosContainer mockContainer = mock(CosmosContainer.class); + + Iterator mockIterator = mock(Iterator.class); + IOMessage ioMessage = new IOMessage(); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(ioMessage)); + + when(mockIterable.iterator()).thenReturn(mockIterator); + + when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIterable); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + + Assertions.assertDoesNotThrow(() -> client.getIoMessage("scsdf")); + } + + @Test + void getIoMessage_NotFound() { + CosmosClient mockClient = mock(CosmosClient.class); + + CosmosDatabase mockDatabase = mock(CosmosDatabase.class); + CosmosContainer mockContainer = mock(CosmosContainer.class); + + Iterator mockIterator = mock(Iterator.class); + + CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockIterable.stream()).thenAnswer(invocation -> Stream.empty()); + + when(mockIterable.iterator()).thenReturn(mockIterator); + + when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIterable); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + Assertions.assertThrows(IoMessageNotFoundException.class, () -> client.getIoMessage("asdf")); } } \ No newline at end of file From 4c509a61ebfada206a4c9711f15b11e885746914 Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 19 Dec 2025 12:23:12 +0100 Subject: [PATCH 15/38] code cleaning --- .../datastore/client/ReceiptCosmosClient.java | 2 +- .../client/impl/ReceiptCosmosClientImpl.java | 2 - .../BizEventBadRequestException.java | 1 - .../exception/BizEventNotFoundException.java | 1 - .../service/ReceiptCosmosService.java | 3 -- .../impl/BizEventToReceiptServiceImpl.java | 37 -------------- .../utils/BizEventToReceiptUtils.java | 28 ----------- .../impl/BizEventCosmosClientImplTest.java | 3 -- .../impl/ReceiptCosmosClientImplTest.java | 31 +++++------- .../helpdesk/http/ReceiptToReviewedTest.java | 50 +++++++++---------- .../http/RecoverFailedReceiptMassiveTest.java | 39 +++++++-------- .../http/RecoverFailedReceiptTest.java | 5 +- .../RecoverNotNotifiedReceiptMassiveTest.java | 4 +- .../http/RecoverNotNotifiedReceiptTest.java | 5 +- .../RecoverFailedReceiptScheduledTest.java | 4 +- ...ecoverNotNotifiedReceiptScheduledTest.java | 4 +- 16 files changed, 68 insertions(+), 151 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java index 64ca083f..42fc0342 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/ReceiptCosmosClient.java @@ -79,6 +79,6 @@ public interface ReceiptCosmosClient { * @return io message document * @throws IoMessageNotFoundException in case no receipt has been found with the given messageId */ - IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException, IoMessageNotFoundException; + IOMessage getIoMessage(String messageId) throws IoMessageNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java index e5f18d44..f3578b40 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImpl.java @@ -7,7 +7,6 @@ import com.azure.cosmos.models.CosmosItemResponse; import com.azure.cosmos.models.CosmosQueryRequestOptions; import com.azure.cosmos.models.FeedResponse; -import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; @@ -18,7 +17,6 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; -import java.util.Iterator; import java.util.Optional; /** diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java index c6209d79..d85b68ae 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventBadRequestException.java @@ -1,7 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.exception; import com.microsoft.azure.functions.HttpStatus; -import lombok.Getter; /** * Thrown in case no receipt is found in the CosmosDB container diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java index 8ff4a4cf..0f5872c5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventNotFoundException.java @@ -1,7 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.exception; import com.microsoft.azure.functions.HttpStatus; -import lombok.Getter; /** * Thrown in case no receipt is found in the CosmosDB container diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index 7912813a..ce95ceab 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -11,9 +11,6 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - /** * Service that handle the input and output for the {@link ReceiptCosmosClient} */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index e889a32c..64cadab5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -217,43 +217,6 @@ public void tokenizeFiscalCodes(BizEvent bizEvent, Receipt receipt, EventData ev } } -// /** -// * {@inheritDoc} -// */ -// @Override -// public void tokenizeFiscalCodes(BizEvent bizEvent, Receipt receipt, EventData eventData) throws JsonProcessingException, PDVTokenizerException { -// try { -// eventData.setDebtorFiscalCode( -// bizEvent.getDebtor() != null && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getDebtor().getEntityUniqueIdentifierValue()) ? -// pdvTokenizerService.generateTokenForFiscalCodeWithRetry(bizEvent.getDebtor().getEntityUniqueIdentifierValue()) : -// FISCAL_CODE_ANONYMOUS -// ); -// -// if (isFromAuthenticatedOrigin(bizEvent)) { -// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getUser() != null -// && bizEvent.getTransactionDetails().getUser().getFiscalCode() != null -// && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getTransactionDetails().getUser().getFiscalCode())) { -// eventData.setPayerFiscalCode( -// pdvTokenizerService.generateTokenForFiscalCodeWithRetry( -// bizEvent.getTransactionDetails().getUser().getFiscalCode()) -// ); -// } else if (bizEvent.getPayer() != null && BizEventToReceiptUtils.isValidFiscalCode(bizEvent.getPayer().getEntityUniqueIdentifierValue())) { -// eventData.setPayerFiscalCode( -// pdvTokenizerService.generateTokenForFiscalCodeWithRetry( -// bizEvent.getPayer().getEntityUniqueIdentifierValue()) -// ); -// } -// } -// } catch (PDVTokenizerException e) { -// handleTokenizerException(receipt, e.getMessage(), e.getStatusCode()); -// throw e; -// } catch (JsonProcessingException e) { -// handleTokenizerException(receipt, e.getMessage(), ReasonErrorCode.ERROR_PDV_MAPPING.getCode()); -// throw e; -// } -// } - - /** * {@inheritDoc} */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index b92d6bf5..8fbac0db 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -390,34 +390,6 @@ public static boolean isValidChannelOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } -// public static boolean isFromAuthenticatedOrigin(BizEvent bizEvent) { -// if (bizEvent.getTransactionDetails() == null) { -// return false; -// } -// -// var transactionDetails = bizEvent.getTransactionDetails(); -// var transaction = transactionDetails.getTransaction(); -// var info = transactionDetails.getInfo(); -// var user = transactionDetails.getUser(); -// -// String origin = (transaction != null) ? transaction.getOrigin() : null; -// String clientId = (info != null) ? info.getClientId() : null; -// UserType userType = (user != null) ? user.getType() : null; -// -// boolean originMatches = origin != null && AUTHENTICATED_CHANNELS.contains(origin); -// boolean clientIdMatches = clientId != null && AUTHENTICATED_CHANNELS.contains(clientId); -// -// boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); -// boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); -// boolean isRegisteredUser = UserType.REGISTERED.equals(userType); -// -// if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { -// return false; -// } -// -// return originMatches || clientIdMatches; -// } - public static MassiveRecoverResult massiveRecoverByStatus( ExecutionContext context, BizEventToReceiptService bizEventToReceiptService, diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java index 7910c3cf..ed79bc9d 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java @@ -14,11 +14,8 @@ import java.util.Iterator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import static org.mockito.Mockito.verify; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; class BizEventCosmosClientImplTest { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java index e4644866..412b2969 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java @@ -3,22 +3,17 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; -import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; import java.util.Iterator; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -38,7 +33,7 @@ void testSingletonConnectionError() throws Exception { @Test void runOk() throws ReceiptNotFoundException { - String RECEIPT_ID = "a valid receipt id"; + String receiptId = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -47,7 +42,7 @@ void runOk() throws ReceiptNotFoundException { Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); - receipt.setId(RECEIPT_ID); + receipt.setId(receiptId); CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); @@ -60,10 +55,10 @@ void runOk() throws ReceiptNotFoundException { ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); - Assertions.assertDoesNotThrow(() -> client.getReceiptDocument(RECEIPT_ID)); + Assertions.assertDoesNotThrow(() -> client.getReceiptDocument(receiptId)); - Receipt receiptResponse = client.getReceiptDocument(RECEIPT_ID); - Assertions.assertEquals(RECEIPT_ID, receiptResponse.getId()); + Receipt receiptResponse = client.getReceiptDocument(receiptId); + Assertions.assertEquals(receiptId, receiptResponse.getId()); } @Test @@ -94,7 +89,7 @@ void runKo() { @Test void runOk_FailedQueryClient() { - String RECEIPT_ID = "a valid receipt id"; + String receiptId = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -105,7 +100,7 @@ void runOk_FailedQueryClient() { Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); - receipt.setId(RECEIPT_ID); + receipt.setId(receiptId); when(mockIterator.hasNext()).thenReturn(true); when(mockIterator.next()).thenReturn(receipt); @@ -125,7 +120,7 @@ void runOk_FailedQueryClient() { @Test void getGeneratedReceiptDocuments_Success() { - String RECEIPT_ID = "a valid receipt id"; + String receiptId = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -134,7 +129,7 @@ void getGeneratedReceiptDocuments_Success() { Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); - receipt.setId(RECEIPT_ID); + receipt.setId(receiptId); CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); @@ -153,7 +148,7 @@ void getGeneratedReceiptDocuments_Success() { @Test void getIOErrorToNotifyReceiptDocuments_Success() { - String RECEIPT_ID = "a valid receipt id"; + String receiptId = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -162,7 +157,7 @@ void getIOErrorToNotifyReceiptDocuments_Success() { Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); - receipt.setId(RECEIPT_ID); + receipt.setId(receiptId); CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); @@ -180,7 +175,7 @@ void getIOErrorToNotifyReceiptDocuments_Success() { @Test void getInsertedReceiptDocuments_Success() { - String RECEIPT_ID = "a valid receipt id"; + String receiptId = "a valid receipt id"; CosmosClient mockClient = mock(CosmosClient.class); @@ -189,7 +184,7 @@ void getInsertedReceiptDocuments_Success() { Iterator mockIterator = mock(Iterator.class); Receipt receipt = new Receipt(); - receipt.setId(RECEIPT_ID); + receipt.setId(receiptId); CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index 11f4b6eb..77afc007 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -68,31 +68,31 @@ void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFound assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); } - @Test - void requestWithValidCartSaveReceiptErrorInReviewed() throws ReceiptNotFoundException, CartNotFoundException { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(request).createResponseBuilder(any(HttpStatus.class)); - - ReceiptError receiptError = ReceiptError.builder() - .bizEventId(BIZ_EVENT_ID) - .status(ReceiptErrorStatusType.TO_REVIEW) - .build(); - when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); - - function = spy(new ReceiptToReviewed(receiptCosmosService)); - - // test execution - AtomicReference responseMessage = new AtomicReference<>(); - assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); - assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); - - verify(documentdb).setValue(receiptErrorCaptor.capture()); - ReceiptError captured = receiptErrorCaptor.getValue(); - assertEquals(BIZ_EVENT_ID, captured.getBizEventId()); - assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); - } +// @Test +// void requestWithValidCartSaveReceiptErrorInReviewed() throws ReceiptNotFoundException, CartNotFoundException { +// doAnswer((Answer) invocation -> { +// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; +// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); +// }).when(request).createResponseBuilder(any(HttpStatus.class)); +// +// ReceiptError receiptError = ReceiptError.builder() +// .bizEventId(BIZ_EVENT_ID) +// .status(ReceiptErrorStatusType.TO_REVIEW) +// .build(); +// when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); +// +// function = spy(new ReceiptToReviewed(receiptCosmosService)); +// +// // test execution +// AtomicReference responseMessage = new AtomicReference<>(); +// assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); +// assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); +// +// verify(documentdb).setValue(receiptErrorCaptor.capture()); +// ReceiptError captured = receiptErrorCaptor.getValue(); +// assertEquals(BIZ_EVENT_ID, captured.getBizEventId()); +// assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); +// } @Test void requestWithValidBizEventIdButReceiptNotFound() throws ReceiptNotFoundException { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java index 5a6ffc25..c902a398 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -35,9 +35,9 @@ @ExtendWith(MockitoExtension.class) class RecoverFailedReceiptMassiveTest { - private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; - private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; - private final String EVENT_ID = "a valid id"; + private final String tokenizedDebtorFiscalCode = "tokenizedDebtorFiscalCode"; + private final String tokenizedPayerFiscalCode = "tokenizedPayerFiscalCode"; + private final String eventId = "a valid id"; @Mock private ExecutionContext contextMock; @@ -62,13 +62,13 @@ class RecoverFailedReceiptMassiveTest { private RecoverFailedReceiptMassive sut; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); sut = spy(new RecoverFailedReceiptMassive(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } @@ -82,16 +82,16 @@ void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVT .createFeedResponse(Collections.singletonList(createFailedReceipt()), Collections.emptyMap()))); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent(EVENT_ID)); + when(bizEventCosmosClientMock.getBizEventDocument(eventId)) + .thenReturn(generateValidBizEvent(eventId)); Answer successAnswer = invocation -> { // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData EventData eventDataArg = invocation.getArgument(2); // simulate tokenization - eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); - eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + eventDataArg.setPayerFiscalCode(tokenizedPayerFiscalCode); + eventDataArg.setDebtorFiscalCode(tokenizedDebtorFiscalCode); return null; }; @@ -114,9 +114,9 @@ void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVT assertEquals(1, receiptCaptor.getValue().size()); Receipt captured = receiptCaptor.getValue().get(0); assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertEquals(eventId, captured.getEventId()); + assertEquals(tokenizedPayerFiscalCode, captured.getEventData().getPayerFiscalCode()); + assertEquals(tokenizedDebtorFiscalCode, captured.getEventData().getDebtorFiscalCode()); assertNotNull(captured.getEventData().getCart()); assertEquals(1, captured.getEventData().getCart().size()); } @@ -196,8 +196,8 @@ void recoverFailedReceiptMassivePartialOK() { EventData eventDataArg = invocation.getArgument(2); // simulate tokenization - eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); - eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); + eventDataArg.setPayerFiscalCode(tokenizedPayerFiscalCode); + eventDataArg.setDebtorFiscalCode(tokenizedDebtorFiscalCode); return null; }; @@ -207,7 +207,7 @@ void recoverFailedReceiptMassivePartialOK() { String bizOk = "a valid id 2"; when(bizEventCosmosClientMock.getBizEventDocument(anyString())) - .thenReturn(generateValidBizEvent(EVENT_ID)) + .thenReturn(generateValidBizEvent(eventId)) .thenReturn(generateValidBizEvent(bizOk)); doAnswer((Answer) invocation -> { @@ -232,10 +232,9 @@ void recoverFailedReceiptMassivePartialOK() { verify(documentdb).setValue(receiptCaptor.capture()); assertEquals(1, receiptCaptor.getValue().size()); Receipt captured = receiptCaptor.getValue().get(0); -// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); assertEquals(bizOk, captured.getEventId()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertEquals(tokenizedPayerFiscalCode, captured.getEventData().getPayerFiscalCode()); + assertEquals(tokenizedDebtorFiscalCode, captured.getEventData().getDebtorFiscalCode()); assertNotNull(captured.getEventData().getCart()); assertEquals(1, captured.getEventData().getCart().size()); } @@ -309,8 +308,8 @@ private Receipt createFailedReceipt() { receipt.setStatus(ReceiptStatusType.FAILED); EventData eventData = new EventData(); - eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); - eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + eventData.setDebtorFiscalCode(tokenizedDebtorFiscalCode); + eventData.setPayerFiscalCode(tokenizedPayerFiscalCode); receipt.setEventData(eventData); CartItem item = new CartItem(); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 225a5e96..2b1c6a5a 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -24,7 +24,6 @@ import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; -import it.gov.pagopa.receipt.pdf.datastore.utils.ObjectMapperUtils; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -84,7 +83,7 @@ class RecoverFailedReceiptTest { private RecoverFailedReceipt sut; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); BizEventToReceiptServiceImpl receiptService = new BizEventToReceiptServiceImpl( pdvTokenizerServiceMock, @@ -98,7 +97,7 @@ public void openMocks() { } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java index 837ea378..64c83200 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java @@ -49,13 +49,13 @@ class RecoverNotNotifiedReceiptMassiveTest { private AutoCloseable closeable; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); sut = spy(new RecoverNotNotifiedReceiptMassive(receiptCosmosServiceMock)); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index 3110e79e..90792df9 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -6,7 +6,6 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; @@ -52,13 +51,13 @@ class RecoverNotNotifiedReceiptTest { private AutoCloseable closeable; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); sut = spy(new RecoverNotNotifiedReceipt(receiptCosmosServiceMock)); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java index f73ebbb8..34d8b921 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -65,12 +65,12 @@ class RecoverFailedReceiptScheduledTest { private RecoverFailedReceiptScheduled sut; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java index 303c67e7..9cdc6260 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java @@ -47,13 +47,13 @@ class RecoverNotNotifiedReceiptScheduledTest { private AutoCloseable closeable; @BeforeEach - public void openMocks() { + void openMocks() { closeable = MockitoAnnotations.openMocks(this); sut = spy(new RecoverNotNotifiedReceiptScheduled(receiptCosmosServiceMock)); } @AfterEach - public void releaseMocks() throws Exception { + void releaseMocks() throws Exception { closeable.close(); } From 1a255aeab8b8130ab841a80922a3b243c574e37f Mon Sep 17 00:00:00 2001 From: Francesco Date: Sun, 21 Dec 2025 16:34:17 +0100 Subject: [PATCH 16/38] fix tests --- .../http/RecoverFailedReceiptMassive.java | 8 +- .../RecoverFailedReceiptScheduled.java | 19 ++- .../RecoverNotNotifiedReceiptScheduled.java | 1 - .../datastore/model/MassiveRecoverResult.java | 1 + .../utils/BizEventToReceiptUtils.java | 12 +- .../http/RecoverFailedReceiptMassiveTest.java | 25 +--- .../http/RecoverFailedReceiptTest.java | 111 +++++------------- .../RecoverFailedReceiptScheduledTest.java | 30 ++--- 8 files changed, 57 insertions(+), 150 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index 8ea84c77..d4ed5d4f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -122,12 +122,12 @@ public HttpResponseMessage run( .build()) .build(); } - List receiptList = recoverResult.getReceiptList(); + + int successCounter = recoverResult.getSuccessCounter(); int errorCounter = recoverResult.getErrorCounter(); - documentdb.setValue(receiptList); if (errorCounter > 0) { - String msg = String.format("Recovered %s receipts but %s encountered an error.", receiptList.size(), errorCounter); + String msg = String.format("Recovered %s receipts but %s encountered an error.", successCounter, errorCounter); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) .body(ProblemJson.builder() @@ -137,7 +137,7 @@ public HttpResponseMessage run( .build()) .build(); } - String responseMsg = String.format("Recovered %s receipts", receiptList.size()); + String responseMsg = String.format("Recovered %s receipts", successCounter); return request.createResponseBuilder(HttpStatus.OK) .body(responseMsg) .build(); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java index 5c93277b..4ca1368c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java @@ -19,10 +19,7 @@ import org.slf4j.LoggerFactory; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.massiveRecoverByStatus; @@ -77,13 +74,9 @@ public void run( ) { if (isEnabled) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - List receiptList = new ArrayList<>(); - - receiptList.addAll(recover(context, ReceiptStatusType.INSERTED)); - receiptList.addAll(recover(context, ReceiptStatusType.FAILED)); - receiptList.addAll(recover(context, ReceiptStatusType.NOT_QUEUE_SENT)); - - documentdb.setValue(receiptList); + recover(context, ReceiptStatusType.INSERTED); + recover(context, ReceiptStatusType.FAILED); + recover(context, ReceiptStatusType.NOT_QUEUE_SENT); } } @@ -95,9 +88,11 @@ private List recover(ExecutionContext context, ReceiptStatusType status logger.error("[{}] Error recovering {} failed receipts for status {}", context.getFunctionName(), recoverResult.getErrorCounter(), statusType); } + List idList = recoverResult.getReceiptList().parallelStream().map(Receipt::getId).toList(); + logger.info("[{}] Recovered {} receipts for status {} with ids: {}", - context.getFunctionName(), recoverResult.getReceiptList().size(), statusType, idList); + context.getFunctionName(), idList.size(), statusType, idList); return recoverResult.getReceiptList(); } catch (NoSuchElementException e) { logger.error("[{}] Unexpected error during recover of failed receipt for status {}", diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java index a7f39f4b..89fd2a54 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java @@ -71,7 +71,6 @@ public void processRecoverNotNotifiedScheduledTrigger( documentReceipts.setValue(receiptList); } - } private List process(ExecutionContext context, ReceiptStatusType statusType) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java index b22c2ded..b87b8e78 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java @@ -12,4 +12,5 @@ public class MassiveRecoverResult { private List receiptList; private int errorCounter; + private int successCounter; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 8fbac0db..6fae1bcb 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -397,8 +397,9 @@ public static MassiveRecoverResult massiveRecoverByStatus( ReceiptCosmosService receiptCosmosService, Logger logger, ReceiptStatusType statusType) { - int errorCounter = 0; List receiptList = new ArrayList<>(); + int successCounter = 0; + int errorCounter = 0; String continuationToken = null; do { Iterable> feedResponseIterator = @@ -409,11 +410,11 @@ public static MassiveRecoverResult massiveRecoverByStatus( try { Receipt restored = getEvent(receipt.getEventId(), context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, receipt, logger); - if (!isReceiptStatusValid(restored)) { - errorCounter++; - } - else { + if (isReceiptStatusValid(restored)) { receiptList.add(restored); + successCounter++; + } else { + errorCounter++; } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -426,6 +427,7 @@ public static MassiveRecoverResult massiveRecoverByStatus( return MassiveRecoverResult.builder() .receiptList(receiptList) + .successCounter(successCounter) .errorCounter(errorCounter) .build(); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java index c902a398..dc49cd56 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -108,17 +108,7 @@ void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVT // test assertion assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - assertEquals(1, receiptCaptor.getValue().size()); - Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(eventId, captured.getEventId()); - assertEquals(tokenizedPayerFiscalCode, captured.getEventData().getPayerFiscalCode()); - assertEquals(tokenizedDebtorFiscalCode, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); + assertEquals("Recovered 1 receipts", response.getBody()); } @Test @@ -225,18 +215,9 @@ void recoverFailedReceiptMassivePartialOK() { ProblemJson problemJson = (ProblemJson) response.getBody(); assertNotNull(problemJson); - assertEquals(HttpStatus.MULTI_STATUS.value(), problemJson.getStatus()); + assertEquals(HttpStatus.MULTI_STATUS.value(), problemJson.getStatus()); // 207 assertEquals("Partial OK", problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - - verify(documentdb).setValue(receiptCaptor.capture()); - assertEquals(1, receiptCaptor.getValue().size()); - Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(bizOk, captured.getEventId()); - assertEquals(tokenizedPayerFiscalCode, captured.getEventData().getPayerFiscalCode()); - assertEquals(tokenizedDebtorFiscalCode, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); + assertTrue(problemJson.getDetail().contains("Recovered 1 receipts but 1 encountered an error")); } @Test diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 2b1c6a5a..08bc4be2 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -101,43 +101,27 @@ void releaseMocks() throws Exception { closeable.close(); } - // TODO Gioele questo scenario è cambiato -// @Test -// @SneakyThrows -// void requestOnValidBizEventShouldCreateRequest() { -// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) -// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); -// -// Response queueResponse = mock(Response.class); -// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); -// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); -// -// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) -// .thenReturn(generateValidBizEvent("1")); -// -// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); -// -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); -// -// // test execution -// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); -// -// // test assertion -// assertNotNull(response); -// assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); -// assertNotNull(response.getBody()); -// -// verify(documentdb).setValue(receiptCaptor.capture()); -// Receipt captured = receiptCaptor.getValue(); -// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); -// assertEquals(EVENT_ID, captured.getEventId()); -// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); -// assertNotNull(captured.getEventData().getCart()); -// assertEquals(1, captured.getEventData().getCart().size()); -// } + @Test + @SneakyThrows + void requestOnValidBizEventShouldNotCreateRequest() { + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) + .thenReturn(generateValidBizEvent("1")); + + when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); + + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + } // @Test // @SneakyThrows @@ -181,47 +165,6 @@ void releaseMocks() throws Exception { // assertEquals(1, captured.getEventData().getCart().size()); // } -// @Test -// @SneakyThrows -// void requestOnValidBizEventTransactionDetailsShouldCreateRequest() { -// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) -// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); -// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) -// .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); -// -// Response queueResponse = mock(Response.class); -// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); -// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); -// -// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) -// .thenReturn(generateValidBizEventWithTDetails("1")); -// -// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); -// -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); -// -// // test execution -// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); -// -// // test assertion -// assertNotNull(response); -// assertEquals(HttpStatus.OK, response.getStatus()); -// assertNotNull(response.getBody()); -// -// verify(documentdb).setValue(receiptCaptor.capture()); -// Receipt captured = receiptCaptor.getValue(); -// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); -// assertEquals(EVENT_ID, captured.getEventId()); -// assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); -// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); -// assertNotNull(captured.getEventData().getCart()); -// assertEquals(1, captured.getEventData().getCart().size()); -// } - - @Test void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) @@ -293,23 +236,24 @@ void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoun // assertEquals(1, captured.getEventData().getCart().size()); // } - // TODO Gioele @Test @SneakyThrows void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken() { when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); -// Response queueResponse = mock(Response.class); -// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); -// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); + when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) + .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); Receipt receipt = createFailedReceipt(); receipt.setEventData(null); when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + BizEvent bizEvent = generateValidBizEvent("1"); + bizEvent.getTransactionDetails().getTransaction().setOrigin("IO"); + when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent("1")); + .thenReturn(bizEvent); doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; @@ -329,6 +273,7 @@ void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken assertEquals(ReceiptStatusType.FAILED, captured.getStatus()); assertEquals(EVENT_ID, captured.getEventId()); assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); + assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); assertNotNull(captured.getEventData().getCart()); assertEquals(1, captured.getEventData().getCart().size()); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java index 34d8b921..55dd12c9 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -13,6 +13,7 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import org.junit.jupiter.api.AfterEach; @@ -28,6 +29,7 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -117,29 +119,11 @@ void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException, PD // test execution assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); - verify(documentdb).setValue(receiptCaptor.capture()); - assertEquals(3, receiptCaptor.getValue().size()); - - Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - - captured = receiptCaptor.getValue().get(1); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - - captured = receiptCaptor.getValue().get(2); - assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); + verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.INSERTED)); + verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.FAILED)); + verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.NOT_QUEUE_SENT)); + + verify(bizEventCosmosClientMock, times(3)).getBizEventDocument(anyString()); } @Test From 5d4658e0f6008a4eea3dc556e5c26ef7c9d6bec6 Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 5 Jan 2026 13:35:21 +0100 Subject: [PATCH 17/38] fix integration tests --- host.json | 9 +- .../src/features/receipt_pdf_helpdesk.feature | 38 ++ integration-test/src/package.json | 5 + .../src/scripts/teardown_script.js | 119 ++++++ .../step_definitions/api_helpdesk_client.js | 194 +++++++++ .../step_definitions/blob_storage_client.js | 46 +++ .../src/step_definitions/common.js | 83 +++- .../receipt_pdf_helpdesk_step.js | 384 ++++++++++++++++++ .../receipts_datastore_client.js | 90 +++- .../helpdesk/http/RecoverFailedReceipt.java | 2 +- .../utils/BizEventToReceiptUtils.java | 5 +- 11 files changed, 959 insertions(+), 16 deletions(-) create mode 100644 integration-test/src/features/receipt_pdf_helpdesk.feature create mode 100644 integration-test/src/scripts/teardown_script.js create mode 100644 integration-test/src/step_definitions/api_helpdesk_client.js create mode 100644 integration-test/src/step_definitions/blob_storage_client.js create mode 100644 integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js diff --git a/host.json b/host.json index 735d345a..9902232d 100644 --- a/host.json +++ b/host.json @@ -21,7 +21,14 @@ "fileLoggingMode": "always", "logLevel": { "Function.BizEventToReceiptProcessor": "Information", - "Function.CartEventToReceiptProcessor": "Information" + "Function.ReceiptToReviewed": "Information", + "Function.CartNotSentRecoverScheduled": "Information", + "Function.RecoverFailedReceipt": "Information", + "Function.RecoverFailedReceiptMassive": "Information", + "Function.RecoverFailedReceiptScheduled": "Information", + "Function.RecoverNotNotifiedReceipt": "Information", + "Function.RecoverNotNotifiedReceiptMassive": "Information", + "Function.RecoverNotNotifiedTimerTriggerProcessor": "Information" }, "applicationInsights": { "samplingSettings": { diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature new file mode 100644 index 00000000..76aa50f6 --- /dev/null +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -0,0 +1,38 @@ +Feature: All about payment events to recover managed by Azure functions receipt-pdf-helpdesk + + Scenario: receiptToReviewed API retrieve a receipt error and updates its status to REVIEWED + Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" and status "TO_REVIEW" stored into receipt-error datastore + When receiptToReviewed API is called with bizEventId "receipt-helpdesk-int-test-id-5" + Then the api response has a 200 Http status + And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" is recovered from datastore + And the receipt-error has not status "TO_REVIEW" + + Scenario: recoverFailedReceipt API retrieve a receipt in status FAILED and updates its status + Given a receipt with eventId "receipt-helpdesk-int-test-id-6" and status "FAILED" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-6" and status "DONE" stored on biz-events datastore + When recoverFailedReceipt API is called with eventId "receipt-helpdesk-int-test-id-6" + Then the api response has a 200 Http status + And the receipt with eventId "receipt-helpdesk-int-test-id-6" is recovered from datastore + And the receipt has not status "FAILED" + + Scenario: recoverFailedReceiptMassive API retrieve all the receipts in status FAILED and updates their status + Given a list of 5 receipts in status "FAILED" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-7" + And a list of 5 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-7" + When recoverFailedReceiptMassive API is called with status "FAILED" as query param + Then the api response has a 200 Http status + And the list of receipt is recovered from datastore and no receipt in the list has status "FAILED" + + Scenario: recoverNotNotifiedReceipt API retrieve a receipt in status IO_ERROR_TO_NOTIFY and updates its status + Given a receipt with eventId "receipt-helpdesk-int-test-id-8" and status "IO_ERROR_TO_NOTIFY" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-8" and status "DONE" stored on biz-events datastore + When recoverNotNotifiedReceipt API is called with eventId "receipt-helpdesk-int-test-id-8" + Then the api response has a 200 Http status + And the receipt with eventId "receipt-helpdesk-int-test-id-8" is recovered from datastore + And the receipt has not status "IO_ERROR_TO_NOTIFY" + + Scenario: recoverNotNotifiedReceiptMassive API retrieve all the receipts in status IO_ERROR_TO_NOTIFY and updates their status + Given a list of 5 receipts in status "IO_ERROR_TO_NOTIFY" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-9" + And a list of 5 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-9" + When recoverNotNotifiedReceiptMassive API is called with status "IO_ERROR_TO_NOTIFY" as query param + Then the api response has a 200 Http status + And the list of receipt is recovered from datastore and no receipt in the list has status "IO_ERROR_TO_NOTIFY" \ No newline at end of file diff --git a/integration-test/src/package.json b/integration-test/src/package.json index 31444a39..0a9bd22f 100644 --- a/integration-test/src/package.json +++ b/integration-test/src/package.json @@ -7,10 +7,15 @@ "test:local": "dotenv -e ./config/.env.local yarn cucumber", "test:dev": "dotenv -e ./config/.env.dev yarn cucumber", "test:uat": "dotenv -e ./config/.env.uat yarn cucumber", + "teardown": "dotenv -e ./config/.env.local node scripts/teardown_script.js", + "teardown:local": "dotenv -e ./config/.env.local node scripts/teardown_script.js", + "teardown:dev": "dotenv -e ./config/.env.dev node scripts/teardown_script.js", + "teardown:uat": "dotenv -e ./config/.env.uat node scripts/teardown_script.js", "cucumber": "npx cucumber-js --publish -r step_definitions" }, "devDependencies": { "@azure/cosmos": "^3.17.3", + "@azure/storage-blob": "^12.14.0", "@cucumber/cucumber": "^9.6.0", "axios": "^0.27.2", "dotenv": "^16.1.4", diff --git a/integration-test/src/scripts/teardown_script.js b/integration-test/src/scripts/teardown_script.js new file mode 100644 index 00000000..c799cb59 --- /dev/null +++ b/integration-test/src/scripts/teardown_script.js @@ -0,0 +1,119 @@ +const { CosmosClient } = require("@azure/cosmos"); +const { BlobServiceClient } = require('@azure/storage-blob'); +const { TOKENIZED_FISCAL_CODE } = require("../../src/step_definitions/common"); + +//COSMOS RECEIPT +const cosmos_db_conn_string = process.env.RECEIPTS_COSMOS_CONN_STRING || ""; +const databaseId = process.env.RECEIPT_COSMOS_DB_NAME; +const receiptContainerId = process.env.RECEIPT_COSMOS_DB_CONTAINER_NAME; +const receiptErrorContainerId = process.env.RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME; +const receiptMessageContainerId = process.env.RECEIPT_MESSAGE_COSMOS_DB_CONTAINER_NAME; + +const client = new CosmosClient(cosmos_db_conn_string); +const receiptContainer = client.database(databaseId).container(receiptContainerId); +const receiptErrorContainer = client.database(databaseId).container(receiptErrorContainerId); +const receiptMessageContainer = client.database(databaseId).container(receiptMessageContainerId); + +//COSMOS BIZEVENT +const biz_cosmos_db_conn_string = process.env.BIZEVENTS_COSMOS_CONN_STRING; +const bizDatabaseId = process.env.BIZ_EVENT_COSMOS_DB_NAME; // es. db +const bizContainerId = process.env.BIZ_EVENT_COSMOS_DB_CONTAINER_NAME; // es. biz-events + +const bizClient = new CosmosClient(biz_cosmos_db_conn_string); +const bizContainer = bizClient.database(bizDatabaseId).container(bizContainerId); + +//BLOB +const blobStorageConnString = process.env.RECEIPTS_STORAGE_CONN_STRING; +const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; + +const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName || ""); + + +const deleteDocumentFromAllDatabases = async () => { + let { resources } = await receiptContainer.items.query({ + query: "SELECT * from c WHERE c.eventData.debtorFiscalCode = @fiscalCode", + parameters: [{ name: "@fiscalCode", value: TOKENIZED_FISCAL_CODE }] + }).fetchAll(); + + console.info(`Found n. ${resources?.length} receipts in the database`); + + for (const el of resources) { + console.log("Cleaning documents linked to receipts with id: " + el.id); + + //Delete PDF from Blob Storage + if (el?.mdAttach?.name?.length > 1) { + let response = await containerClient.getBlockBlobClient(el.mdAttach.name).deleteIfExists(); + if (response._response.status !== 202) { + console.error(`Error deleting PDF ${el.id}`); + } + console.log("RESPONSE DELETE PDF STATUS", response._response.status); + } + if (el?.mdAttachPayer?.name?.length > 1) { + let response = await containerClient.getBlockBlobClient(el.mdAttachPayer.name).deleteIfExists(); + if (response._response.status !== 202) { + console.error(`Error deleting PDF ${el.id}`); + } + console.log("RESPONSE DELETE PDF STATUS", response._response.status); + } + + //Delete Receipt from CosmosDB + try { + await receiptContainer.item(el.id, el.id).delete(); + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting receipt ${el.id}`); + } + } + + //Delete Receipt error from CosmosDB + try { + let response = await receiptErrorContainer.items.query({ + query: "SELECT * from c WHERE c.bizEventId = @bizEventId", + parameters: [{ name: "@bizEventId", value: el.eventId }] + }).fetchAll(); + + let resourcesError = response.resources; + + for (let error of resourcesError) { + await receiptErrorContainer.item(error.id, error.id).delete(); + } + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting receipt error ${el.eventId}`); + } + } + + //Delete Receipt message from CosmosDB + try { + let response = await receiptMessageContainer.items.query({ + query: "SELECT * from c WHERE c.eventId = @eventId", + parameters: [{ name: "@eventId", value: el.eventId }] + }).fetchAll(); + + let resourcesMessage = response.resources; + + for (let message of resourcesMessage) { + await receiptMessageContainer.item(message.id, message.id).delete(); + } + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting receipt message ${el.eventId}`); + } + } + + //Delete BizEvent from CosmosDB + try { + await bizContainer.item(el.eventId, el.eventId).delete(); + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting bizevent ${el.eventId}`); + } + } + + console.log(`DONE ${el.id}`) + } + +}; + +deleteDocumentFromAllDatabases(); \ No newline at end of file diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js new file mode 100644 index 00000000..8f368f3d --- /dev/null +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -0,0 +1,194 @@ +const axios = require("axios"); + +const helpdesk_url = process.env.HELPDESK_URL; + +axios.defaults.headers.common['Ocp-Apim-Subscription-Key'] = process.env.SUBKEY || ""; // for all requests +if (process.env.canary) { + axios.defaults.headers.common['X-CANARY'] = 'canary' // for all requests +} + +async function getReceipt(id) { + let endpoint = process.env.GET_RECEIPT_ENDPOINT || "receipts/{event-id}"; + endpoint = endpoint.replace("{event-id}", id); + console.debug("GET RECEIPT", helpdesk_url + endpoint) + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptMessage(id) { + let endpoint = process.env.GET_RECEIPT_ENDPOINT || "receipts/io-message/{message-id}"; + endpoint = endpoint.replace("{message-id}", id); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptByOrganizationFiscalCodeAndIUV(orgCode, iuv) { + let endpoint = process.env.GET_RECEIPT_BY_ORGCODE_AND_IUV_ENDPOINT || "receipts/organizations/{organization-fiscal-code}/iuvs/{iuv}"; + endpoint = endpoint.replace("{organization-fiscal-code}", orgCode); + endpoint = endpoint.replace("{iuv}", iuv); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptError(id) { + let endpoint = process.env.GET_RECEIPT_ERROR_ENDPOINT || "errors-toreview/{bizvent-id}"; + endpoint = endpoint.replace("{bizvent-id}", id); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptPdf(fileName) { + let endpoint = process.env.GET_RECEIPT_PDF_ENDPOINT || "pdf-receipts/{file-name}"; + endpoint = endpoint.replace("{file-name}", fileName); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postReceiptToReviewed(eventId) { + let endpoint = process.env.RECEIPT_TO_REVIEW_ENDPOINT || "receipts-error/{event-id}/reviewed"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverFailedReceipt(eventId) { + let endpoint = process.env.RECOVER_FAILED_ENDPOINT || "receipts/{event-id}/recover-failed"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverFailedReceiptMassive(status) { + let endpoint = process.env.RECOVER_FAILED_MASSIVE_ENDPOINT || "receipts/recover-failed?status={STATUS}"; + endpoint = endpoint.replace("{STATUS}", status); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverNotNotifiedReceipt(eventId) { + let endpoint = process.env.RECOVER_NOT_NOTIFIED_ENDPOINT || "receipts/{event-id}/recover-not-notified"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverNotNotifiedReceiptMassive(status) { + let endpoint = process.env.RECOVER_NOT_NOTIFIED_MASSIVE_ENDPOINT || "receipts/recover-not-notified?status={STATUS}"; + endpoint = endpoint.replace("{STATUS}", status); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRegenerateReceiptPdf(eventId) { + let endpoint = process.env.REGENERATE_RECEIPT_PDF_ENDPOINT || "receipts/{bizevent-id}/regenerate-receipt-pdf"; + endpoint = endpoint.replace("{bizevent-id}", eventId); + console.debug("POST RECOVER MASSIVE FAILED RECEIPT", helpdesk_url + endpoint); + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverFailedCart(cartId) { + let endpoint = process.env.RECOVER_FAILED_CART_ENDPOINT || "carts/{cart-id}/recover-failed"; + endpoint = endpoint.replace("{cart-id}", cartId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverFailedCartMassive(status) { + let endpoint = process.env.RECOVER_FAILED_CART_MASSIVE_ENDPOINT || "carts/recover-failed?status={STATUS}"; + endpoint = endpoint.replace("{STATUS}", status); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +module.exports = { + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptMessage, + getReceiptPdf, + postReceiptToReviewed, + postRecoverFailedReceipt, + postRecoverFailedReceiptMassive, + postRecoverNotNotifiedReceipt, + postRecoverNotNotifiedReceiptMassive, + postRegenerateReceiptPdf, + postRecoverFailedCart, + postRecoverFailedCartMassive +} \ No newline at end of file diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js new file mode 100644 index 00000000..dacf864a --- /dev/null +++ b/integration-test/src/step_definitions/blob_storage_client.js @@ -0,0 +1,46 @@ +const { BlobServiceClient } = require('@azure/storage-blob'); + +const blobStorageConnString = process.env.RECEIPTS_STORAGE_CONN_STRING; +const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; + +const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName || ""); + +async function uploadBlobFromLocalPath(fileName, localFilePath) { + const blobClient = containerClient.getBlockBlobClient(fileName); + + try { + return await blobClient.uploadFile(localFilePath); + } catch (err) { + return { status: 500 } + } +} + +async function deleteBlob(blobName) { + // include: Delete the base blob and all of its snapshots. + // only: Delete only the blob's snapshots and not the blob itself. + const options = { + deleteSnapshots: 'include' // or 'only' + } + + // Create blob client from container client + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + await blockBlobClient.deleteIfExists(options); +} + +async function receiptPDFExist(blobName) { + let blobs = containerClient.listBlobsFlat(); + for await (const blob of blobs) { + if (blob.name === blobName) { + return true; + } + } + return false; +} + +module.exports = { + uploadBlobFromLocalPath, + deleteBlob, + receiptPDFExist +} \ No newline at end of file diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 4640fa62..ba2fee9c 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -1,3 +1,6 @@ +const TOKENIZED_FISCAL_CODE = "cd07268c-73e8-4df4-8305-a35085e32eff"; +const FISCAL_CODE = "AAAAAA00A00A000A"; + const axios = require("axios"); const datastore_url = process.env.DATASTORE_URL; @@ -7,6 +10,17 @@ if (process.env.canary) { axios.defaults.headers.common['X-CANARY'] = 'canary' // for all requests } +const getTokenizedBizEvent = () => { + let environment = process.env.ENVIRONMENT || ""; + if (environment === "uat") { + return "775WJQduojxFn6xp3J8LfQccR0A4e4sP3ifh9mRytS2p5xdTU3hIapEdpL85PiExnnkv60Qo88HQPyTF5LLnpcgA+rTigKKkQZg6bJ31L5B9m8Iwl3A/SSyHZ7rPhuDCIcP/zo3tkh+6SKCx6teuqmXqZ9nug2VVz9H3KAYNTyBEvbf2mTifHVSmF8qO2bquZ+FuJUcR4wZXGofjlXCunIdQ+xoS4/tqD/1GBiYLG72MeAJM9O7aQZHrgQUR4TurvUIj8G04XBCnP6in8reyK5pniWvfX6Il4iN46tPrdVJprv1ZQlzun6Vq5gnzL1RLHdUgK7OogfolqG5tAz2vULl0QJPOBUSXbTUVqhZnMhE9sTYTUrtHMekievYiW/S4SjYnRwDbiEmyrS7orIu345+jFOqZ5ONo80aKxS1FUuLUiTg5xg4Ozm/6I8BNVzLJDZVHU40XTIpAnM5LsD3cRXSkyMD355UqRkGzOfk7PUhJOOQNkGGju+CVVk+Qzp1DmJsJif5SYR9p+Pd4EWmJpO68Dxo5fOeXMiq4ZyTtV1Dp+HApScUjDzFssN0Q3mk3ih94S1MJ5BHY6zdpbER+BJaEjPxX2G0mK+wF6xdPjdaZa8RLQBhz/VkUcxIIOXAPrbu0hZDc4v7AxCRzvznocp+oGL0dmdU8wqKWOjQzeOREp/UvMc8+8SplJqJQA1fF/PO00lyFKYgNbMMtVbsXVnbZCPjSQLjTtdaHXA85PF+ocUkLmtAoz28nAHfxQmoB+4/ACGyqABsFRVMYwlBCmvc9TvadKD+mwZy9PGz5qrp2VOBz1KppWvKUwTvAyJtxXXk78DJQBWx4I6nVXAfAoeKBhvJB+FxYDmHcmHtJdDtlZDzbgrwHjLKmbtpOfRaPN6TAKLjh6SD4hBOifzRt1k6yHHn7BJkiAALlvSnGAP17Fzm9uNXTHreBoV6fJuV3sJIpvPXJPqZfWfnXwkE4YCxsFLtAuCdn2WCGii4g/k4cQljrNi5MiSoDdLDXbjZTY/uey9TjiXe2P/WLmU1Sc8hWD/rKsUas0LHHB/NbiskiaYj/A/nwCOR+7tfcoVBpru3t+yFkgonL4Xr4Ez5rt7fOj7BcRup0iyOKBAHxcvO2mo0M67pfZBJY7oBg7jfjDobO8PSFl1ua7k5obwj6nBuzE4TCeSKpJ2qTmbdExYqgS5+Ahq0iRTertrtK1KWqDzDlM9dWKvsAz5rGwP0EiW9/m6MGZhRmQHxe/jiMxSvdTk25+9MxMBth389dNT7RZy3eu1WbBdfmuqNS5DL0+rhWHi59mnQkXxHLuPbaWKTUy7uXKzc/kN+sLnE5oIM60+wiJTlpZF/kohKNUtrIwryW6xN6hQ/MY0ZoHdafbB9MNY+TfA30t99MGZrruzCanWil8XYURGghQ+0Lwe8IGM53IQqBFlilnqxD6GjDjnBHQQpeIlJe32wjzTWh9d5t3q+Smo1lfR0pppJzaVvjAEEGuNCUOQPgbBY9hWVy8aOGfLAaQ2EF4RR5F0FWKhluOcYqwwTYV7DORafxAEwNeTRxIN+4NaZaR4iOoPpILVAp4TG7zyRLhw5qzNNmQrS2HGovzBuxA3I4U1NdmXarFLiw6LSImzwytKBJ9qPHDNbLPpM+X48pfLvPECKac/UmmMFlMNATUoawGY4zlGXo/qoxz2Sow1Hs887KnlgCv6z0dxIlIuOLYSiu1RJuwEO4Bbn3+n7bsOayZpEIw9WTC06Coq+Q5VjQ1IgKABlX/nwx1NwzHFjCVWWIOCEst7WDeLYCdt4aZ92ZNA/RNV112iVg5dE820m0TpuYedcALTOUAlPNI2ECZvPlq2LxHQCggND8bp3dq2lUCzYykO8xkmegRHlhPIqaGF2pzELKHrKOLNBX/Nll+XjQdyvvcg9AJ0wANhMzBt0QKRBl54hNo2LfMBVLHrvA+JDyQ0qZ2yOykQ3O7m0McnzKaJW7erXDL39vYibuxBnr/Dp0lQP7pRt5x7zwIlO4CyD5cJ4a3KkBvQ0tGGqJwXhUwuVetH8/HhG2itVs5NXFIWUCUyxBJp/bKlV/v/Q5Wc95EeDjcP5GyjbLha8haQ50UqJFB7up3/kwmn8DNSXl1vTx/nmVS1FCj109o2KccY+qRZVgPr8eBYBdY/p59O5yZxb3rsh8v7y9+cGzDFW7N1sNbSrKQWgRT1pzleGjElPDBg6bA2j4RwCTNJfJI+U9iTOoVdxuu05o9qJdIrEgSLvMCV9s2yzLcsiLfFwTI1V3gSl0FzMjh+M6eI35b2TuSEthkvvefgBzfA526EfNYp8wEX7E7n9QxDqJj+UXflO11EeICTiurukTZlcrjECT2blXYFfB+odEk9U7NZbyG0faM3kCFOwahxoy3NwG4S/mJZDPRGnqOQe8v//0IDLijrhaCZm3MgMWfUQXti0AiyvblBNOhQ+Es4m4SKFEP8MzjQxnfdgnHVl5ZgsSRT7/sDb8pNpJ0A8nB+rcGvFah9YlAv0kDn/aoCy/3ImLAKvcGdwSKJH0t0wHMLGwk7ePuAS8jhZV4atAKRVTkqQxTraoBbZCzzbkpHdUI0HDyD9jQXwoht1XoiNeQCiLGin0EDHKOuQevyLE2QjJf56OVNzFfr6AclKARFfanZ4jLK9BViUzZCp7VXsJHAf5M13gazMnFAgu1nckzKrSLj5e7+NrxAKzVYtESAxKgrTaXQ9AalbFbkaYsymXp5iAZLbsDAXmesZtuVeERytBxQEUW4kmrs/GbNWDMH8mQFvXxxRLSqRp+Ygr88wDnlkKYn629w9CZ1lcJbntyyU0Q1g0ieP0YP5RFp+abY3O2NY0oW2rS7FUMpz0diq1Ofhr0Qn3Ymr5ophzYfB9GJ8p7V8XQJlKOvoBidrREDnFsNoVyC1ytXzN7UzOxNmKXpIPnLbJelcVM6xrd9uM/UjKIQ0Nqg5jB8hztuDx1hSlo+jZhzneme9Yq0OucacURhuy4OGxWLt6lOwTniHgWQkstZ+SftKeumCAIN0bOaFiOCKZeY9Uxezib2oii5jIdNvKth+q9V+o0bAljA/6oo0NtlZKSHy+QThLofSSXpnoOnPGMOARgRzKDZXvaZ6ingmtFrTH1sNWo7fNuvFcM2aqog86T5fA3khWYm1LxmHFJbmvPsRv5mUZPGJCo4hOC+KoIlE+e9KwYCix"; + } + + return "AWL5bnBg68AfzYAVg4yQgEhXUeja1sPe95M8FI3pL+RHha0ZthwheWJRraeqEmu8tiW13pyjwVY6U9J2NnmJW9ouli+vWa3q8igmT1t1PIaRgcivLPumU+HKkCRRgrXXQbYpzb76yH9deGJfI4BX7AlBywGx0rDkY/80nI6vbSOFYmg8s175fQqmkLmpG+k39IJt558z88pOaWGp+Xsmn7wUivQdI+c2MYiYto9VhieGS/R3IX+EhOEQLm6NxlOKfC8auP3bf26sDnLIpLjXGG7xBG8tSe07Gx1m9ubNzFV2Kr1AGxhq8cht+cnprMfNfXG262aPaW3HxTVtQ3FpWz3NY4yMG7NtqU9+YycnXfL9OZrwYIV3R51hoh5TrdPYC/5arcRksdDvKvFvdn/2kGyDyl/t/Mg7DjlnyH6xlokNzxiMZGCbALp9Y9cEaBqsucYztwVbIuq1e0CB8CgaKi3eYrYje6sgq7DV0WjT2lyMmWKv5aMLNk9vhCEsSZpyXvUIafcyEVNKe8GiCMJjp2JLNvf0ELsHEoY81p4T+S6P9lkP6mKBsbQChVG5KFvL/ZrMpP1Lx6boVZMXnTnrO5/eYwlMhW/ldEfjsg8vm2t5FWc/aBHKnoxFGyRcHGB0Gexs36AXGDgDJzb03bcZm4e3PxwHndVB3maCMUR6uGQVZB6cEHWZ4qZo2Hqa+UpE7SplUTI+CftMK3FbqzG1OTI4uZ2Bt+hRHVqBTOJJJwA/S8JuuW5lNSPQD2bZJuSPZKujgMrYCt3HDnpimR+2QDge6YDPm2KK/CB4vNGJAkwaDLJV3U36hRgJa8+dyIdoc5SlrpdKSIeLzSeKSwiFfI06V9wioTeQ0gJV9bSafQ+NkhxyJxeH/AUb3TTV3AzYwuO/nmCk9kkrO75VH8c9ICxpnQ34TLrApG9BeXSdn5cMmqB3CPrCSjHMBKtiFiypO87XOgAE56anSP3yKJtYCCCMvoYiUTNMhS593dNYoYRYzsI+E7u2gW8WBgPQidqfWGAQXTrzkL7z2GVYtCtNPoAvJpaf0oenS3RunR72G8GMvX7fEiwaFNUAeDBQZkJSUq/vpEAASBav7vEnwF3xolBHsTpe39V92V2Bq5IQpwtw6ZUVBu5a43aRt43nkmxqZLmGaw71XOcoymDH1c/OYbEPHwSyR9jRw0L74lRAO+jN6e3/9cOTPp+6C4E09OwDUYeCKMi2bZrBbKuTf1BGE9AsSQddlqWCsnY0J04RL5E27ehx5yywiRblTL8W8z2whRJG8/OYz3x0JmbH2jNW3ST9IDUMZkN5qOwy/KbdULvmhuaTlVVMKQt19fNOTJHg5WwPCl9vbHFPbiE505CzdqOEmecy6KvZxy6NUuMDaToiqgCZKDXlzDFhOoWNcKKN1kHwyQCoKZRCVTiY5iPWbQQ4hOd858BgkDpUV60iN52PYkeGyl/OOMHYFjEENU/sbWZBhzxOHd++fHbuhWyIR5iTJmign+2nkEcReDT7jm8cvMVeo/92QETbgNfIvLyx9sW8fsD0mdr9VJpFzsvRNm6rpylKf2Fjad0wbHFGgdhcCJtb8BLEBvsrfKPWFpaxY6GDIfs+kwiIHw5dekQ8yyL94ngofVfnPHcgp9LtVSS1RUo/PnGfE/imJardA+hMG0yJusfwMtNDnuyQwiV8cfHpblTqTf0eVD/MLinKv7naK5Hcg9hpyDaBnopMsJs8NN6WWhY30xHHQaKjiaX9SSyzYAuzhkeIjUkMRJcKELS3DnY34pIadSLAlZG3WGPV1QaVPJLf7SziXAiRhER9Cf32zpwYbddYysP52qd+jmsK5X70Z4pFm0JIuYlQ784ri7V4gmS2IwY7+2JvsJCJz+9Rx7hSHuaQteq525xCDJPvNoDAKlvfBugvio58WHjWNeBauJ+MnmeeTf66xb35coZ1DrSYlr04n1O4Jd/5VrW2nUfXlZiaGHSVSlaQ6gJ6d6PYd/gb3V3xIFIbVNWNMng9aZ23gCZg9HVJctJN9DDjryzgDHtUJ29Fdxgn677401TapY8OaXqeacVZ6aCDk0vO806Yv/TJuoc/y2EzOpkK0T7FjbaBaIK7tzsb6EOdN5aLpBG7/xu6Jv/m7udxVyPQHCfJn5eKfxwzlzuceBQC8+NEvQHI9txSW1sdODW9kjJP0DoLQt0sclFoNBlzyhHLT1qB90CAdArFQvdzCfEvZXQj5Zyfw0/NqlUTKtsRxXwRsRYiwuXvo6P4kICzGqpgPgJGOFTdFp2vYLe+4VYfeRCdOn0hpFW23CIPoA8llMrF1+LSImBEIGEiElu4SLrp9qnhFqNfWCwzshxS7ukgRd1CqfjL9962AYLxja/+RheFKul3i/kLSy2+ZQgN8aJ6gccAAdrTzhWMvlj4eSytOgL8uBDuY0bxvmFEQ98pBBVReCFxsFYWsxITFFLcKAkV0z+2XFy8HCRU7EUra2QUM8QBfSPd4QHyalSTs7BPP/jntuIwe2YrCsh3D1LEOMy1o/CQK5O7Vh2Wwcx7jyATwQNCh9X2VKW+DyaZCuq7nON7aZKIbgBDYMyPZSnXDdjO+o1egCp8+KVUsLPwC3vYTTS8P+/atW8pDNDrwjh8ou/StAfB9JKf9UqBVbvZeqEB+rubLF0kWBOTnQ80/+eZrXz3GhmAx82tt+r/fNnu0n3N232LW3YSxZKP9YQ+UarQrR/vQLnuFXwUabsN/txY/0L83Ud30Jg0kOX0P5kACKkv/hnZfLi1SnQXS7s87WrviWwIAOHo2Z62jzLCGyW55JnWQgCCouOimN11F9iJJl+dU2I3P+YsI7ITAUuvMJYo4XcdFS3FAJ6Jo+Se8pZNcQukOzyOqVNs2Fw5JMQ9K3ak7ZN2iWGshycnYE3B43g/nNR/dRNguzOrOol0C7dgsMZ7sloqin/Mmop2yDB/+nMPyuAbif15mKhLkCcRYaYJ4W9+ZecUVzTeRoeUslaJvY+Zv9b/5H3SIKK9fvK1A5mI5fEXhZnE61/yZIqr8g8rpGiUs9HtXCDvBtkYyhic6BRL4b/CaYI2n03a4TK6J7ua5YNSE86epl0G30FX3mi4t0zDsY9aZko9rFi85DaqFD52dVC81XVim26AvusfB26lh501W3HyvaJ8mgvXtPoXR+kUYGEhAfnVbxacLCU1mvPv1WphQvI36v2IQAXW"; +} + +const TOKENIZED_BIZ_EVENT = getTokenizedBizEvent(); + function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -134,21 +148,59 @@ function createEvent(id, transactionId, totalNotice) { return json_event } -function createReceipt(id, fiscalCode, pdfName) { +// function createReceipt(id, fiscalCode, pdfName) { +// let receipt = +// { +// "eventId": id, +// "eventData": { +// "debtorFiscalCode": fiscalCode, +// "payerFiscalCode": fiscalCode +// }, +// "status": "IO_NOTIFIED", +// "mdAttach": { +// "name": pdfName, +// "url": pdfName +// }, +// "id": id +// } +// return receipt +// } + +function createReceiptError(id, status) { + return { + "id": id, + "bizEventId": id, + "messagePayload": TOKENIZED_BIZ_EVENT, + "messageError": "Unexpected error when decrypting the given string", + "status": status || "TO_REVIEW", + } +} + +function createReceipt(id, status) { + let currentDate = new Date(); let receipt = { "eventId": id, "eventData": { - "debtorFiscalCode": fiscalCode, - "payerFiscalCode": fiscalCode - }, - "status": "IO_NOTIFIED", - "mdAttach": { - "name": pdfName, - "url": pdfName + "payerFiscalCode": TOKENIZED_FISCAL_CODE, + "debtorFiscalCode": TOKENIZED_FISCAL_CODE, + "amount": "200", + "cart": [ + { + "payeeName": "Comune di Milano", + "subject": "ACI" + } + ] }, + "status": status || "INSERTED", + "numRetry": 0, + "inserted_at": currentDate.getTime() - 360000, + "generated_at": currentDate.getTime() - 360000, "id": id } + if (status === "IO_NOTIFIED" || status === "GENERATED") { + receipt.mdAttach = { "name": "helpdesk-pdf-p.pdf", "url": "helpdesk-pdf-p.pdf" }; + } return receipt } @@ -161,6 +213,19 @@ function createCartEvent(id, listOfBizEventsIds) { } } +function makeId(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + let counter = 0; + while (counter < length) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + counter += 1; + } + return result; +} + module.exports = { - createEvent, sleep, createCartEvent + TOKENIZED_FISCAL_CODE, + createEvent, sleep, createCartEvent, createReceipt, createReceiptError, makeId } \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js new file mode 100644 index 00000000..a65f3d54 --- /dev/null +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -0,0 +1,384 @@ +const assert = require('assert'); +const { Before, After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); +let fs = require('fs'); +const { sleep, makeId } = require("../../src/step_definitions/common"); +const { + createDocumentInBizEventsDatastore, + createDocumentInBizEventsDatastoreIsCartEvent, + createDocumentInBizEventsDatastoreWithIUVAndOrgCode, + deleteDocumentFromBizEventsDatastore } = require("../../src/step_definitions/biz_events_datastore_client"); +const { + deleteDocumentFromReceiptsDatastore, + createDocumentInReceiptsDatastore, + deleteMultipleDocumentsFromReceiptsDatastoreByEventId, + createDocumentInReceiptErrorDatastore, + deleteDocumentFromReceiptErrorDatastore, + getDocumentFromReceiptsErrorDatastoreByBizEventId, + getDocumentFromReceiptsDatastoreByEventId, + deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, + deleteDocumentFromReceiptMessageDatastore, + createDocumentInReceiptIoMessageDatastore, + createDocumentInCartDatastore, + deleteDocumentFromCartDatastore, + getDocumentFromCartDatastoreById +} = require("../../src/step_definitions/receipts_datastore_client"); +const { + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptMessage, + getReceiptPdf, + postReceiptToReviewed, + postRecoverFailedReceipt, + postRecoverFailedReceiptMassive, + postRecoverNotNotifiedReceipt, + postRecoverNotNotifiedReceiptMassive, + postRegenerateReceiptPdf, + postRecoverFailedCart, + postRecoverFailedCartMassive +} = require("../../src/step_definitions/api_helpdesk_client"); +const { uploadBlobFromLocalPath, deleteBlob, receiptPDFExist } = require("../../src/step_definitions/blob_storage_client"); + +// set timeout for Hooks function, it allows to wait for long task +setDefaultTimeout(360 * 1000); + +// initialize variables +let eventId = null; +let messageId = null; +let responseAPI = null; +let receipt = null; +let receiptError = null; +let receiptMessage = null; +let receiptPdfFileName = null; +let listOfReceipts = []; +let bizEventIds = []; +let cart = null; +let cartList = []; + +// Before each Scenario +Before({tags: '@clean-up-required'}, async function () { + +}) + +// After each Scenario +After(async function () { + // remove event + if (eventId != null) { + await deleteDocumentFromBizEventsDatastore(eventId); + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId); + await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); + } + if (receiptPdfFileName != null) { + await deleteBlob(receiptPdfFileName); + if(fs.existsSync(receiptPdfFileName)){ + fs.unlinkSync(receiptPdfFileName); + } + } + if(listOfReceipts.length > 0){ + for(let receipt of listOfReceipts){ + await deleteDocumentFromReceiptsDatastore(receipt.id); + await deleteDocumentFromBizEventsDatastore(receipt.eventId); + } + } + if (cart != null) { + for (let id of cart.cartPaymentId) { + await deleteDocumentFromBizEventsDatastore(id); + } + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(cart.id); + await deleteDocumentFromCartDatastore(cart.id); + } + if(cartList.length > 0){ + for(let cart of cartList){ + for (let id of cart.cartPaymentId) { + await deleteDocumentFromBizEventsDatastore(id); + } + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(cart.id); + await deleteDocumentFromCartDatastore(cart.id); + } + } + if (receipt != null) { + await deleteDocumentFromReceiptsDatastore(receipt.id); + } + + eventId = null; + responseAPI = null; + receipt = null; + receiptError = null; + receiptPdfFileName = null; + listOfReceipts = []; + bizEventIds = []; + cart = null; + cartList = []; +}); + +//Given +Given('a biz event with id {string} and status {string} stored on biz-events datastore', async function (id, status) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromBizEventsDatastore(eventId); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastore(eventId, status); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); +}); + +Given('a biz event with id {string} and status {string} and organizationFiscalCode {string} and IUV {string} stored on biz-events datastore', async function (id, status, orgCode, iuv) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromBizEventsDatastore(eventId); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastoreWithIUVAndOrgCode(id, status, orgCode, iuv); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + }); + +Given('a receipt with eventId {string} and status {string} stored into receipt datastore', async function (id, status) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptsDatastore(id); + + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id, status); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); +}); + +Given('a receipt-error with bizEventId {string} and status {string} stored into receipt-error datastore', async function (id, status) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptErrorDatastore(id); + + let receiptsStoreResponse = await createDocumentInReceiptErrorDatastore(id, status); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); +}); + +Given("a receipt pdf with filename {string} stored into blob storage", async function (fileName) { + receiptPdfFileName = fileName; + // prior cancellation to avoid dirty cases + await deleteBlob(fileName); + + fs.writeFileSync(fileName, "", "binary"); + let blobStorageResponse = await uploadBlobFromLocalPath(fileName, fileName); + assert.notStrictEqual(blobStorageResponse.status, 500); +}); + +Given("a list of {int} receipts in status {string} stored into receipt datastore starting from eventId {string}", async function (numberOfReceipts, status, startingId) { + listOfReceipts = []; + for (let i = 0; i < numberOfReceipts; i++) { + let nextEventId = startingId + i; + // prior cancellation to avoid dirty cases + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(nextEventId); + + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(nextEventId, status); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); + + listOfReceipts.push(receiptsStoreResponse.resource); + } +}); + +Given("a list of {int} biz events in status {string} stored into biz-events datastore starting from eventId {string}", async function (numberOfEvents, status, startingId) { + for (let i = 0; i < numberOfEvents; i++) { + let nextEventId = startingId + i; + // prior cancellation to avoid dirty cases + await deleteDocumentFromBizEventsDatastore(nextEventId); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastore(nextEventId, status); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + } +}); + +//When +When("getReceipt API is called with eventId {string}", async function (id) { + responseAPI = await getReceipt(id); + receipt = responseAPI.data; +}); + +When("getReceiptByOrganizationFiscalCodeAndIUV API is called with organizationFiscalCode {string} and IUV {string}", async function (orgCode, iuv) { + responseAPI = await getReceiptByOrganizationFiscalCodeAndIUV(orgCode, iuv); + receipt = responseAPI.data; +}); + +When("getReceiptError API is called with bizEventId {string}", async function (id) { + responseAPI = await getReceiptError(id); + receiptError = responseAPI.data; +}); + +When("getReceiptPdf API is called with filename {string}", async function (filename) { + responseAPI = await getReceiptPdf(filename); +}); + +When("receiptToReviewed API is called with bizEventId {string}", async function (id) { + responseAPI = await postReceiptToReviewed(id); +}); + +When("recoverFailedReceipt API is called with eventId {string}", async function (id) { + responseAPI = await postRecoverFailedReceipt(id); +}); + +When("recoverFailedReceiptMassive API is called with status {string} as query param", async function (status) { + responseAPI = await postRecoverFailedReceiptMassive(status); +}); + +When("recoverNotNotifiedReceipt API is called with eventId {string}", async function (id) { + responseAPI = await postRecoverNotNotifiedReceipt(id); +}); + +When("recoverNotNotifiedReceiptMassive API is called with status {string} as query param", async function (status) { + responseAPI = await postRecoverNotNotifiedReceiptMassive(status); +}); + +//Then +Then('the api response has a {int} Http status', function (expectedStatus) { + assert.strictEqual(responseAPI.status, expectedStatus); +}); + +Then('the receipt has eventId {string}', function (targetId) { + assert.strictEqual(receipt.eventId, targetId); +}); + +Then('the receipt has not status {string}', function (targetStatus) { + assert.notStrictEqual(receipt.status, targetStatus); +}); + +Then("the receipt-error has bizEventId {string}", async function (id) { + assert.strictEqual(receiptError.bizEventId, id); +}); + +Then("the receipt-error payload has bizEvent decrypted with eventId {string}", async function (id) { + let messagePayload = JSON.parse(receiptError.messagePayload); + assert.strictEqual(messagePayload.id, id); +}); + +Then("the receipt-error has not status {string}", async function (status) { + assert.notStrictEqual(receiptError.status, status); +}); + +Then("the receipt-error with bizEventId {string} is recovered from datastore", async function (id) { + let responseCosmos = await getDocumentFromReceiptsErrorDatastoreByBizEventId(id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + receiptError = responseCosmos.resources[0]; +}); + +Then("the receipt with eventId {string} is recovered from datastore", async function (id) { + let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + receipt = responseCosmos.resources[0]; +}); + +Then("the list of receipt is recovered from datastore and no receipt in the list has status {string}", async function (status) { + for (let recoveredReceipt of listOfReceipts) { + let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(recoveredReceipt.eventId); + assert.strictEqual(responseCosmos.resources.length > 0, true); + assert.notStrictEqual(responseCosmos.resources[0].status, status); + } +}); + +Then('the receipt has attachment metadata', function () { + assert.strictEqual(receipt.mdAttach != undefined && receipt.mdAttach != null && receipt.mdAttach.name != "", true); + receiptPdfFileName = receipt.mdAttach.name; +}); + +Then('the PDF is present on blob storage', async function () { + let blobExist = await receiptPDFExist(receiptPdfFileName); + assert.strictEqual(blobExist, true); + }); + +Then("wait {int} ms", async function (milliSec) { + sleep(milliSec) +}); + +Given('a receipt-io-message with bizEventId {string} and messageId {string} stored into receipt-io-message datastore', async function (eventId, messageId) { + messageId = messageId; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptMessageDatastore(messageId); + + let receiptsMessageStoreResponse = await createDocumentInReceiptIoMessageDatastore(eventId, messageId); + assert.strictEqual(receiptsMessageStoreResponse.statusCode, 201); +}); + +When("getReceiptMessage API is called with messageId {string}", async function (id) { + responseAPI = await getReceiptMessage(id); + receiptMessage = responseAPI.data; +}); + +Then("the receipt-message has eventId {string}", async function (id) { + assert.strictEqual(receiptMessage.eventId, id); +}); + +Then("the receipt-message has messageId {string}", async function (id) { + assert.strictEqual(receiptMessage.messageId, id); +}); + + +Given('a biz event with transactionId {string} and status {string} stored on biz-events datastore', async function (transactionId, status) { + let id = transactionId + makeId(5); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastoreIsCartEvent(id, transactionId, status, "2"); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + bizEventIds.push(id); +}); + +Given('a cart with id {string} and status {string} stored into cart datastore', async function (id, status) { + // prior cancellation to avoid dirty cases + await deleteDocumentFromCartDatastore(id); + + let cartStoreResponse = await createDocumentInCartDatastore(id, bizEventIds, status); + assert.strictEqual(cartStoreResponse.statusCode, 201); + cart = cartStoreResponse.resource; +}); + +When("recoverFailedCart API is called with cartId {string}", async function (id) { + responseAPI = await postRecoverFailedCart(id); +}); + +Then("the cart with id {string} is retrieved from datastore", async function (id) { + let responseCosmos = await getDocumentFromCartDatastoreById(id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + cart = responseCosmos.resources[0]; +}); + +Then('the cart has not status {string}', function (targetStatus) { + assert.notStrictEqual(cart.status, targetStatus); +}); + +Given("a list of {int} carts in status {string} stored into cart datastore starting from id {string}", async function (numberOfCarts, status, startingId) { + cartList = []; + for (let i = 0; i < numberOfCarts; i++) { + let nextTransactionId = startingId + i; + + let id = nextTransactionId + makeId(5); + let id2 = nextTransactionId + makeId(5); + let cartStoreResponse = await createDocumentInCartDatastore(nextTransactionId, [id, id2], status); + assert.strictEqual(cartStoreResponse.statusCode, 201); + cartList.push(cartStoreResponse.resource); + } +}); + +Given("a list of {int} biz events in status {string} stored into biz-events datastore", async function (numberOfEvents, status) { + assert.strictEqual(numberOfEvents / 2, cartList.length); + for (let cart of cartList) { + let transactionId = cart.id; + for (let bizEventId of cart.cartPaymentId) { + let bizEventStoreResponse = await createDocumentInBizEventsDatastoreIsCartEvent(bizEventId, transactionId, status, "2"); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + } + } +}); + +When("recoverFailedCartMassive API is called with status {string} as query param", async function (status) { + responseAPI = await postRecoverFailedCartMassive(status); +}); + +Then('the list of cart is retrieved from datastore and no cart in the list has status {string}', async function (status) { + for (let cart of cartList) { + let responseCosmos = await getDocumentFromCartDatastoreById(cart.id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + assert.notStrictEqual(responseCosmos.resources[0].status, status); + } +}) + +Then('the list of receipt is retrieved from datastore and no receipt in the list has status {string}', async function (status) { + listOfReceipts = []; + for (let cart of cartList) { + let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(cart.id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + assert.notStrictEqual(responseCosmos.resources[0].status, status); + listOfReceipts.push(responseCosmos.resources[0]); + } +}) diff --git a/integration-test/src/step_definitions/receipts_datastore_client.js b/integration-test/src/step_definitions/receipts_datastore_client.js index f11b6737..ac22bb35 100644 --- a/integration-test/src/step_definitions/receipts_datastore_client.js +++ b/integration-test/src/step_definitions/receipts_datastore_client.js @@ -1,14 +1,17 @@ const { CosmosClient } = require("@azure/cosmos"); -const { createCartEvent } = require("./common"); +const { createReceipt, createCartEvent, createReceiptError } = require("./common"); const cosmos_db_conn_string = process.env.RECEIPTS_COSMOS_CONN_STRING || ""; const databaseId = process.env.RECEIPT_COSMOS_DB_NAME; const receiptContainerId = process.env.RECEIPT_COSMOS_DB_CONTAINER_NAME; const cartContainerId = process.env.RECEIPTS_COSMOS_CART_CONTAINER_NAME; +const receiptErrorContainerId = process.env.RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME; const client = new CosmosClient(cosmos_db_conn_string); const receiptContainer = client.database(databaseId).container(receiptContainerId); const cartContainer = client.database(databaseId).container(cartContainerId); +const receiptErrorContainer = client.database(databaseId).container(receiptErrorContainerId); + async function getDocumentByIdFromReceiptsDatastore(id) { return await receiptContainer.items @@ -65,6 +68,89 @@ async function deleteDocumentFromCartDatastore(id, eventId) { } } +async function createDocumentInReceiptsDatastore(id, status) { + let receipt = createReceipt(id, status); + try { + return await receiptContainer.items.create(receipt); + } catch (err) { + console.log(err); + } +} + +async function getDocumentFromReceiptsDatastoreByEventId(id) { + return await receiptContainer.items + .query({ + query: "SELECT * from c WHERE c.eventId=@eventId", + parameters: [{ name: "@eventId", value: id }] + }) + .fetchNext(); +} + +async function createDocumentInReceiptErrorDatastore(id, status) { + let receipt = createReceiptError(id, status); + try { + return await receiptErrorContainer.items.create(receipt); + } catch (err) { + console.log(err); + } +} + +async function deleteDocumentFromReceiptErrorDatastore(id) { + try { + return await receiptErrorContainer.item(id, id).delete(); + } catch (error) { + if (error.code !== 404) { + console.log(error) + } + } +} + +async function getDocumentFromReceiptsErrorDatastoreByBizEventId(id) { + return await receiptErrorContainer.items + .query({ + query: "SELECT * from c WHERE c.bizEventId=@bizEventId", + parameters: [{ name: "@bizEventId", value: id }] + }) + .fetchNext(); +} + +async function deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId) { + let documents = await getDocumentFromReceiptsDatastoreByEventId(eventId); + + documents?.resources?.forEach(el => { + deleteDocumentFromReceiptsDatastore(el.id); + }) +} + +async function deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(id) { + let documents = await getDocumentFromReceiptsErrorDatastoreByBizEventId(id); + + documents?.resources?.forEach(el => { + deleteDocumentFromReceiptErrorDatastore(el.id); + }) +} + +async function deleteDocumentFromReceiptsDatastore(id) { + try { + return await receiptContainer.item(id, id).delete(); + } catch (error) { + if (error.code !== 404) { + console.log(error) + } + } +} + +async function getDocumentFromReceiptsDatastoreByEventId(id) { + return await receiptContainer.items + .query({ + query: "SELECT * from c WHERE c.eventId=@eventId ORDER BY c._ts DESC", + parameters: [{ name: "@eventId", value: id }] + }) + .fetchNext(); +} + module.exports = { - getDocumentByIdFromReceiptsDatastore, deleteDocumentFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, createDocumentInCartDatastore, deleteDocumentFromCartDatastore, getCartDocumentByIdFromReceiptsDatastore + getDocumentByIdFromReceiptsDatastore, deleteDocumentFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, createDocumentInCartDatastore, deleteDocumentFromCartDatastore, getCartDocumentByIdFromReceiptsDatastore, + createDocumentInReceiptErrorDatastore, deleteDocumentFromReceiptErrorDatastore, getDocumentFromReceiptsErrorDatastoreByBizEventId, deleteMultipleDocumentsFromReceiptsDatastoreByEventId, deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, + createDocumentInReceiptsDatastore, getDocumentFromReceiptsDatastoreByEventId } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index a244255e..a1c395e7 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -87,7 +87,7 @@ public HttpResponseMessage run ( logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); try { - Receipt receipt = BizEventToReceiptUtils.getEvent(eventId, context, this.bizEventToReceiptService, + Receipt receipt = BizEventToReceiptUtils.retrieveBizAndSendReceipt(eventId, context, this.bizEventToReceiptService, this.bizEventCosmosClient, this.receiptCosmosService, null, logger); documentdb.setValue(receipt); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 6fae1bcb..8a281566 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -27,7 +27,6 @@ import java.math.RoundingMode; import java.text.NumberFormat; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,7 +45,7 @@ public class BizEventToReceiptUtils { private BizEventToReceiptUtils() { } - public static Receipt getEvent( + public static Receipt retrieveBizAndSendReceipt( String eventId, ExecutionContext context, BizEventToReceiptService bizEventToReceiptService, @@ -408,7 +407,7 @@ public static MassiveRecoverResult massiveRecoverByStatus( for (FeedResponse page : feedResponseIterator) { for (Receipt receipt : page.getResults()) { try { - Receipt restored = getEvent(receipt.getEventId(), context, bizEventToReceiptService, + Receipt restored = retrieveBizAndSendReceipt(receipt.getEventId(), context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, receipt, logger); if (isReceiptStatusValid(restored)) { receiptList.add(restored); From 8120ac0cea6da037c8f632ecddb0f0bdd6518e36 Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 5 Jan 2026 13:38:41 +0100 Subject: [PATCH 18/38] clean codes --- .../helpdesk/http/ReceiptToReviewedTest.java | 27 ----- .../http/RecoverFailedReceiptTest.java | 105 ------------------ .../RecoverFailedReceiptScheduledTest.java | 2 - 3 files changed, 134 deletions(-) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index 77afc007..a52f3637 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -5,7 +5,6 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; @@ -68,32 +67,6 @@ void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFound assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); } -// @Test -// void requestWithValidCartSaveReceiptErrorInReviewed() throws ReceiptNotFoundException, CartNotFoundException { -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(request).createResponseBuilder(any(HttpStatus.class)); -// -// ReceiptError receiptError = ReceiptError.builder() -// .bizEventId(BIZ_EVENT_ID) -// .status(ReceiptErrorStatusType.TO_REVIEW) -// .build(); -// when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); -// -// function = spy(new ReceiptToReviewed(receiptCosmosService)); -// -// // test execution -// AtomicReference responseMessage = new AtomicReference<>(); -// assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); -// assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); -// -// verify(documentdb).setValue(receiptErrorCaptor.capture()); -// ReceiptError captured = receiptErrorCaptor.getValue(); -// assertEquals(BIZ_EVENT_ID, captured.getBizEventId()); -// assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); -// } - @Test void requestWithValidBizEventIdButReceiptNotFound() throws ReceiptNotFoundException { doAnswer((Answer) invocation -> { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 08bc4be2..fbd92716 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -123,48 +123,6 @@ void requestOnValidBizEventShouldNotCreateRequest() { assertNotNull(response.getBody()); } -// @Test -// @SneakyThrows -// void requestOnValidCartShouldCreateRequest() { -// when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) -// .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); -// -// Response queueResponse = mock(Response.class); -// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); -// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); -// -// when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); -// -// FeedResponse feedResponseMock = mock(FeedResponse.class); -// List receiptList = Collections.singletonList(generateValidBizEvent("1")); -// when(feedResponseMock.getResults()).thenReturn(receiptList); -// doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) -// .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); -// -// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); -// -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); -// -// // test execution -// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); -// -// // test assertion -// assertNotNull(response); -// assertEquals(HttpStatus.OK, response.getStatus()); -// assertNotNull(response.getBody()); -// -// verify(documentdb).setValue(receiptCaptor.capture()); -// Receipt captured = receiptCaptor.getValue(); -// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); -// assertEquals(EVENT_ID, captured.getEventId()); -// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); -// assertNotNull(captured.getEventData().getCart()); -// assertEquals(1, captured.getEventData().getCart().size()); -// } - @Test void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) @@ -197,45 +155,6 @@ void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoun assertEquals(1, captured.getEventData().getCart().size()); } -// @Test -// void requestOnValidCartAndFailedReceiptShouldResend() throws ReceiptNotFoundException { -// when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); -// -// Response queueResponse = mock(Response.class); -// when(queueResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); -// when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); -// -// FeedResponse feedResponseMock = mock(FeedResponse.class); -// List receiptList = Collections.singletonList(generateValidBizEvent("1")); -// when(feedResponseMock.getResults()).thenReturn(receiptList); -// doReturn(Collections.singletonList(feedResponseMock)).when(bizEventCosmosClientMock) -// .getAllBizEventDocument(Mockito.eq("a valid id"), any(), any()); -// -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); -// -// when(requestMock.getQueryParameters()).thenReturn(Collections.singletonMap("isCart","true")); -// -// // test execution -// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); -// -// // test assertion -// assertNotNull(response); -// assertEquals(HttpStatus.OK, response.getStatus()); -// assertNotNull(response.getBody()); -// -// verify(documentdb).setValue(receiptCaptor.capture()); -// Receipt captured = receiptCaptor.getValue(); -// assertEquals(ReceiptStatusType.INSERTED, captured.getStatus()); -// assertEquals(EVENT_ID, captured.getEventId()); -// assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); -// assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); -// assertNotNull(captured.getEventData().getCart()); -// assertEquals(1, captured.getEventData().getCart().size()); -// } - @Test @SneakyThrows void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken() { @@ -404,30 +323,6 @@ void runDiscardedEventWithInvalidTotalNotice() throws BizEventNotFoundException verifyNoInteractions(queueClientMock); } -// @Test -// void runDiscardedWithInvalidCartAmounts() throws BizEventNotFoundException { -// BizEvent bizEvent = generateValidBizEvent(null); -// bizEvent.getTransactionDetails().getTransaction().setAmount(10); -// when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) -// .thenReturn(bizEvent); -// -// doAnswer((Answer) invocation -> { -// HttpStatus status = (HttpStatus) invocation.getArguments()[0]; -// return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); -// }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); -// -// HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); -// -// // test assertion -// assertNotNull(response); -// assertEquals(HttpStatus.OK, response.getStatus()); -// assertNotNull(response.getBody()); -// -// verifyNoInteractions(receiptCosmosServiceMock); -// verifyNoInteractions(queueClientMock); -// -// } - @Test @SneakyThrows void errorTokenizingFiscalCodes() { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java index 55dd12c9..c34aae18 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -13,7 +13,6 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import org.junit.jupiter.api.AfterEach; @@ -29,7 +28,6 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; From 1561d37375534476b94b2c666f022159910bfd4e Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 5 Jan 2026 13:49:42 +0100 Subject: [PATCH 19/38] clean codes --- .../helpdesk/http/ReceiptToReviewedTest.java | 10 ----- .../http/RecoverFailedReceiptTest.java | 25 ------------ .../http/RecoverNotNotifiedReceiptTest.java | 38 ------------------- ...ecoverNotNotifiedReceiptScheduledTest.java | 2 +- 4 files changed, 1 insertion(+), 74 deletions(-) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index a52f3637..dd14d744 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -124,14 +124,4 @@ void requestWithoutEventIdReturnsBadRequest() { verifyNoInteractions(documentdb); } - - private CartForReceipt generateCart() { - CartForReceipt cart = new CartForReceipt(); - cart.setId("1"); - cart.setStatus(CartStatusType.FAILED); - cart.setTotalNotice(1); - cart.setCartPaymentId(new HashSet<>(new ArrayList<>( - List.of(new String[]{"valid_biz_event_id"})))); - return cart; - } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index fbd92716..6ea049dd 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -417,31 +417,6 @@ private BizEvent generateValidBizEvent(String totalNotice){ return item; } - private BizEvent generateValidBizEventWithTDetails(String totalNotice) { - BizEvent item = new BizEvent(); - - Debtor debtor = new Debtor(); - debtor.setEntityUniqueIdentifierValue(DEBTOR_FISCAL_CODE); - - TransactionDetails transactionDetails = new TransactionDetails(); - transactionDetails.setInfo(InfoTransaction.builder().clientId("IO").build()); - Transaction transaction = new Transaction(); - transaction.setCreationDate(String.valueOf(LocalDateTime.now())); - transactionDetails.setTransaction(transaction); - transactionDetails.setUser(User.builder().fiscalCode(PAYER_FISCAL_CODE).build()); - - PaymentInfo paymentInfo = new PaymentInfo(); - paymentInfo.setTotalNotice(totalNotice); - - item.setEventStatus(BizEventStatusType.DONE); - item.setId(EVENT_ID); - item.setDebtor(debtor); - item.setTransactionDetails(transactionDetails); - item.setPaymentInfo(paymentInfo); - - return item; - } - private Receipt createFailedReceipt() { Receipt receipt = new Receipt(); diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index 90792df9..aef08629 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -90,35 +90,6 @@ void recoverNotNotifiedReceiptSuccess() throws ReceiptNotFoundException { assertNull(captured.getReasonErrPayer()); } - @Test - void recoverNotNotifiedCartReceiptSuccess() throws ReceiptNotFoundException { - Receipt receipt = buildReceipt(); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentReceipts).setValue(receiptCaptor.capture()); - - assertEquals(1, receiptCaptor.getValue().size()); - Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(0, captured.getNotificationNumRetry()); - assertNull(captured.getReasonErr()); - assertNull(captured.getReasonErrPayer()); - } - @Test void recoverReceiptFailForMissingEventId() { doAnswer((Answer) invocation -> { @@ -236,13 +207,4 @@ private Receipt buildReceipt() { .build(); } - private CartForReceipt generateCart() { - CartForReceipt cart = new CartForReceipt(); - cart.setId("1"); - cart.setStatus(CartStatusType.FAILED); - cart.setTotalNotice(1); - cart.setCartPaymentId(new HashSet<>(new ArrayList<>( - List.of(new String[]{"eventId"})))); - return cart; - } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java index 9cdc6260..956aea3e 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java @@ -58,7 +58,7 @@ void releaseMocks() throws Exception { } @Test - public void scheduledTriggerShouldReturnAllValidReceiptsProcessed() { + void scheduledTriggerShouldReturnAllValidReceiptsProcessed() { FeedResponse feedResponseMock = mock(FeedResponse.class); List receiptList = getReceiptList(ReceiptStatusType.IO_ERROR_TO_NOTIFY); when(feedResponseMock.getResults()).thenReturn(receiptList); From c164bfac2376eea95c59738538ea75b2d6c159fa Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 5 Jan 2026 13:56:00 +0100 Subject: [PATCH 20/38] clean codes --- .../pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java | 5 ----- .../helpdesk/http/RecoverNotNotifiedReceiptTest.java | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index dd14d744..7a5d2e56 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -1,8 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; @@ -16,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index aef08629..db7bc0bd 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -1,8 +1,6 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; @@ -18,8 +16,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Optional; From 7c31a534ca6a68ae525825da1d1b7c6146a51560 Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 12 Jan 2026 09:35:44 +0100 Subject: [PATCH 21/38] improvements --- helm/values-dev.yaml | 1 + helm/values-prod.yaml | 1 + helm/values-uat.yaml | 1 + .../client/CartReceiptsCosmosClient.java | 11 + .../impl/CartReceiptsCosmosClientImpl.java | 73 ++- .../entity/receipt/CartReceiptError.java | 18 + .../helpdesk/http/CartReceiptToReviewed.java | 112 +++++ .../http/CartRecoverFailedReceipt.java | 193 ++++++++ .../service/CartReceiptCosmosService.java | 21 + .../impl/CartReceiptCosmosServiceImpl.java | 42 ++ .../TransactionEventToCartReceiptUtils.java | 435 ++++++++++++++++++ 11 files changed, 891 insertions(+), 17 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/CartReceiptError.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index b6c6db1d..ddd8102f 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -97,6 +97,7 @@ microservice-chart: COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + CART_RECEIPTS_MESSAGE_ERRORS_CONTAINER_NAME: "cart-receipts-message-errors" COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 71c2c1ab..82141c3e 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -97,6 +97,7 @@ microservice-chart: COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + CART_RECEIPTS_MESSAGE_ERRORS_CONTAINER_NAME: "cart-receipts-message-errors" COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index b1d810a3..3aabc044 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -97,6 +97,7 @@ microservice-chart: COSMOS_BIZ_EVENT_DB_NAME: "db" COSMOS_RECEIPT_CONTAINER_NAME: "receipts" CART_FOR_RECEIPT_CONTAINER_NAME: "cart-for-receipts" + CART_RECEIPTS_MESSAGE_ERRORS_CONTAINER_NAME: "cart-receipts-message-errors" COSMOS_RECEIPT_ERROR_CONTAINER_NAME: "receipts-message-errors" COSMOS_RECEIPT_MESSAGE_CONTAINER_NAME: "receipts-io-messages-evt" COSMOS_RECEIPT_CART_CONTAINER_NAME: "cart-for-receipts" diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java index a9ef68e6..b083ab36 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java @@ -2,6 +2,8 @@ import com.azure.cosmos.models.CosmosItemResponse; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.CartConcurrentUpdateException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; @@ -32,4 +34,13 @@ public interface CartReceiptsCosmosClient { * @return the updated cart-for-receipts document */ CosmosItemResponse updateCart(CartForReceipt receipt) throws CartConcurrentUpdateException; + + /** + * Retrieve cartReceiptError document from CosmosDB database + * + * @param cartId id of the collection + * @return CartReceiptError found + * @throws CartNotFoundException If the document isn't found + */ + CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java index 6301e4be..a66d4e77 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java @@ -8,14 +8,21 @@ import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.exception.CartConcurrentUpdateException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import java.util.Optional; + public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { private static CartReceiptsCosmosClientImpl instance; private final String databaseId = System.getenv("COSMOS_RECEIPT_DB_NAME"); private final String cartForReceiptContainerName = System.getenv("CART_FOR_RECEIPT_CONTAINER_NAME"); + private final String cartReceiptsMessageErrorsContainerName = System.getenv("CART_RECEIPTS_MESSAGE_ERRORS_CONTAINER_NAME"); + + private static final String DOCUMENT_NOT_FOUND_ERR_MSG = "Document not found in the defined container"; private final CosmosClient cosmosClient; @@ -45,27 +52,33 @@ public static CartReceiptsCosmosClientImpl getInstance() { /** * {@inheritDoc} */ +// @Override +// public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { +// CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); +// +// CosmosContainer cosmosContainer = cosmosDatabase.getContainer(cartForReceiptContainerName); +// +// //Build query +// String query = "SELECT * FROM c WHERE c.eventId = '%s'".formatted(eventId); +// +// //Query the container +// CosmosPagedIterable queryResponse = cosmosContainer +// .queryItems(query, new CosmosQueryRequestOptions(), CartForReceipt.class); +// +// if (queryResponse.iterator().hasNext()) { +// return queryResponse.iterator().next(); +// } else { +// throw new CartNotFoundException("Document not found in the defined container"); +// } +// } + @Override public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(cartForReceiptContainerName); - - //Build query - String query = "SELECT * FROM c WHERE c.eventId = '%s'".formatted(eventId); - - //Query the container - CosmosPagedIterable queryResponse = cosmosContainer - .queryItems(query, new CosmosQueryRequestOptions(), CartForReceipt.class); - - if (queryResponse.iterator().hasNext()) { - return queryResponse.iterator().next(); - } else { - throw new CartNotFoundException("Document not found in the defined container"); - } - + return getDocumentByFilter(cartForReceiptContainerName, "eventId", eventId, CartForReceipt.class) + .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } + /** * {@inheritDoc} */ @@ -90,4 +103,30 @@ public CosmosItemResponse updateCart(CartForReceipt receipt) thr } } + /** + * {@inheritDoc} + */ + @Override + public CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException { + return getDocumentByFilter(cartReceiptsMessageErrorsContainerName, "id", cartId, CartReceiptError.class) + .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); + } + + + /** + * PRIVATE METHODS + */ + + private Optional getDocumentByFilter(String containerId, String propertyName, String propertyValue, Class classType) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); + + String query = String.format("SELECT * FROM c WHERE c.%s = '%s'", propertyName, propertyValue); + + // use stream() to convert iterable and find first element + return cosmosContainer + .queryItems(query, new CosmosQueryRequestOptions(), classType) + .stream() + .findFirst(); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/CartReceiptError.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/CartReceiptError.java new file mode 100644 index 00000000..7b0f4f8e --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/CartReceiptError.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.receipt.pdf.datastore.entity.receipt; + +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CartReceiptError { + + private String id; + private String messagePayload; + private String messageError; + private ReceiptErrorStatusType status; + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java new file mode 100644 index 00000000..4e90c0bf --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java @@ -0,0 +1,112 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.*; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * Azure Functions with Azure Http trigger. + */ +public class CartReceiptToReviewed { + private final Logger logger = LoggerFactory.getLogger(CartReceiptToReviewed.class); + + private final CartReceiptCosmosService cartReceiptCosmosService; + + public CartReceiptToReviewed() { + this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); + } + + CartReceiptToReviewed( + CartReceiptCosmosService cartReceiptCosmosService + ) { + this.cartReceiptCosmosService = cartReceiptCosmosService; + } + + /** + * This function will be invoked when an Http Trigger occurs + * + * TODO + * collection cart-receipt-message-errors + * per il carrello arriva l'id del carrello (id collection) + * logica analoga a quanto presente in ReceiptToReviewed + * + * + * @return response with HttpStatus.OK + */ + @FunctionName("CartReceiptToReviewed") + public HttpResponseMessage run( + @HttpTrigger(name = "CartReceiptToReviewedFunction", + methods = {HttpMethod.POST}, + route = "cart-receipts-error/{cart-id}/reviewed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("cart-id") String cartId, + @CosmosDBOutput( + name = "CartReceiptErrorDatastore", + databaseName = "db", + containerName = "cart-receipts-message-errors", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding documentdb, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + if (cartId == null || cartId.isBlank()) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail("Please pass a valid cart id") + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + String responseMsg; + CartReceiptError receiptError; + + try { + receiptError = cartReceiptCosmosService.getCartReceiptError(cartId); + } catch (NoSuchElementException | CartNotFoundException e) { + responseMsg = String.format("No cartReceiptError has been found with cartId %s", cartId); + logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); + return request + .createResponseBuilder(HttpStatus.NOT_FOUND) + .body(ProblemJson.builder() + .title(HttpStatus.NOT_FOUND.name()) + .detail(responseMsg) + .status(HttpStatus.NOT_FOUND.value()) + .build()) + .build(); + } + + if (!receiptError.getStatus().equals(ReceiptErrorStatusType.TO_REVIEW)) { + responseMsg = String.format("Found cartReceiptError with invalid status %s for cartId %s", receiptError.getStatus(), cartId); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(responseMsg) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } + receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); + + documentdb.setValue(receiptError); + + responseMsg = String.format("CartReceiptError with cartId %s updated to status %s with success", receiptError.getId(), ReceiptErrorStatusType.REVIEWED); + return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java new file mode 100644 index 00000000..f100e6db --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java @@ -0,0 +1,193 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.annotation.*; +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; +import it.gov.pagopa.receipt.pdf.datastore.utils.TransactionEventToCartReceiptUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * Azure Functions with Azure Http trigger. + */ +public class CartRecoverFailedReceipt { + + private final Logger logger = LoggerFactory.getLogger(CartRecoverFailedReceipt.class); + + private final BizEventToReceiptService bizEventToReceiptService; + private final CartReceiptCosmosService cartReceiptCosmosService; + + public CartRecoverFailedReceipt(){ + this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + } + + CartRecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, + CartReceiptCosmosService cartReceiptCosmosService){ + this.bizEventToReceiptService = bizEventToReceiptService; + this.cartReceiptCosmosService = cartReceiptCosmosService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + * The function is responsible for retrieving receipts that are in a FAILED, INSERTED and NOT_QUEUE_SENT state. + * TODO collection cart-for-receipts + * TODO https://github.com/pagopa/pagopa-receipt-pdf-generator/pull/170/changes#diff-5809f1fa1db9b483c00c90153ca5dc50b57437d89bb6f96b50f9447430ecd440 + * TODO vedere impl https://github.com/pagopa/pagopa-receipt-pdf-datastore/blob/b0403d8b614a97e02e2af713737b1054fa693a19/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java#L143 + * For a cart receipt, the function should: + * - try to retrieve the biz event -> if it doesn't find it, error + * - check that it's a valid biz: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error + * - check that it's not a cart biz: BizEventToReceiptUtils.getTotalNotice() == 1 -> if cart, error + * - check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error + * - recreate the receipt from the biz: BizEventToReceiptUtils.createReceipt() + * - if everything is OK, it updates the receipt on the cosmos. BizEventToReceiptService.handleSaveReceipt() + * - if everything is OK, send it to the queue BizEventToReceiptService.handleSendMessageToQueue() + *

+ * It recovers the receipt with the specified biz event id that has the following status: + * - ({@link ReceiptStatusType#INSERTED}) + * - ({@link ReceiptStatusType#FAILED}) + * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

+ * It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("CartRecoverFailedReceipt") + public HttpResponseMessage run ( + @HttpTrigger(name = "CartRecoverFailedReceiptTrigger", + methods = {HttpMethod.POST}, + route = "cart-receipts/{cart-id}/recover-failed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("cart-id") String cartId, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding documentdb, + final ExecutionContext context) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + // TODO + // a partire dal cartId recuperare i biz-event + // validazione dei biz-event + // controllare che il numero di biz-event con il total notice presente nei biz + // se i biz sono validi procedere con la ricreazione della cart e l'invio in coda + // vedere regenerate sul recupero dell'id e sovrascrittura + + try { + // retrieve biz-event with the specified cartId + List bizEvents = bizEventToReceiptService.getCartBizEventsById(cartId); + for(BizEvent bizEvent : bizEvents) { + // biz-event validation + if (BizEventToReceiptUtils.isBizEventInvalid(bizEvent, context, logger)) { + String errMsg = String.format("Biz-event with id %s is invalid", bizEvent.getId()); + throw new BizEventBadRequestException(errMsg); + } + + // total notice check + Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, context, logger); + if (totalNotice != bizEvents.size()) { + String errMsg = String.format("Failed to regenerate cart, the expected total notice %s does not match the number of biz events %s", + totalNotice, bizEvents.size()); + logger.error(errMsg); + return request + .createResponseBuilder(HttpStatus.UNPROCESSABLE_ENTITY) + .body(ProblemJson.builder() + .title(HttpStatus.UNPROCESSABLE_ENTITY.name()) + .detail(errMsg) + .status(HttpStatus.UNPROCESSABLE_ENTITY.value()) + .build()) + .build(); + } + } + + // all ok, proceed with the cart recovery + return null; + +// CartForReceipt receipt = TransactionEventToCartReceiptUtils.retrieveCartAndSendReceipt( +// cartId, context, this.cartReceiptCosmosService, +// this.bizEventCosmosClient, null, logger); +// +// documentdb.setValue(receipt); +// if (BizEventToReceiptUtils.isReceiptStatusValid(receipt)) { +// String responseMsg = String.format("Receipt with cartId %s recovered", cartId); +// return request.createResponseBuilder(HttpStatus.OK) +// .body(responseMsg) +// .build(); +// } else { +// return request +// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ProblemJson.builder() +// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) +// .detail(receipt.getReasonErr().getMessage()) +// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) +// .build()) +// .build(); +// } + } catch (BizEventBadRequestException ex) { + String msg = String.format("Problem with the biz-events related to cartId %s", cartId); + logger.error(msg, ex); + return request + .createResponseBuilder(ex.getHttpStatus()) + .body(ProblemJson.builder() + .title(ex.getHttpStatus().name()) + .detail(msg) + .status(ex.getHttpStatus().value()) + .build()) + .build(); + + } catch (BizEventNotFoundException | BizEventBadRequestException exception) { + String msg = String.format("Unable to retrieve the biz-event with id %s", cartId); + logger.error(msg, exception); + return request + .createResponseBuilder(exception.getHttpStatus()) + .body(ProblemJson.builder() + .title(exception.getHttpStatus().name()) + .detail(msg) + .status(exception.getHttpStatus().value()) + .build()) + .build(); + } catch (PDVTokenizerException | JsonProcessingException e) { + logger.error(e.getMessage(), e); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .detail(e.getMessage()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .build()) + .build(); + } catch (ReceiptNotFoundException e) { + logger.error(e.getMessage(), e); + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail(e.getMessage()) + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java new file mode 100644 index 00000000..5d6a6573 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.receipt.pdf.datastore.service; + +import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; + +/** + * Service that handle the input and output for the {@link ReceiptCosmosClient} + */ +public interface CartReceiptCosmosService { + + /** + * Retrieve the cart receipt error with the provided cart-id + * + * @param cartId the cart id + * @return the receipt error + * @throws CartNotFoundException if the receipt was not found or the retrieved receipt is null + */ + CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException; + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java new file mode 100644 index 00000000..89012320 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java @@ -0,0 +1,42 @@ +package it.gov.pagopa.receipt.pdf.datastore.service.impl; + +import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartReceiptsCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; + + +public class CartReceiptCosmosServiceImpl implements CartReceiptCosmosService { + + private final CartReceiptsCosmosClient cartReceiptsCosmosClient; + + public CartReceiptCosmosServiceImpl() { + this.cartReceiptsCosmosClient = CartReceiptsCosmosClientImpl.getInstance(); + } + + CartReceiptCosmosServiceImpl(CartReceiptsCosmosClient cartReceiptsCosmosClient) { + this.cartReceiptsCosmosClient = cartReceiptsCosmosClient; + } + + + /** + * {@inheritDoc} + */ + @Override + public CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException { + CartReceiptError receipt; + try { + receipt = this.cartReceiptsCosmosClient.getCartReceiptError(cartId); + } catch (CartNotFoundException e) { + String errorMsg = String.format("Cart receipt error not found with the cart-id %s", cartId); + throw new CartNotFoundException(errorMsg, e); + } + + if (receipt == null) { + String errorMsg = String.format("Receipt error retrieved with the cart-id %s is null", cartId); + throw new CartNotFoundException(errorMsg); + } + return receipt; + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java new file mode 100644 index 00000000..3c1d70b9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java @@ -0,0 +1,435 @@ +//package it.gov.pagopa.receipt.pdf.datastore.utils; +// +//import com.azure.cosmos.models.FeedResponse; +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.microsoft.azure.functions.ExecutionContext; +//import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +//import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; +//import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +//import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +//import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +//import it.gov.pagopa.receipt.pdf.datastore.entity.event.Transfer; +//import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; +//import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.UserType; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +//import it.gov.pagopa.receipt.pdf.datastore.exception.*; +//import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +//import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +//import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +//import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +//import org.slf4j.Logger; +// +//import javax.validation.constraints.NotNull; +//import java.math.BigDecimal; +//import java.math.RoundingMode; +//import java.text.NumberFormat; +//import java.util.*; +//import java.util.regex.Matcher; +//import java.util.regex.Pattern; +// +//public class TransactionEventToCartReceiptUtils { +// +// private static final String REMITTANCE_INFORMATION_REGEX = "/TXT/(.*)"; +// private static final Boolean ECOMMERCE_FILTER_ENABLED = Boolean.parseBoolean(System.getenv().getOrDefault( +// "ECOMMERCE_FILTER_ENABLED", "true")); +// private static final List AUTHENTICATED_CHANNELS = Arrays.asList(System.getenv().getOrDefault( +// "AUTHENTICATED_CHANNELS", "IO,CHECKOUT,WISP,CHECKOUT_CART").split(",")); +// private static final List UNWANTED_REMITTANCE_INFO = Arrays.asList(System.getenv().getOrDefault( +// "UNWANTED_REMITTANCE_INFO", "pagamento multibeneficiario,pagamento bpay").split(",")); +// private static final String ECOMMERCE = "CHECKOUT"; +// +// +// private TransactionEventToCartReceiptUtils() { +// } +// +// public static CartForReceipt retrieveCartAndSendReceipt( +// String eventId, +// ExecutionContext context, +// @NotNull CartReceiptsCosmosClient cartReceiptCosmosClient, +// Receipt receipt, +// Logger logger +// ) throws BizEventNotFoundException, BizEventBadRequestException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException, CartNotFoundException { +// +// CartForReceipt cartItem = cartReceiptCosmosClient.getCartItem(eventId); +// +// // check total notice +// // Checks if the instance of Biz Event is in status DONE and contains all the required information to process in the receipt generation +// +// +// if (isBizEventInvalid(cartItem, context, logger)) { +// throw new BizEventBadRequestException("BizEvent not valid"); +// } +// +// if (!hasValidTotalNotice(cartItem, context, logger)) { +// throw new BizEventBadRequestException("BizEvent has not a valid total notice"); +// } +// +// if (receipt == null) { +// receipt = receiptCosmosService.getReceipt(eventId); +// } +// +// // check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error +// if (receipt != null && ( +// receipt.getStatus().equals(ReceiptStatusType.FAILED) || +// receipt.getStatus().equals(ReceiptStatusType.INSERTED) || +// receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) +// )) { +// // recreate the receipt from the biz +// receipt = createReceipt(cartItem, bizEventToReceiptService, logger); +// if (isReceiptStatusValid(receipt)) { +// bizEventToReceiptService.handleSaveReceipt(receipt); +// +// if (isReceiptStatusValid(receipt)) { +// bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(cartItem), receipt); +// +// return receipt; +// } +// } +// } +// return receipt; +// } +// +// /** +// * Creates a new instance of Receipt, using the tokenizer service to mask the PII, based on +// * the provided BizEvent +// * +// * @param bizEvent instance of BizEvent +// * @param service implementation of the BizEventToReceipt service to use +// * @return generated instance of Receipt +// */ +// public static Receipt createReceipt(BizEvent bizEvent, BizEventToReceiptService service, Logger logger) { +// Receipt receipt = new Receipt(); +// +// // Insert biz-event data into receipt +// receipt.setId(bizEvent.getId() + UUID.randomUUID()); +// receipt.setEventId(bizEvent.getId()); +// +// EventData eventData = new EventData(); +// +// try { +// service.tokenizeFiscalCodes(bizEvent, receipt, eventData); +// } catch (Exception e) { +// logger.error("Error tokenizing receipt with bizEventId {}", bizEvent.getId(), e); +// receipt.setStatus(ReceiptStatusType.FAILED); +// return receipt; +// } +// +// eventData.setTransactionCreationDate( +// service.getTransactionCreationDate(bizEvent)); +// BigDecimal amount = getAmount(bizEvent); +// eventData.setAmount(!amount.equals(BigDecimal.ZERO) ? formatAmount(amount.toString()) : null); +// +// CartItem item = new CartItem(); +// item.setPayeeName(bizEvent.getCreditor() != null ? bizEvent.getCreditor().getCompanyName() : null); +// item.setSubject(getItemSubject(bizEvent)); +// List cartItems = Collections.singletonList(item); +// eventData.setCart(cartItems); +// +// receipt.setEventData(eventData); +// receipt.setStatus(ReceiptStatusType.INSERTED); +// return receipt; +// } +// +// /** +// * Checks if the instance of Biz Event is in status DONE and contains all the required information to process +// * in the receipt generation +// * +// * @param bizEvent BizEvent to validate +// * @param context Function context +// * @param logger Function logger +// * @return boolean to determine if the proposed event is invalid +// */ +// public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext context, Logger logger) { +// +// if (bizEvent == null) { +// logger.debug("[{}] event is null", context.getFunctionName()); +// return true; +// } +// +// if (!BizEventStatusType.DONE.equals(bizEvent.getEventStatus())) { +// logger.debug("[{}] event with id {} discarded because in status {}", +// context.getFunctionName(), bizEvent.getId(), bizEvent.getEventStatus()); +// return true; +// } +// +// if (!hasValidFiscalCode(bizEvent)) { +// logger.debug("[{}] event with id {} discarded because debtor's and payer's identifiers are missing or not valid", +// context.getFunctionName(), bizEvent.getId()); +// return true; +// } +// +// if (Boolean.TRUE.equals(ECOMMERCE_FILTER_ENABLED) +// && bizEvent.getTransactionDetails() != null +// && bizEvent.getTransactionDetails().getInfo() != null +// && ECOMMERCE.equals(bizEvent.getTransactionDetails().getInfo().getClientId()) +// ) { +// logger.debug("[{}] event with id {} discarded because from e-commerce {}", +// context.getFunctionName(), bizEvent.getId(), bizEvent.getTransactionDetails().getInfo().getClientId()); +// return true; +// } +// +// if (!isCartMod1(bizEvent)) { +// logger.debug("[{}] event with id {} contain either an invalid amount value," + +// " or it is a legacy cart element", +// context.getFunctionName(), bizEvent.getId()); +// return true; +// } +// +// return false; +// } +// +// private static boolean hasValidTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { +// if (bizEvent.getPaymentInfo() != null) { +// String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); +// +// if (totalNotice != null) { +// int intTotalNotice; +// +// try { +// intTotalNotice = Integer.parseInt(totalNotice); +// +// } catch (NumberFormatException e) { +// logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", +// context.getFunctionName(), bizEvent.getId(), +// totalNotice, +// e); +// return false; +// } +// +// if (intTotalNotice > 1) { +// logger.error("[{}] event with id {} discarded because is part of a payment cart ({} total notice)", +// context.getFunctionName(), bizEvent.getId(), +// intTotalNotice); +// return false; +// } +// } +// } +// return true; +// } +// +// private static boolean hasValidFiscalCode(BizEvent bizEvent) { +// boolean isValidDebtor = false; +// boolean isValidPayer = false; +// +// if (bizEvent.getDebtor() != null && isValidFiscalCode(bizEvent.getDebtor().getEntityUniqueIdentifierValue())) { +// isValidDebtor = true; +// } +// if (isValidChannelOrigin(bizEvent)) { +// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getUser() != null && isValidFiscalCode(bizEvent.getTransactionDetails().getUser().getFiscalCode())) { +// isValidPayer = true; +// } +// if (bizEvent.getPayer() != null && isValidFiscalCode(bizEvent.getPayer().getEntityUniqueIdentifierValue())) { +// isValidPayer = true; +// } +// } +// return isValidDebtor || isValidPayer; +// } +// +// public static Integer getTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { +// if (bizEvent.getPaymentInfo() != null) { +// String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); +// +// if (totalNotice != null) { +// int intTotalNotice; +// +// try { +// intTotalNotice = Integer.parseInt(totalNotice); +// } catch (NumberFormatException e) { +// logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", +// context.getFunctionName(), bizEvent.getId(), +// totalNotice, +// e); +// throw e; +// } +// return intTotalNotice; +// } +// } +// return 1; +// } +// +// /** +// * Retrieve RemittanceInformation from BizEvent +// * +// * @param bizEvent BizEvent from which retrieve the data +// * @return the remittance information +// */ +// public static String getItemSubject(BizEvent bizEvent) { +// if ( +// bizEvent.getPaymentInfo() != null && +// bizEvent.getPaymentInfo().getRemittanceInformation() != null && +// !UNWANTED_REMITTANCE_INFO.contains(bizEvent.getPaymentInfo().getRemittanceInformation().toLowerCase()) +// ) { +// return bizEvent.getPaymentInfo().getRemittanceInformation(); +// } +// List transferList = bizEvent.getTransferList(); +// if (transferList != null && !transferList.isEmpty()) { +// double amount = 0; +// String remittanceInformation = null; +// for (Transfer transfer : transferList) { +// double transferAmount; +// try { +// transferAmount = Double.parseDouble(transfer.getAmount()); +// } catch (Exception ignored) { +// continue; +// } +// if (amount < transferAmount) { +// amount = transferAmount; +// remittanceInformation = transfer.getRemittanceInformation(); +// } +// } +// return formatRemittanceInformation(remittanceInformation); +// } +// return null; +// } +// +// public static BigDecimal getAmount(BizEvent bizEvent) { +// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getTransaction() != null) { +// return formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getGrandTotal()); +// } +// if (bizEvent.getPaymentInfo() != null && bizEvent.getPaymentInfo().getAmount() != null) { +// return new BigDecimal(bizEvent.getPaymentInfo().getAmount()); +// } +// return BigDecimal.ZERO; +// } +// +// public static BigDecimal getCartAmount(BizEvent bizEvent) { +// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getTransaction() != null) { +// return formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getGrandTotal()); +// } +// return BigDecimal.ZERO; +// } +// +// private static BigDecimal formatEuroCentAmount(long grandTotal) { +// BigDecimal amount = new BigDecimal(grandTotal); +// BigDecimal divider = new BigDecimal(100); +// return amount.divide(divider, 2, RoundingMode.UNNECESSARY); +// } +// +// public static String formatAmount(String value) { +// BigDecimal valueToFormat = new BigDecimal(value); +// NumberFormat numberFormat = NumberFormat.getInstance(Locale.ITALY); +// numberFormat.setMaximumFractionDigits(2); +// numberFormat.setMinimumFractionDigits(2); +// return numberFormat.format(valueToFormat); +// } +// +// private static String formatRemittanceInformation(String remittanceInformation) { +// if (remittanceInformation != null) { +// Pattern pattern = Pattern.compile(REMITTANCE_INFORMATION_REGEX); +// Matcher matcher = pattern.matcher(remittanceInformation); +// if (matcher.find()) { +// return matcher.group(1); +// } +// } +// return remittanceInformation; +// } +// +// public static boolean isReceiptStatusValid(Receipt receipt) { +// return receipt.getStatus() != ReceiptStatusType.FAILED && receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT; +// } +// +// public static boolean isCartStatusValid(CartForReceipt cartForReceipt) { +// return cartForReceipt.getStatus() != CartStatusType.FAILED && cartForReceipt.getStatus() != CartStatusType.NOT_QUEUE_SENT; +// } +// +// public static boolean isValidFiscalCode(String fiscalCode) { +// if (fiscalCode != null && !fiscalCode.isEmpty()) { +// Pattern patternCF = Pattern.compile("^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"); +// Pattern patternPIVA = Pattern.compile("/^[0-9]{11}$/"); +// +// return patternCF.matcher(fiscalCode).find() || patternPIVA.matcher(fiscalCode).find(); +// } +// +// return false; +// } +// +// /** +// * Method to check if the content comes from a legacy cart model (see https://pagopa.atlassian.net/browse/VAS-1167) +// * +// * @param bizEvent bizEvent to validate +// * @return flag to determine if it is a manageable cart, or otherwise, will return false if +// * it is considered a legacy cart content (not having a totalNotice field and having amount values != 0) +// */ +// public static boolean isCartMod1(BizEvent bizEvent) { +// if (bizEvent.getPaymentInfo() != null && bizEvent.getPaymentInfo().getTotalNotice() == null) { +// return bizEvent.getTransactionDetails() != null && +// new BigDecimal(bizEvent.getPaymentInfo().getAmount()).subtract( +// formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getAmount())) +// .floatValue() == 0; +// } +// return true; +// } +// +// public static boolean isValidChannelOrigin(BizEvent bizEvent) { +// if (bizEvent.getTransactionDetails() == null) { +// return false; +// } +// +// var transactionDetails = bizEvent.getTransactionDetails(); +// var transaction = transactionDetails.getTransaction(); +// var info = transactionDetails.getInfo(); +// var user = transactionDetails.getUser(); +// +// String origin = (transaction != null) ? transaction.getOrigin() : null; +// String clientId = (info != null) ? info.getClientId() : null; +// UserType userType = (user != null) ? user.getType() : null; +// +// boolean originMatches = origin != null && AUTHENTICATED_CHANNELS.contains(origin); +// boolean clientIdMatches = clientId != null && AUTHENTICATED_CHANNELS.contains(clientId); +// +// boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); +// boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); +// boolean isRegisteredUser = UserType.REGISTERED.equals(userType); +// +// if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { +// return false; +// } +// +// return originMatches || clientIdMatches; +// } +// +//// public static MassiveRecoverResult massiveRecoverByStatus( +//// ExecutionContext context, +//// BizEventToReceiptService bizEventToReceiptService, +//// BizEventCosmosClient bizEventCosmosClient, +//// ReceiptCosmosService receiptCosmosService, +//// Logger logger, +//// ReceiptStatusType statusType) { +//// List receiptList = new ArrayList<>(); +//// int successCounter = 0; +//// int errorCounter = 0; +//// String continuationToken = null; +//// do { +//// Iterable> feedResponseIterator = +//// receiptCosmosService.getFailedReceiptByStatus(continuationToken, 100, statusType); +//// +//// for (FeedResponse page : feedResponseIterator) { +//// for (Receipt receipt : page.getResults()) { +//// try { +//// Receipt restored = retrieveBizAndSendReceipt(receipt.getEventId(), context, bizEventToReceiptService, +//// bizEventCosmosClient, receiptCosmosService, receipt, logger); +//// if (isReceiptStatusValid(restored)) { +//// receiptList.add(restored); +//// successCounter++; +//// } else { +//// errorCounter++; +//// } +//// } catch (Exception e) { +//// logger.error(e.getMessage(), e); +//// errorCounter++; +//// } +//// } +//// continuationToken = page.getContinuationToken(); +//// } +//// } while (continuationToken != null); +//// +//// return MassiveRecoverResult.builder() +//// .receiptList(receiptList) +//// .successCounter(successCounter) +//// .errorCounter(errorCounter) +//// .build(); +//// } +//} From cc6a334b034d68a56a9df98749ad610ba52e4923 Mon Sep 17 00:00:00 2001 From: giomella Date: Mon, 12 Jan 2026 16:23:23 +0100 Subject: [PATCH 22/38] [PAGOPA-3343] added recover failed cart function --- .../client/BizEventCosmosClient.java | 9 +- .../client/impl/BizEventCosmosClientImpl.java | 8 +- .../BizEventUnprocessableEntityException.java | 30 +++ .../http/CartRecoverFailedReceipt.java | 215 +++++++++--------- .../service/BizEventToReceiptService.java | 28 ++- .../datastore/service/HelpdeskService.java | 12 + .../impl/BizEventToReceiptServiceImpl.java | 81 ++++--- .../service/impl/HelpdeskServiceImpl.java | 42 ++++ .../utils/BizEventToReceiptUtils.java | 6 + 9 files changed, 282 insertions(+), 149 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventUnprocessableEntityException.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java index 7f5eca82..930c8eb8 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/BizEventCosmosClient.java @@ -1,10 +1,11 @@ package it.gov.pagopa.receipt.pdf.datastore.client; -import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import java.util.List; + public interface BizEventCosmosClient { /** @@ -19,10 +20,8 @@ public interface BizEventCosmosClient { /** * Retrieve all biz-event documents related to a specific cart from CosmosDB database * - * @param transactionId id that identifies the cart - * @param continuationToken Paged query continuation token - * @param pageSize the page size + * @param transactionId id that identifies the cart * @return a list of biz-event document */ - Iterable> getAllBizEventDocument(String transactionId, String continuationToken, Integer pageSize); + List getAllCartBizEventDocument(String transactionId); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java index 11d6fc8c..75777a0e 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java @@ -5,12 +5,13 @@ import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; import com.azure.cosmos.models.CosmosQueryRequestOptions; -import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import java.util.List; + /** * Client for the CosmosDB database */ @@ -71,7 +72,7 @@ public BizEvent getBizEventDocument(String bizEventId) throws BizEventNotFoundEx * {@inheritDoc} */ @Override - public Iterable> getAllBizEventDocument(String transactionId, String continuationToken, Integer pageSize) { + public List getAllCartBizEventDocument(String transactionId) { CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); @@ -82,6 +83,7 @@ public Iterable> getAllBizEventDocument(String transactio //Query the container return cosmosContainer .queryItems(query, new CosmosQueryRequestOptions(), BizEvent.class) - .iterableByPage(continuationToken, pageSize); + .stream().limit(6) + .toList(); } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventUnprocessableEntityException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventUnprocessableEntityException.java new file mode 100644 index 00000000..3565c213 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/BizEventUnprocessableEntityException.java @@ -0,0 +1,30 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +import com.microsoft.azure.functions.HttpStatus; + +/** + * Thrown in case no receipt is found in the CosmosDB container + */ +public class BizEventUnprocessableEntityException extends BizEventException { + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public BizEventUnprocessableEntityException(String message) { + super(message, HttpStatus.UNPROCESSABLE_ENTITY); + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public BizEventUnprocessableEntityException(String message, Throwable cause) { + super(message, cause, HttpStatus.UNPROCESSABLE_ENTITY); + } +} + + diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java index f100e6db..ed1b7311 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java @@ -1,24 +1,33 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.azure.functions.*; -import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; -import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; -import it.gov.pagopa.receipt.pdf.datastore.utils.TransactionEventToCartReceiptUtils; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +35,9 @@ import java.util.List; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.hasCartInvalidStatus; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; + /** * Azure Functions with Azure Http trigger. */ @@ -34,17 +46,27 @@ public class CartRecoverFailedReceipt { private final Logger logger = LoggerFactory.getLogger(CartRecoverFailedReceipt.class); private final BizEventToReceiptService bizEventToReceiptService; - private final CartReceiptCosmosService cartReceiptCosmosService; + private final ReceiptCosmosService receiptCosmosService; + private final BizEventCosmosClient bizEventCosmosClient; + private final HelpdeskService helpdeskService; - public CartRecoverFailedReceipt(){ - this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); + public CartRecoverFailedReceipt() { + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + this.helpdeskService = new HelpdeskServiceImpl(); } - CartRecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, - CartReceiptCosmosService cartReceiptCosmosService){ + CartRecoverFailedReceipt( + BizEventToReceiptService bizEventToReceiptService, + ReceiptCosmosService receiptCosmosService, + BizEventCosmosClient bizEventCosmosClient, + HelpdeskService helpdeskService + ) { this.bizEventToReceiptService = bizEventToReceiptService; - this.cartReceiptCosmosService = cartReceiptCosmosService; + this.receiptCosmosService = receiptCosmosService; + this.bizEventCosmosClient = bizEventCosmosClient; + this.helpdeskService = helpdeskService; } /** @@ -72,7 +94,7 @@ public CartRecoverFailedReceipt(){ * @return response with {@link HttpStatus#OK} if the operation succeeded */ @FunctionName("CartRecoverFailedReceipt") - public HttpResponseMessage run ( + public HttpResponseMessage run( @HttpTrigger(name = "CartRecoverFailedReceiptTrigger", methods = {HttpMethod.POST}, route = "cart-receipts/{cart-id}/recover-failed", @@ -85,7 +107,8 @@ public HttpResponseMessage run ( containerName = "cart-for-receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding documentdb, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); // TODO @@ -95,99 +118,79 @@ public HttpResponseMessage run ( // se i biz sono validi procedere con la ricreazione della cart e l'invio in coda // vedere regenerate sul recupero dell'id e sovrascrittura + CartForReceipt existingCart; + try { + existingCart = this.receiptCosmosService.getCart(cartId); + } catch (CartNotFoundException e) { + String errMsg = "Cart receipt not found with the provided cart id"; + logger.error(errMsg, e); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); + } + + if (!hasCartInvalidStatus(existingCart.getStatus())) { + String errMsg = String.format( + "The provided cart is in status %s, which is not among the processable " + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", + existingCart.getStatus() + ); + logger.error(errMsg); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); + } + + // retrieve biz-event with the specified cartId + List bizEvents = this.bizEventCosmosClient.getAllCartBizEventDocument(cartId); try { - // retrieve biz-event with the specified cartId - List bizEvents = bizEventToReceiptService.getCartBizEventsById(cartId); - for(BizEvent bizEvent : bizEvents) { - // biz-event validation - if (BizEventToReceiptUtils.isBizEventInvalid(bizEvent, context, logger)) { - String errMsg = String.format("Biz-event with id %s is invalid", bizEvent.getId()); - throw new BizEventBadRequestException(errMsg); - } - - // total notice check - Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, context, logger); - if (totalNotice != bizEvents.size()) { - String errMsg = String.format("Failed to regenerate cart, the expected total notice %s does not match the number of biz events %s", - totalNotice, bizEvents.size()); - logger.error(errMsg); - return request - .createResponseBuilder(HttpStatus.UNPROCESSABLE_ENTITY) - .body(ProblemJson.builder() - .title(HttpStatus.UNPROCESSABLE_ENTITY.name()) - .detail(errMsg) - .status(HttpStatus.UNPROCESSABLE_ENTITY.value()) - .build()) - .build(); - } - } - - // all ok, proceed with the cart recovery - return null; - -// CartForReceipt receipt = TransactionEventToCartReceiptUtils.retrieveCartAndSendReceipt( -// cartId, context, this.cartReceiptCosmosService, -// this.bizEventCosmosClient, null, logger); -// -// documentdb.setValue(receipt); -// if (BizEventToReceiptUtils.isReceiptStatusValid(receipt)) { -// String responseMsg = String.format("Receipt with cartId %s recovered", cartId); -// return request.createResponseBuilder(HttpStatus.OK) -// .body(responseMsg) -// .build(); -// } else { -// return request -// .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(ProblemJson.builder() -// .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) -// .detail(receipt.getReasonErr().getMessage()) -// .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) -// .build()) -// .build(); -// } - } catch (BizEventBadRequestException ex) { - String msg = String.format("Problem with the biz-events related to cartId %s", cartId); - logger.error(msg, ex); - return request - .createResponseBuilder(ex.getHttpStatus()) - .body(ProblemJson.builder() - .title(ex.getHttpStatus().name()) - .detail(msg) - .status(ex.getHttpStatus().value()) - .build()) - .build(); - - } catch (BizEventNotFoundException | BizEventBadRequestException exception) { - String msg = String.format("Unable to retrieve the biz-event with id %s", cartId); - logger.error(msg, exception); - return request - .createResponseBuilder(exception.getHttpStatus()) - .body(ProblemJson.builder() - .title(exception.getHttpStatus().name()) - .detail(msg) - .status(exception.getHttpStatus().value()) - .build()) - .build(); + this.helpdeskService.validateCartBizEvents(bizEvents); + } catch (BizEventBadRequestException e) { + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } catch (BizEventUnprocessableEntityException e) { + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); + } + + CartForReceipt cartForReceipt; + try { + cartForReceipt = this.bizEventToReceiptService.buildCartFromBizEventList(bizEvents); + cartForReceipt.set_etag(existingCart.get_etag()); } catch (PDVTokenizerException | JsonProcessingException e) { logger.error(e.getMessage(), e); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(e.getMessage()) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); - } catch (ReceiptNotFoundException e) { - logger.error(e.getMessage(), e); - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } + + if (isCartStatusValid(cartForReceipt)) { + // saved on CosmosDB + cartForReceipt = this.bizEventToReceiptService.saveCartForReceiptWithoutRetry(cartForReceipt); + } + + if (isCartStatusValid(cartForReceipt)) { + this.bizEventToReceiptService.handleSendCartMessageToQueue(bizEvents, cartForReceipt); + } + + if (!isCartStatusValid(cartForReceipt)) { + String errMsg = String.format("Recover failed for cart with id %s. Reason: %s", + cartId, cartForReceipt.getReasonErr().getMessage()); + logger.error(errMsg); + documentdb.setValue(cartForReceipt); + return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, errMsg); + } + + String responseMsg = String.format("Receipt with eventId %s recovered", cartId); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + } + + private HttpResponseMessage buildErrorResponse( + HttpRequestMessage> request, + HttpStatus httpStatus, + String errMsg + ) { + return request + .createResponseBuilder(httpStatus) + .body(ProblemJson.builder() + .title(httpStatus.name()) + .detail(errMsg) + .status(httpStatus.value()) + .build()) + .build(); } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java index aee025f7..e7e4289b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/BizEventToReceiptService.java @@ -84,20 +84,23 @@ void tokenizeFiscalCodes( CartForReceipt buildCartForReceipt(BizEvent bizEvent); /** - * Retrieve all events that are associated to the cart with the specified id + * Creates a new instance of Cart Receipt, using the tokenizer service to mask the PII, based on + * the provided list of BizEvent * - * @param cart the cart - * @return a list of biz-events + * @param bizEventList list of biz event that compose the cart + * @return the Cart Receipt + * @throws PDVTokenizerException when an error occur while tokenizing PII + * @throws JsonProcessingException when an error occur while parsing PDV Tokenizer response */ - List getCartBizEvents(CartForReceipt cart); + CartForReceipt buildCartFromBizEventList(List bizEventList) throws PDVTokenizerException, JsonProcessingException; /** * Retrieve all events that are associated to the cart with the specified id * - * @param cartId the id of the cart + * @param cart the cart * @return a list of biz-events */ - List getCartBizEventsById(String cartId); + List getCartBizEvents(CartForReceipt cart); /** * This method saves the provided CartForReceipt object to the datastore. @@ -116,6 +119,19 @@ void tokenizeFiscalCodes( */ CartForReceipt saveCartForReceipt(CartForReceipt cartForReceipt, BizEvent bizEvent); + /** + * This method saves the provided CartForReceipt object to the datastore without attempting a retry on failure. + * + *

+ * If the operation fail it change the {@link CartForReceipt#getStatus()} + * to {@link CartStatusType#FAILED} and add a {@link ReasonError} + *

+ * + * @param cartForReceipt the cart to save + * @return the saved cart or if it fails the cart updated with the reason error + */ + CartForReceipt saveCartForReceiptWithoutRetry(CartForReceipt cartForReceipt); + /** * Recovers a cart from the CosmosDB by the property eventId * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java new file mode 100644 index 00000000..8a790ead --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java @@ -0,0 +1,12 @@ +package it.gov.pagopa.receipt.pdf.datastore.service; + +import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; + +import java.util.List; + +public interface HelpdeskService { + void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException; + +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index 64cadab5..0ede7ca6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -252,6 +252,31 @@ public CartForReceipt buildCartForReceipt(BizEvent bizEvent) { } + /** + * {@inheritDoc} + */ + public CartForReceipt buildCartFromBizEventList(List bizEventList) throws PDVTokenizerException, JsonProcessingException { + List cartItems = new ArrayList<>(); + for (BizEvent bizEvent : bizEventList) { + cartItems.add(buildCartPayment(bizEvent)); + } + + BizEvent bizEvent = bizEventList.get(0); + String transactionId = bizEvent.getTransactionDetails().getTransaction().getTransactionId(); + BigDecimal amount = getCartAmount(bizEvent); + return CartForReceipt.builder() + .id(transactionId) + .eventId(transactionId) + .version("1") // this is the first version of this document + .payload(Payload.builder() + .payerFiscalCode(tokenizerPayerFiscalCode(bizEvent)) + .totalNotice(Integer.parseInt(bizEvent.getPaymentInfo().getTotalNotice())) + .totalAmount(!amount.equals(BigDecimal.ZERO) ? formatAmount(amount.toString()) : null) + .transactionCreationDate(getTransactionCreationDate(bizEvent)) + .cart(cartItems) + .build()) + .build(); + } @Override public List getCartBizEvents(CartForReceipt cart) { @@ -273,25 +298,6 @@ public List getCartBizEvents(CartForReceipt cart) { return bizEventList; } - /** - * {@inheritDoc} - */ - @Override - public List getCartBizEventsById(String cartId) { - List bizEventList = new ArrayList<>(); - String continuationToken = null; - do { - Iterable> feedResponseIterator = - this.bizEventCosmosClient.getAllBizEventDocument(cartId, continuationToken, 100); - - for (FeedResponse page : feedResponseIterator) { - bizEventList.addAll(page.getResults()); - continuationToken = page.getContinuationToken(); - } - } while (continuationToken != null); - return bizEventList; - } - /** * {@inheritDoc} */ @@ -312,16 +318,20 @@ public CartForReceipt saveCartForReceipt(CartForReceipt cartForReceipt, BizEvent statusCode = trySaveCart(cartForReceipt); } - if (statusCode != HttpStatus.CREATED.value() && statusCode != HttpStatus.OK.value()) { - String errorString = String.format( - "[BizEventToReceiptService] Error saving cart to cosmos for receipt with eventId %s, cosmos client responded with status %s", - cartForReceipt.getEventId(), statusCode); - ReasonError reasonError = new ReasonError(statusCode, errorString); - cartForReceipt.setReasonErr(reasonError); - cartForReceipt.setStatus(CartStatusType.FAILED); - //Error info - logger.error(errorString); - } + handleSaveCartFailure(cartForReceipt, statusCode); + return cartForReceipt; + } + + /** + * {@inheritDoc} + */ + @Override + public CartForReceipt saveCartForReceiptWithoutRetry(CartForReceipt cartForReceipt) { + int statusCode; + + statusCode = trySaveCart(cartForReceipt); + handleSaveCartFailure(cartForReceipt, statusCode); + return cartForReceipt; } @@ -361,6 +371,19 @@ private int trySaveCart(CartForReceipt cartForReceipt) { return statusCode; } + private void handleSaveCartFailure(CartForReceipt cartForReceipt, int statusCode) { + if (statusCode != HttpStatus.CREATED.value() && statusCode != HttpStatus.OK.value()) { + String errorString = String.format( + "[BizEventToReceiptService] Error saving cart to cosmos for receipt with eventId %s, cosmos client responded with status %s", + cartForReceipt.getEventId(), statusCode); + ReasonError reasonError = new ReasonError(statusCode, errorString); + cartForReceipt.setReasonErr(reasonError); + cartForReceipt.setStatus(CartStatusType.FAILED); + //Error info + logger.error(errorString); + } + } + /** * Handles errors for PDV tokenizer and updates receipt's status accordingly * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java new file mode 100644 index 00000000..a0c236ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -0,0 +1,42 @@ +package it.gov.pagopa.receipt.pdf.datastore.service.impl; + +import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class HelpdeskServiceImpl implements HelpdeskService { + + private final Logger logger = LoggerFactory.getLogger(HelpdeskServiceImpl.class); + + @Override + public void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { + if (bizEvents.isEmpty()) { + String errMsg = String.format("BizEvents for cart %s not found", cartId); + logger.error(errMsg); + throw new BizEventBadRequestException(errMsg); + } + + for(BizEvent bizEvent : bizEvents) { + // biz-event validation + if (BizEventToReceiptUtils.isBizEventInvalid(bizEvent, context, logger)) { + String errMsg = String.format("Biz-event with id %s is invalid", bizEvent.getId()); + throw new BizEventBadRequestException(errMsg); + } + + // total notice check + Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, context, logger); + if (totalNotice != bizEvents.size()) { + String errMsg = String.format("Failed to regenerate cart, the expected total notice %s does not match the number of biz events %s", + totalNotice, bizEvents.size()); + logger.error(errMsg); + throw new BizEventUnprocessableEntityException(errMsg); + } + } + } +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 8a281566..ef57b774 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -333,6 +333,12 @@ public static boolean isCartStatusValid(CartForReceipt cartForReceipt) { return cartForReceipt.getStatus() != CartStatusType.FAILED && cartForReceipt.getStatus() != CartStatusType.NOT_QUEUE_SENT; } + public static boolean hasCartInvalidStatus(CartStatusType status) { + return CartStatusType.INSERTED.equals(status) + || CartStatusType.NOT_QUEUE_SENT.equals(status) + || CartStatusType.FAILED.equals(status); + } + public static boolean isValidFiscalCode(String fiscalCode) { if (fiscalCode != null && !fiscalCode.isEmpty()) { Pattern patternCF = Pattern.compile("^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"); From 99390c2aaf039e757f76a35d15e89d6d37d86070 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 11:19:10 +0100 Subject: [PATCH 23/38] [PAGOPA-3343] added massive and scheduled recover failed cart functions and related logic --- .../pdf/datastore/BizEventToReceipt.java | 15 +- .../client/CartReceiptsCosmosClient.java | 21 ++- .../impl/CartReceiptsCosmosClientImpl.java | 45 +++++- .../datastore/entity/cart/CartStatusType.java | 13 +- .../exception/InvalidParameterException.java | 28 ++++ .../http/RecoverFailedCartReceiptMassive.java | 133 ++++++++++++++++++ .../RecoverFailedCartReceiptScheduled.java | 87 ++++++++++++ .../model/MassiveCartRecoverResult.java | 16 +++ .../datastore/service/HelpdeskService.java | 36 ++++- .../service/ReceiptCosmosService.java | 14 ++ .../service/impl/HelpdeskServiceImpl.java | 104 +++++++++++++- .../impl/ReceiptCosmosServiceImpl.java | 23 +++ .../utils/BizEventToReceiptUtils.java | 52 ++++--- 13 files changed, 546 insertions(+), 41 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/InvalidParameterException.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java index 899e102d..4bd1befb 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java @@ -14,6 +14,7 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -112,7 +113,7 @@ public void processBizEventToReceipt( - legacy cart - already processed */ - if (isBizEventInvalid(bizEvent, context, logger) || isBizEventAlreadyProcessed(context, bizEvent)) { + if (isInvalid(bizEvent) || isBizEventAlreadyProcessed(context, bizEvent)) { discarder++; continue; } @@ -120,7 +121,7 @@ public void processBizEventToReceipt( logger.debug("[{}] function called at {} for event with id {} and status {}", context.getFunctionName(), LocalDateTime.now(), bizEvent.getId(), bizEvent.getEventStatus()); - Integer totalNotice = getTotalNotice(bizEvent, context, logger); + Integer totalNotice = getTotalNotice(bizEvent, logger); if (totalNotice == 1) { Receipt receipt = createReceipt(bizEvent, this.bizEventToReceiptService, logger); @@ -190,8 +191,16 @@ public void processBizEventToReceipt( } } + private boolean isInvalid(BizEvent bizEvent) { + BizEventToReceiptUtils.BizEventValidityCheck bizEventValidityCheck = isBizEventInvalid(bizEvent); + if (bizEventValidityCheck.invalid()) { + logger.debug(bizEventValidityCheck.error()); + } + return bizEventValidityCheck.invalid(); + } + private boolean isBizEventAlreadyProcessed(ExecutionContext context, BizEvent bizEvent) { - Integer totalNotice = getTotalNotice(bizEvent, context, logger); + Integer totalNotice = getTotalNotice(bizEvent, logger); if (totalNotice == 1) { try { Receipt receipt = this.bizEventToReceiptService.getReceipt(bizEvent.getId()); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java index b083ab36..1a4d6e7c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java @@ -1,12 +1,12 @@ package it.gov.pagopa.receipt.pdf.datastore.client; import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.CartConcurrentUpdateException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; public interface CartReceiptsCosmosClient { @@ -43,4 +43,21 @@ public interface CartReceiptsCosmosClient { * @throws CartNotFoundException If the document isn't found */ CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException; + + /** + * Retrieve failed cart receipt documents from CosmosDB database + * + * @param continuationToken Paged query continuation token + * @return receipt documents + */ + Iterable> getFailedCartReceiptDocuments(String continuationToken, Integer pageSize); + + /** + * Retrieve the failed cart receipt documents with {@link CartStatusType#INSERTED} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return receipt documents + */ + Iterable> getInsertedCartReceiptDocuments(String continuationToken, Integer pageSize); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java index a66d4e77..e0cd1095 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java @@ -5,14 +5,16 @@ import com.azure.cosmos.models.CosmosItemRequestOptions; import com.azure.cosmos.models.CosmosItemResponse; import com.azure.cosmos.models.CosmosQueryRequestOptions; -import com.azure.cosmos.util.CosmosPagedIterable; +import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartConcurrentUpdateException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.Optional; public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { @@ -24,6 +26,9 @@ public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { private static final String DOCUMENT_NOT_FOUND_ERR_MSG = "Document not found in the defined container"; + private final String millisDiff = System.getenv("MAX_DATE_DIFF_MILLIS"); + private final String numDaysRecoverFailed = System.getenv().getOrDefault("RECOVER_FAILED_MASSIVE_MAX_DAYS", "0"); + private final CosmosClient cosmosClient; private CartReceiptsCosmosClientImpl() { @@ -112,6 +117,30 @@ public CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundEx .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } + /** + * {@inheritDoc} + */ + @Override + public Iterable> getFailedCartReceiptDocuments(String continuationToken, Integer pageSize) { + String query = "SELECT * FROM c WHERE c.status = 'FAILED'"; + return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getInsertedCartReceiptDocuments(String continuationToken, Integer pageSize) { + OffsetDateTime currentDateTime = OffsetDateTime.now(); + long now = currentDateTime.toInstant().toEpochMilli(); + long daysAgo = currentDateTime.truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverFailed)).toInstant().toEpochMilli(); + + String query = String.format( + "SELECT * FROM c WHERE (c.status = '%s' AND c.inserted_at >= %s AND ( %s - c.inserted_at) >= %s)", + ReceiptStatusType.INSERTED, daysAgo, now, millisDiff); + + return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); + } /** * PRIVATE METHODS @@ -129,4 +158,16 @@ private Optional getDocumentByFilter(String containerId, String propertyN .stream() .findFirst(); } + + private Iterable> executePagedQuery( + String containerName, + String query, + String continuationToken, + Integer pageSize + ) { + CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); + return cosmosDatabase.getContainer(containerName) + .queryItems(query, new CosmosQueryRequestOptions(), CartForReceipt.class) + .iterableByPage(continuationToken, pageSize); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java index 44845a53..4cb9f143 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java @@ -1,5 +1,7 @@ package it.gov.pagopa.receipt.pdf.datastore.entity.cart; +import java.util.Set; + public enum CartStatusType { WAITING_FOR_BIZ_EVENT, @@ -14,6 +16,15 @@ public enum CartStatusType { IO_NOTIFIER_RETRY, UNABLE_TO_SEND, NOT_TO_NOTIFY, - TO_REVIEW + TO_REVIEW; + + private static final Set DATASTORE_FAILED_STATUS = Set.of( + NOT_QUEUE_SENT, + INSERTED, + FAILED + ); + public boolean isAFailedDatastoreStatus() { + return DATASTORE_FAILED_STATUS.contains(this); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/InvalidParameterException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/InvalidParameterException.java new file mode 100644 index 00000000..fa9220ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/InvalidParameterException.java @@ -0,0 +1,28 @@ +package it.gov.pagopa.receipt.pdf.datastore.exception; + +/** + * Thrown in case an invalid parameter is provided in a helpdesk request + */ +public class InvalidParameterException extends Exception { + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public InvalidParameterException(String message) { + super(message); + } + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception thrown + */ + public InvalidParameterException(String message, Throwable cause) { + super(message, cause); + } +} + + diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java new file mode 100644 index 00000000..1240f5fe --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java @@ -0,0 +1,133 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + + +/** + * Azure Functions with Azure Http trigger. + */ +public class RecoverFailedCartReceiptMassive { + + private final Logger logger = LoggerFactory.getLogger(RecoverFailedCartReceiptMassive.class); + + private final HelpdeskService helpdeskService; + + public RecoverFailedCartReceiptMassive() { + this.helpdeskService = new HelpdeskServiceImpl(); + } + + RecoverFailedCartReceiptMassive(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + *

+ * It recovers all the cart receipts with the specified status that has to be one of: + * - ({@link CartStatusType#INSERTED}) + * - ({@link CartStatusType#FAILED}) + * - ({@link CartStatusType#NOT_QUEUE_SENT}) + *

+ * It creates the cart receipts and send on queue the event in order to proceed with the receipt generation. + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverFailedCartReceiptMassive") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverFailedCartReceiptMassiveTrigger", + methods = {HttpMethod.POST}, + route = "cart-receipts/recover-failed", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context + ) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + // Get named parameter + String statusParam = request.getQueryParameters().get("status"); + CartStatusType status; + try { + status = validateStatusParam(statusParam); + } catch (InvalidParameterException e) { + return request + .createResponseBuilder(HttpStatus.BAD_REQUEST) + .body(ProblemJson.builder() + .title(HttpStatus.BAD_REQUEST.name()) + .detail(e.getMessage()) + .status(HttpStatus.BAD_REQUEST.value()) + .build()) + .build(); + } + + MassiveCartRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); + + int successCounter = recoverResult.getSuccessCounter(); + int errorCounter = recoverResult.getErrorCounter(); + + if (errorCounter > 0) { + documentdb.setValue(recoverResult.getFailedCartList()); + + String msg = String.format("Recovered %s receipts but %s encountered an error.", successCounter, errorCounter); + return request + .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ProblemJson.builder() + .title("Partial OK") + .detail(msg) + .status(HttpStatus.MULTI_STATUS.value()) + .build()) + .build(); + } + String responseMsg = String.format("Recovered %s receipts", successCounter); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + } + + private CartStatusType validateStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + CartStatusType status; + try { + status = CartStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + if (!status.isAFailedDatastoreStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + throw new InvalidParameterException(message); + } + return status; + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java new file mode 100644 index 00000000..0ef7e35d --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java @@ -0,0 +1,87 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.TimerTrigger; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * Azure Functions with Timer trigger. + */ +public class RecoverFailedCartReceiptScheduled { + + private final Logger logger = LoggerFactory.getLogger(RecoverFailedCartReceiptScheduled.class); + + private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("FAILED_AUTORECOVER_ENABLED", "true")); + + private final HelpdeskService helpdeskService; + + public RecoverFailedCartReceiptScheduled() { + this.helpdeskService = new HelpdeskServiceImpl(); + } + + RecoverFailedCartReceiptScheduled(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; + } + + /** + * This function will be invoked periodically according to the specified schedule. + *

+ * It recovers all the receipts with the following status: + * - ({@link CartStatusType#INSERTED}) + * - ({@link CartStatusType#FAILED}) + * - ({@link CartStatusType#NOT_QUEUE_SENT}) + *

+ * It recovers cart receipts and send on queue the event in order to proceed with the receipt generation. + */ + @FunctionName("RecoverFailedCartReceiptScheduled") + public void run( + @TimerTrigger(name = "timerInfoFailed", schedule = "%RECOVER_FAILED_CRON%") String timerInfo, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context + ) { + if (isEnabled) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + List failedCart = new ArrayList<>(); + failedCart.addAll(recover(CartStatusType.INSERTED)); + failedCart.addAll(recover(CartStatusType.FAILED)); + failedCart.addAll(recover(CartStatusType.NOT_QUEUE_SENT)); + + documentdb.setValue(failedCart); + } + } + + private List recover(CartStatusType status) { + MassiveCartRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); + + int successCounter = recoverResult.getSuccessCounter(); + int errorCounter = recoverResult.getErrorCounter(); + + if (errorCounter > 0) { + logger.error("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); + return recoverResult.getFailedCartList(); + } + + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java new file mode 100644 index 00000000..4f4405f8 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.receipt.pdf.datastore.model; + +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class MassiveCartRecoverResult { + + private List failedCartList; + private int errorCounter; + private int successCounter; +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java index 8a790ead..85549de2 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java @@ -1,12 +1,40 @@ package it.gov.pagopa.receipt.pdf.datastore.service; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; -import java.util.List; - +/** + * Service that hold receipt helpdesk logic + */ public interface HelpdeskService { - void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException; + /** + * Recover the specified cart. + *

+ * It retrieves all related biz event, rebuild {@link CartForReceipt} model, saves it and sends message on queue + * for PDF generation + *

+ * + * @param existingCart the cart to recover + * @return the recover cart + * @throws BizEventUnprocessableEntityException in case one of the biz events does not have the correct total notice + * @throws BizEventBadRequestException in case one of the biz events is invalid for receipt generation + * @throws PDVTokenizerException in case an error occur while tokenizing PII data + * @throws JsonProcessingException in case an error occur while parsing tokenizer response + */ + CartForReceipt recoverCart(CartForReceipt existingCart) + throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException; + + /** + * Massive recover all cart with the specified status {@link CartStatusType} + * + * @param status the status to be recovered + * @return the recover result + */ + MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index ce95ceab..2aeedd74 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -3,6 +3,7 @@ import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; @@ -63,7 +64,20 @@ Iterable> getFailedReceiptByStatus( ); /** + * Retrieve the failed cart receipt with the provided {@link CartStatusType} status * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the receipts + * @return receipt documents + */ + Iterable> getFailedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ); + + /** * @param messageId * @return */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index a0c236ce..cdcd7c93 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -1,38 +1,128 @@ package it.gov.pagopa.receipt.pdf.datastore.service.impl; +import com.azure.cosmos.models.FeedResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; + public class HelpdeskServiceImpl implements HelpdeskService { private final Logger logger = LoggerFactory.getLogger(HelpdeskServiceImpl.class); + private final ReceiptCosmosService receiptCosmosService; + private final BizEventToReceiptService bizEventToReceiptService; + private final BizEventCosmosClient bizEventCosmosClient; + + public HelpdeskServiceImpl() { + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + } + + public HelpdeskServiceImpl( + ReceiptCosmosService receiptCosmosService, + BizEventToReceiptService bizEventToReceiptService, + BizEventCosmosClient bizEventCosmosClient + ) { + this.receiptCosmosService = receiptCosmosService; + this.bizEventToReceiptService = bizEventToReceiptService; + this.bizEventCosmosClient = bizEventCosmosClient; + } + + @Override + public CartForReceipt recoverCart(CartForReceipt existingCart) + throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException { + // retrieve biz-event with the specified cartId + List bizEvents = this.bizEventCosmosClient.getAllCartBizEventDocument(existingCart.getEventId()); + validateCartBizEvents(bizEvents); + + CartForReceipt cart = this.bizEventToReceiptService.buildCartFromBizEventList(bizEvents); + cart.set_etag(existingCart.get_etag()); + + if (isCartStatusValid(cart)) { + cart = this.bizEventToReceiptService.saveCartForReceiptWithoutRetry(cart); + } + + if (isCartStatusValid(cart)) { + this.bizEventToReceiptService.handleSendCartMessageToQueue(bizEvents, cart); + } + return cart; + } + @Override - public void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { + public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { + List failedCart = new ArrayList<>(); + int successCounter = 0; + int errorCounter = 0; + String continuationToken = null; + do { + Iterable> feedResponseIterator = + this.receiptCosmosService.getFailedCartReceiptByStatus(continuationToken, 100, status); + + for (FeedResponse page : feedResponseIterator) { + for (CartForReceipt cart : page.getResults()) { + try { + CartForReceipt recoverCart = recoverCart(cart); + + if (!isCartStatusValid(recoverCart)) { + failedCart.add(recoverCart); + errorCounter++; + } else { + successCounter++; + } + } catch (Exception e) { + logger.error("Recover for cart {} failed", cart.getEventId(), e); + errorCounter++; + } + } + continuationToken = page.getContinuationToken(); + } + } while (continuationToken != null); + + return MassiveCartRecoverResult.builder() + .failedCartList(failedCart) + .successCounter(successCounter) + .errorCounter(errorCounter) + .build(); + } + + private void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { if (bizEvents.isEmpty()) { - String errMsg = String.format("BizEvents for cart %s not found", cartId); + String errMsg = "BizEvents for the specified cart not found"; logger.error(errMsg); throw new BizEventBadRequestException(errMsg); } - for(BizEvent bizEvent : bizEvents) { + for (BizEvent bizEvent : bizEvents) { // biz-event validation - if (BizEventToReceiptUtils.isBizEventInvalid(bizEvent, context, logger)) { - String errMsg = String.format("Biz-event with id %s is invalid", bizEvent.getId()); + BizEventToReceiptUtils.BizEventValidityCheck bizEventValidityCheck = BizEventToReceiptUtils.isBizEventInvalid(bizEvent); + if (bizEventValidityCheck.invalid()) { + String errMsg = String.format("Biz-event with id %s is invalid: %s", bizEvent.getId(), bizEventValidityCheck.error()); throw new BizEventBadRequestException(errMsg); } // total notice check - Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, context, logger); + Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, logger); if (totalNotice != bizEvents.size()) { - String errMsg = String.format("Failed to regenerate cart, the expected total notice %s does not match the number of biz events %s", + String errMsg = String.format("The expected total notice %s does not match the number of biz events %s", totalNotice, bizEvents.size()); logger.error(errMsg); throw new BizEventUnprocessableEntityException(errMsg); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index 56c1e9db..beed5a55 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -6,6 +6,7 @@ import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartReceiptsCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; @@ -110,6 +111,28 @@ public Iterable> getFailedReceiptByStatus( throw new IllegalStateException(errMsg); } + /** + * {@inheritDoc} + */ + @Override + public Iterable> getFailedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(CartStatusType.FAILED) || statusType.equals(CartStatusType.NOT_QUEUE_SENT)) { + return this.cartReceiptsCosmosClient.getFailedCartReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(CartStatusType.INSERTED)) { + return this.cartReceiptsCosmosClient.getInsertedCartReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving failed receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + @Override public IOMessage getReceiptMessage(String messageId) throws IoMessageNotFoundException { IOMessage message; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index ef57b774..c2e4f37d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -21,6 +21,7 @@ import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import lombok.Builder; import org.slf4j.Logger; import java.math.BigDecimal; @@ -136,27 +137,29 @@ public static Receipt createReceipt(BizEvent bizEvent, BizEventToReceiptService * in the receipt generation * * @param bizEvent BizEvent to validate - * @param context Function context - * @param logger Function logger * @return boolean to determine if the proposed event is invalid */ - public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext context, Logger logger) { + public static BizEventValidityCheck isBizEventInvalid(BizEvent bizEvent) { if (bizEvent == null) { - logger.debug("[{}] event is null", context.getFunctionName()); - return true; + return BizEventValidityCheck.builder() + .invalid(true) + .error("Biz event is null") + .build(); } if (!BizEventStatusType.DONE.equals(bizEvent.getEventStatus())) { - logger.debug("[{}] event with id {} discarded because in status {}", - context.getFunctionName(), bizEvent.getId(), bizEvent.getEventStatus()); - return true; + return BizEventValidityCheck.builder() + .invalid(true) + .error(String.format("Biz event is in invalid status %s", bizEvent.getEventStatus())) + .build(); } if (!hasValidFiscalCode(bizEvent)) { - logger.debug("[{}] event with id {} discarded because debtor's and payer's identifiers are missing or not valid", - context.getFunctionName(), bizEvent.getId()); - return true; + return BizEventValidityCheck.builder() + .invalid(true) + .error("Biz event is in invalid because debtor's and payer's identifiers are missing or not valid") + .build(); } if (Boolean.TRUE.equals(ECOMMERCE_FILTER_ENABLED) @@ -164,19 +167,24 @@ public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext cont && bizEvent.getTransactionDetails().getInfo() != null && ECOMMERCE.equals(bizEvent.getTransactionDetails().getInfo().getClientId()) ) { - logger.debug("[{}] event with id {} discarded because from e-commerce {}", - context.getFunctionName(), bizEvent.getId(), bizEvent.getTransactionDetails().getInfo().getClientId()); - return true; + return BizEventValidityCheck.builder() + .invalid(true) + .error("Biz event is in invalid because it is from e-commerce and e-commerce filter is enabled") + .build(); } if (!isCartMod1(bizEvent)) { - logger.debug("[{}] event with id {} contain either an invalid amount value," + - " or it is a legacy cart element", - context.getFunctionName(), bizEvent.getId()); - return true; + return BizEventValidityCheck.builder() + .invalid(true) + .error("Biz event is in invalid because contain either an invalid amount value or it is a legacy cart element") + .build(); } - return false; + return new BizEventValidityCheck(false, null); + } + + @Builder + public record BizEventValidityCheck(boolean invalid, String error) { } private static boolean hasValidTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { @@ -226,7 +234,7 @@ private static boolean hasValidFiscalCode(BizEvent bizEvent) { return isValidDebtor || isValidPayer; } - public static Integer getTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { + public static Integer getTotalNotice(BizEvent bizEvent, Logger logger) { if (bizEvent.getPaymentInfo() != null) { String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); @@ -236,8 +244,8 @@ public static Integer getTotalNotice(BizEvent bizEvent, ExecutionContext context try { intTotalNotice = Integer.parseInt(totalNotice); } catch (NumberFormatException e) { - logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", - context.getFunctionName(), bizEvent.getId(), + logger.error("Event with id {} discarded because has an invalid total notice value: {}", + bizEvent.getId(), totalNotice, e); throw e; From 173ef01949f744f14b35a847444154d4b5952bc7 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 11:36:14 +0100 Subject: [PATCH 24/38] [PAGOPA-3343] refactor to reduce duplicated code --- ...ipt.java => RecoverFailedCartReceipt.java} | 62 ++++++------------- .../service/impl/HelpdeskServiceImpl.java | 5 +- .../utils/BizEventToReceiptUtils.java | 6 -- 3 files changed, 21 insertions(+), 52 deletions(-) rename src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/{CartRecoverFailedReceipt.java => RecoverFailedCartReceipt.java} (78%) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java similarity index 78% rename from src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java rename to src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index ed1b7311..68fad586 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartRecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -12,60 +12,46 @@ import com.microsoft.azure.functions.annotation.CosmosDBOutput; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; -import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.hasCartInvalidStatus; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; /** * Azure Functions with Azure Http trigger. */ -public class CartRecoverFailedReceipt { +public class RecoverFailedCartReceipt { - private final Logger logger = LoggerFactory.getLogger(CartRecoverFailedReceipt.class); + private final Logger logger = LoggerFactory.getLogger(RecoverFailedCartReceipt.class); - private final BizEventToReceiptService bizEventToReceiptService; private final ReceiptCosmosService receiptCosmosService; - private final BizEventCosmosClient bizEventCosmosClient; private final HelpdeskService helpdeskService; - public CartRecoverFailedReceipt() { + public RecoverFailedCartReceipt() { this.receiptCosmosService = new ReceiptCosmosServiceImpl(); - this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); - this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); this.helpdeskService = new HelpdeskServiceImpl(); } - CartRecoverFailedReceipt( - BizEventToReceiptService bizEventToReceiptService, + RecoverFailedCartReceipt( ReceiptCosmosService receiptCosmosService, - BizEventCosmosClient bizEventCosmosClient, HelpdeskService helpdeskService ) { - this.bizEventToReceiptService = bizEventToReceiptService; this.receiptCosmosService = receiptCosmosService; - this.bizEventCosmosClient = bizEventCosmosClient; this.helpdeskService = helpdeskService; } @@ -93,9 +79,9 @@ public CartRecoverFailedReceipt() { * * @return response with {@link HttpStatus#OK} if the operation succeeded */ - @FunctionName("CartRecoverFailedReceipt") + @FunctionName("RecoverFailedCartReceipt") public HttpResponseMessage run( - @HttpTrigger(name = "CartRecoverFailedReceiptTrigger", + @HttpTrigger(name = "RecoverFailedCartReceiptTrigger", methods = {HttpMethod.POST}, route = "cart-receipts/{cart-id}/recover-failed", authLevel = AuthorizationLevel.ANONYMOUS) @@ -127,7 +113,7 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } - if (!hasCartInvalidStatus(existingCart.getStatus())) { + if (isCartStatusNotProcessable(existingCart.getStatus())) { String errMsg = String.format( "The provided cart is in status %s, which is not among the processable " + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", @@ -137,34 +123,20 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } - // retrieve biz-event with the specified cartId - List bizEvents = this.bizEventCosmosClient.getAllCartBizEventDocument(cartId); + CartForReceipt cartForReceipt; try { - this.helpdeskService.validateCartBizEvents(bizEvents); - } catch (BizEventBadRequestException e) { - return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + cartForReceipt = this.helpdeskService.recoverCart(existingCart); } catch (BizEventUnprocessableEntityException e) { + logger.error(e.getMessage(), e); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } catch (BizEventBadRequestException e) { + logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); - } - - CartForReceipt cartForReceipt; - try { - cartForReceipt = this.bizEventToReceiptService.buildCartFromBizEventList(bizEvents); - cartForReceipt.set_etag(existingCart.get_etag()); } catch (PDVTokenizerException | JsonProcessingException e) { logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } - if (isCartStatusValid(cartForReceipt)) { - // saved on CosmosDB - cartForReceipt = this.bizEventToReceiptService.saveCartForReceiptWithoutRetry(cartForReceipt); - } - - if (isCartStatusValid(cartForReceipt)) { - this.bizEventToReceiptService.handleSendCartMessageToQueue(bizEvents, cartForReceipt); - } - if (!isCartStatusValid(cartForReceipt)) { String errMsg = String.format("Recover failed for cart with id %s. Reason: %s", cartId, cartForReceipt.getReasonErr().getMessage()); @@ -179,6 +151,12 @@ public HttpResponseMessage run( .build(); } + public boolean isCartStatusNotProcessable(CartStatusType status) { + return !CartStatusType.INSERTED.equals(status) + && !CartStatusType.NOT_QUEUE_SENT.equals(status) + && !CartStatusType.FAILED.equals(status); + } + private HttpResponseMessage buildErrorResponse( HttpRequestMessage> request, HttpStatus httpStatus, diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index cdcd7c93..2a773a57 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -106,9 +106,7 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { private void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { if (bizEvents.isEmpty()) { - String errMsg = "BizEvents for the specified cart not found"; - logger.error(errMsg); - throw new BizEventBadRequestException(errMsg); + throw new BizEventBadRequestException("BizEvents for the specified cart not found"); } for (BizEvent bizEvent : bizEvents) { @@ -124,7 +122,6 @@ private void validateCartBizEvents(List bizEvents) throws BizEventBadR if (totalNotice != bizEvents.size()) { String errMsg = String.format("The expected total notice %s does not match the number of biz events %s", totalNotice, bizEvents.size()); - logger.error(errMsg); throw new BizEventUnprocessableEntityException(errMsg); } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index c2e4f37d..1ecc2042 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -341,12 +341,6 @@ public static boolean isCartStatusValid(CartForReceipt cartForReceipt) { return cartForReceipt.getStatus() != CartStatusType.FAILED && cartForReceipt.getStatus() != CartStatusType.NOT_QUEUE_SENT; } - public static boolean hasCartInvalidStatus(CartStatusType status) { - return CartStatusType.INSERTED.equals(status) - || CartStatusType.NOT_QUEUE_SENT.equals(status) - || CartStatusType.FAILED.equals(status); - } - public static boolean isValidFiscalCode(String fiscalCode) { if (fiscalCode != null && !fiscalCode.isEmpty()) { Pattern patternCF = Pattern.compile("^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"); From 55410609801dbc7afa3ff3b225002dfccfdea6ef Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 12:43:28 +0100 Subject: [PATCH 25/38] [PAGOPA-3343] improve code and minor fix --- .../impl/CartReceiptsCosmosClientImpl.java | 23 ----------- .../http/RecoverFailedCartReceipt.java | 40 ++++++++----------- .../http/RecoverFailedCartReceiptMassive.java | 9 +++-- .../RecoverFailedCartReceiptScheduled.java | 8 ++-- .../impl/BizEventToReceiptServiceImpl.java | 19 +++------ .../service/impl/HelpdeskServiceImpl.java | 3 +- 6 files changed, 35 insertions(+), 67 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java index e0cd1095..2afb0bc6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java @@ -54,29 +54,6 @@ public static CartReceiptsCosmosClientImpl getInstance() { return instance; } - /** - * {@inheritDoc} - */ -// @Override -// public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { -// CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); -// -// CosmosContainer cosmosContainer = cosmosDatabase.getContainer(cartForReceiptContainerName); -// -// //Build query -// String query = "SELECT * FROM c WHERE c.eventId = '%s'".formatted(eventId); -// -// //Query the container -// CosmosPagedIterable queryResponse = cosmosContainer -// .queryItems(query, new CosmosQueryRequestOptions(), CartForReceipt.class); -// -// if (queryResponse.iterator().hasNext()) { -// return queryResponse.iterator().next(); -// } else { -// throw new CartNotFoundException("Document not found in the defined container"); -// } -// } - @Override public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { return getDocumentByFilter(cartForReceiptContainerName, "eventId", eventId, CartForReceipt.class) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index 68fad586..d619f004 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -57,23 +57,22 @@ public RecoverFailedCartReceipt() { /** * This function will be invoked when an Http Trigger occurs. - * The function is responsible for retrieving receipts that are in a FAILED, INSERTED and NOT_QUEUE_SENT state. - * TODO collection cart-for-receipts - * TODO https://github.com/pagopa/pagopa-receipt-pdf-generator/pull/170/changes#diff-5809f1fa1db9b483c00c90153ca5dc50b57437d89bb6f96b50f9447430ecd440 - * TODO vedere impl https://github.com/pagopa/pagopa-receipt-pdf-datastore/blob/b0403d8b614a97e02e2af713737b1054fa693a19/src/main/java/it/gov/pagopa/receipt/pdf/datastore/BizEventToReceipt.java#L143 + * The function is responsible for retrieving cart receipts that are in a FAILED, INSERTED and NOT_QUEUE_SENT state. * For a cart receipt, the function should: - * - try to retrieve the biz event -> if it doesn't find it, error - * - check that it's a valid biz: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error - * - check that it's not a cart biz: BizEventToReceiptUtils.getTotalNotice() == 1 -> if cart, error - * - check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error - * - recreate the receipt from the biz: BizEventToReceiptUtils.createReceipt() - * - if everything is OK, it updates the receipt on the cosmos. BizEventToReceiptService.handleSaveReceipt() - * - if everything is OK, send it to the queue BizEventToReceiptService.handleSendMessageToQueue() - *

+ *

    + *
  • try to retrieve cart receipt -> if it doesn't find it, error
  • + *
  • check cart receipt {@link CartStatusType}-> if it is no among the processable ones, error
  • + *
  • try to retrieve the list of biz events -> if it doesn't find it, error
  • + *
  • check that the biz events are valid: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error
  • + *
  • check that the biz events are valid: BizEventToReceiptUtils.getTotalNotice() == biz event list size -> if not, error
  • + *
  • recreate the receipt from the biz
  • + *
  • if everything is OK, it updates the receipt on the cosmos
  • + *
  • if everything is OK, send it to the queue
  • + *
* It recovers the receipt with the specified biz event id that has the following status: - * - ({@link ReceiptStatusType#INSERTED}) - * - ({@link ReceiptStatusType#FAILED}) - * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + * - ({@link CartStatusType#INSERTED}) + * - ({@link CartStatusType#FAILED}) + * - ({@link CartStatusType#NOT_QUEUE_SENT}) *

* It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. * @@ -97,13 +96,6 @@ public HttpResponseMessage run( ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - // TODO - // a partire dal cartId recuperare i biz-event - // validazione dei biz-event - // controllare che il numero di biz-event con il total notice presente nei biz - // se i biz sono validi procedere con la ricreazione della cart e l'invio in coda - // vedere regenerate sul recupero dell'id e sovrascrittura - CartForReceipt existingCart; try { existingCart = this.receiptCosmosService.getCart(cartId); @@ -128,10 +120,10 @@ public HttpResponseMessage run( cartForReceipt = this.helpdeskService.recoverCart(existingCart); } catch (BizEventUnprocessableEntityException e) { logger.error(e.getMessage(), e); - return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); } catch (BizEventBadRequestException e) { logger.error(e.getMessage(), e); - return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } catch (PDVTokenizerException | JsonProcessingException e) { logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java index 1240f5fe..c27e883d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java @@ -46,9 +46,12 @@ public RecoverFailedCartReceiptMassive() { * This function will be invoked when an Http Trigger occurs. *

* It recovers all the cart receipts with the specified status that has to be one of: - * - ({@link CartStatusType#INSERTED}) - * - ({@link CartStatusType#FAILED}) - * - ({@link CartStatusType#NOT_QUEUE_SENT}) + *

    + *
  • {@link CartStatusType#INSERTED}
  • + *
  • {@link CartStatusType#FAILED}
  • + *
  • {@link CartStatusType#NOT_QUEUE_SENT}
  • + *
+ *

* It creates the cart receipts and send on queue the event in order to proceed with the receipt generation. * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java index 0ef7e35d..0bd8ff26 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java @@ -42,9 +42,11 @@ public RecoverFailedCartReceiptScheduled() { * This function will be invoked periodically according to the specified schedule. *

* It recovers all the receipts with the following status: - * - ({@link CartStatusType#INSERTED}) - * - ({@link CartStatusType#FAILED}) - * - ({@link CartStatusType#NOT_QUEUE_SENT}) + *

    + *
  • {@link CartStatusType#INSERTED}
  • + *
  • {@link CartStatusType#FAILED}
  • + *
  • {@link CartStatusType#NOT_QUEUE_SENT}
  • + *
*

* It recovers cart receipts and send on queue the event in order to proceed with the receipt generation. */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index 0ede7ca6..bebe6495 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -263,19 +263,12 @@ public CartForReceipt buildCartFromBizEventList(List bizEventList) thr BizEvent bizEvent = bizEventList.get(0); String transactionId = bizEvent.getTransactionDetails().getTransaction().getTransactionId(); - BigDecimal amount = getCartAmount(bizEvent); - return CartForReceipt.builder() - .id(transactionId) - .eventId(transactionId) - .version("1") // this is the first version of this document - .payload(Payload.builder() - .payerFiscalCode(tokenizerPayerFiscalCode(bizEvent)) - .totalNotice(Integer.parseInt(bizEvent.getPaymentInfo().getTotalNotice())) - .totalAmount(!amount.equals(BigDecimal.ZERO) ? formatAmount(amount.toString()) : null) - .transactionCreationDate(getTransactionCreationDate(bizEvent)) - .cart(cartItems) - .build()) - .build(); + + CartForReceipt cartForReceipt = buildCart(bizEvent, transactionId, cartItems); + cartForReceipt.setStatus(CartStatusType.INSERTED); + cartForReceipt.setInserted_at(System.currentTimeMillis()); + + return cartForReceipt; } @Override diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index 2a773a57..5d58e28f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -55,9 +55,10 @@ public CartForReceipt recoverCart(CartForReceipt existingCart) validateCartBizEvents(bizEvents); CartForReceipt cart = this.bizEventToReceiptService.buildCartFromBizEventList(bizEvents); - cart.set_etag(existingCart.get_etag()); if (isCartStatusValid(cart)) { + // set _etag field with existing cart value in order to avoid error on document overwrite + cart.set_etag(existingCart.get_etag()); cart = this.bizEventToReceiptService.saveCartForReceiptWithoutRetry(cart); } From 90a5336c16c9cd8707bbfd0c221e6530961d9f38 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 12:43:44 +0100 Subject: [PATCH 26/38] [PAGOPA-3343] removed unused code --- .../TransactionEventToCartReceiptUtils.java | 435 ------------------ 1 file changed, 435 deletions(-) delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java deleted file mode 100644 index 3c1d70b9..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/TransactionEventToCartReceiptUtils.java +++ /dev/null @@ -1,435 +0,0 @@ -//package it.gov.pagopa.receipt.pdf.datastore.utils; -// -//import com.azure.cosmos.models.FeedResponse; -//import com.fasterxml.jackson.core.JsonProcessingException; -//import com.microsoft.azure.functions.ExecutionContext; -//import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -//import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; -//import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -//import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; -//import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; -//import it.gov.pagopa.receipt.pdf.datastore.entity.event.Transfer; -//import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; -//import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.UserType; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; -//import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -//import it.gov.pagopa.receipt.pdf.datastore.exception.*; -//import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; -//import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -//import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; -//import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -//import org.slf4j.Logger; -// -//import javax.validation.constraints.NotNull; -//import java.math.BigDecimal; -//import java.math.RoundingMode; -//import java.text.NumberFormat; -//import java.util.*; -//import java.util.regex.Matcher; -//import java.util.regex.Pattern; -// -//public class TransactionEventToCartReceiptUtils { -// -// private static final String REMITTANCE_INFORMATION_REGEX = "/TXT/(.*)"; -// private static final Boolean ECOMMERCE_FILTER_ENABLED = Boolean.parseBoolean(System.getenv().getOrDefault( -// "ECOMMERCE_FILTER_ENABLED", "true")); -// private static final List AUTHENTICATED_CHANNELS = Arrays.asList(System.getenv().getOrDefault( -// "AUTHENTICATED_CHANNELS", "IO,CHECKOUT,WISP,CHECKOUT_CART").split(",")); -// private static final List UNWANTED_REMITTANCE_INFO = Arrays.asList(System.getenv().getOrDefault( -// "UNWANTED_REMITTANCE_INFO", "pagamento multibeneficiario,pagamento bpay").split(",")); -// private static final String ECOMMERCE = "CHECKOUT"; -// -// -// private TransactionEventToCartReceiptUtils() { -// } -// -// public static CartForReceipt retrieveCartAndSendReceipt( -// String eventId, -// ExecutionContext context, -// @NotNull CartReceiptsCosmosClient cartReceiptCosmosClient, -// Receipt receipt, -// Logger logger -// ) throws BizEventNotFoundException, BizEventBadRequestException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException, CartNotFoundException { -// -// CartForReceipt cartItem = cartReceiptCosmosClient.getCartItem(eventId); -// -// // check total notice -// // Checks if the instance of Biz Event is in status DONE and contains all the required information to process in the receipt generation -// -// -// if (isBizEventInvalid(cartItem, context, logger)) { -// throw new BizEventBadRequestException("BizEvent not valid"); -// } -// -// if (!hasValidTotalNotice(cartItem, context, logger)) { -// throw new BizEventBadRequestException("BizEvent has not a valid total notice"); -// } -// -// if (receipt == null) { -// receipt = receiptCosmosService.getReceipt(eventId); -// } -// -// // check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error -// if (receipt != null && ( -// receipt.getStatus().equals(ReceiptStatusType.FAILED) || -// receipt.getStatus().equals(ReceiptStatusType.INSERTED) || -// receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) -// )) { -// // recreate the receipt from the biz -// receipt = createReceipt(cartItem, bizEventToReceiptService, logger); -// if (isReceiptStatusValid(receipt)) { -// bizEventToReceiptService.handleSaveReceipt(receipt); -// -// if (isReceiptStatusValid(receipt)) { -// bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(cartItem), receipt); -// -// return receipt; -// } -// } -// } -// return receipt; -// } -// -// /** -// * Creates a new instance of Receipt, using the tokenizer service to mask the PII, based on -// * the provided BizEvent -// * -// * @param bizEvent instance of BizEvent -// * @param service implementation of the BizEventToReceipt service to use -// * @return generated instance of Receipt -// */ -// public static Receipt createReceipt(BizEvent bizEvent, BizEventToReceiptService service, Logger logger) { -// Receipt receipt = new Receipt(); -// -// // Insert biz-event data into receipt -// receipt.setId(bizEvent.getId() + UUID.randomUUID()); -// receipt.setEventId(bizEvent.getId()); -// -// EventData eventData = new EventData(); -// -// try { -// service.tokenizeFiscalCodes(bizEvent, receipt, eventData); -// } catch (Exception e) { -// logger.error("Error tokenizing receipt with bizEventId {}", bizEvent.getId(), e); -// receipt.setStatus(ReceiptStatusType.FAILED); -// return receipt; -// } -// -// eventData.setTransactionCreationDate( -// service.getTransactionCreationDate(bizEvent)); -// BigDecimal amount = getAmount(bizEvent); -// eventData.setAmount(!amount.equals(BigDecimal.ZERO) ? formatAmount(amount.toString()) : null); -// -// CartItem item = new CartItem(); -// item.setPayeeName(bizEvent.getCreditor() != null ? bizEvent.getCreditor().getCompanyName() : null); -// item.setSubject(getItemSubject(bizEvent)); -// List cartItems = Collections.singletonList(item); -// eventData.setCart(cartItems); -// -// receipt.setEventData(eventData); -// receipt.setStatus(ReceiptStatusType.INSERTED); -// return receipt; -// } -// -// /** -// * Checks if the instance of Biz Event is in status DONE and contains all the required information to process -// * in the receipt generation -// * -// * @param bizEvent BizEvent to validate -// * @param context Function context -// * @param logger Function logger -// * @return boolean to determine if the proposed event is invalid -// */ -// public static boolean isBizEventInvalid(BizEvent bizEvent, ExecutionContext context, Logger logger) { -// -// if (bizEvent == null) { -// logger.debug("[{}] event is null", context.getFunctionName()); -// return true; -// } -// -// if (!BizEventStatusType.DONE.equals(bizEvent.getEventStatus())) { -// logger.debug("[{}] event with id {} discarded because in status {}", -// context.getFunctionName(), bizEvent.getId(), bizEvent.getEventStatus()); -// return true; -// } -// -// if (!hasValidFiscalCode(bizEvent)) { -// logger.debug("[{}] event with id {} discarded because debtor's and payer's identifiers are missing or not valid", -// context.getFunctionName(), bizEvent.getId()); -// return true; -// } -// -// if (Boolean.TRUE.equals(ECOMMERCE_FILTER_ENABLED) -// && bizEvent.getTransactionDetails() != null -// && bizEvent.getTransactionDetails().getInfo() != null -// && ECOMMERCE.equals(bizEvent.getTransactionDetails().getInfo().getClientId()) -// ) { -// logger.debug("[{}] event with id {} discarded because from e-commerce {}", -// context.getFunctionName(), bizEvent.getId(), bizEvent.getTransactionDetails().getInfo().getClientId()); -// return true; -// } -// -// if (!isCartMod1(bizEvent)) { -// logger.debug("[{}] event with id {} contain either an invalid amount value," + -// " or it is a legacy cart element", -// context.getFunctionName(), bizEvent.getId()); -// return true; -// } -// -// return false; -// } -// -// private static boolean hasValidTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { -// if (bizEvent.getPaymentInfo() != null) { -// String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); -// -// if (totalNotice != null) { -// int intTotalNotice; -// -// try { -// intTotalNotice = Integer.parseInt(totalNotice); -// -// } catch (NumberFormatException e) { -// logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", -// context.getFunctionName(), bizEvent.getId(), -// totalNotice, -// e); -// return false; -// } -// -// if (intTotalNotice > 1) { -// logger.error("[{}] event with id {} discarded because is part of a payment cart ({} total notice)", -// context.getFunctionName(), bizEvent.getId(), -// intTotalNotice); -// return false; -// } -// } -// } -// return true; -// } -// -// private static boolean hasValidFiscalCode(BizEvent bizEvent) { -// boolean isValidDebtor = false; -// boolean isValidPayer = false; -// -// if (bizEvent.getDebtor() != null && isValidFiscalCode(bizEvent.getDebtor().getEntityUniqueIdentifierValue())) { -// isValidDebtor = true; -// } -// if (isValidChannelOrigin(bizEvent)) { -// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getUser() != null && isValidFiscalCode(bizEvent.getTransactionDetails().getUser().getFiscalCode())) { -// isValidPayer = true; -// } -// if (bizEvent.getPayer() != null && isValidFiscalCode(bizEvent.getPayer().getEntityUniqueIdentifierValue())) { -// isValidPayer = true; -// } -// } -// return isValidDebtor || isValidPayer; -// } -// -// public static Integer getTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { -// if (bizEvent.getPaymentInfo() != null) { -// String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); -// -// if (totalNotice != null) { -// int intTotalNotice; -// -// try { -// intTotalNotice = Integer.parseInt(totalNotice); -// } catch (NumberFormatException e) { -// logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", -// context.getFunctionName(), bizEvent.getId(), -// totalNotice, -// e); -// throw e; -// } -// return intTotalNotice; -// } -// } -// return 1; -// } -// -// /** -// * Retrieve RemittanceInformation from BizEvent -// * -// * @param bizEvent BizEvent from which retrieve the data -// * @return the remittance information -// */ -// public static String getItemSubject(BizEvent bizEvent) { -// if ( -// bizEvent.getPaymentInfo() != null && -// bizEvent.getPaymentInfo().getRemittanceInformation() != null && -// !UNWANTED_REMITTANCE_INFO.contains(bizEvent.getPaymentInfo().getRemittanceInformation().toLowerCase()) -// ) { -// return bizEvent.getPaymentInfo().getRemittanceInformation(); -// } -// List transferList = bizEvent.getTransferList(); -// if (transferList != null && !transferList.isEmpty()) { -// double amount = 0; -// String remittanceInformation = null; -// for (Transfer transfer : transferList) { -// double transferAmount; -// try { -// transferAmount = Double.parseDouble(transfer.getAmount()); -// } catch (Exception ignored) { -// continue; -// } -// if (amount < transferAmount) { -// amount = transferAmount; -// remittanceInformation = transfer.getRemittanceInformation(); -// } -// } -// return formatRemittanceInformation(remittanceInformation); -// } -// return null; -// } -// -// public static BigDecimal getAmount(BizEvent bizEvent) { -// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getTransaction() != null) { -// return formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getGrandTotal()); -// } -// if (bizEvent.getPaymentInfo() != null && bizEvent.getPaymentInfo().getAmount() != null) { -// return new BigDecimal(bizEvent.getPaymentInfo().getAmount()); -// } -// return BigDecimal.ZERO; -// } -// -// public static BigDecimal getCartAmount(BizEvent bizEvent) { -// if (bizEvent.getTransactionDetails() != null && bizEvent.getTransactionDetails().getTransaction() != null) { -// return formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getGrandTotal()); -// } -// return BigDecimal.ZERO; -// } -// -// private static BigDecimal formatEuroCentAmount(long grandTotal) { -// BigDecimal amount = new BigDecimal(grandTotal); -// BigDecimal divider = new BigDecimal(100); -// return amount.divide(divider, 2, RoundingMode.UNNECESSARY); -// } -// -// public static String formatAmount(String value) { -// BigDecimal valueToFormat = new BigDecimal(value); -// NumberFormat numberFormat = NumberFormat.getInstance(Locale.ITALY); -// numberFormat.setMaximumFractionDigits(2); -// numberFormat.setMinimumFractionDigits(2); -// return numberFormat.format(valueToFormat); -// } -// -// private static String formatRemittanceInformation(String remittanceInformation) { -// if (remittanceInformation != null) { -// Pattern pattern = Pattern.compile(REMITTANCE_INFORMATION_REGEX); -// Matcher matcher = pattern.matcher(remittanceInformation); -// if (matcher.find()) { -// return matcher.group(1); -// } -// } -// return remittanceInformation; -// } -// -// public static boolean isReceiptStatusValid(Receipt receipt) { -// return receipt.getStatus() != ReceiptStatusType.FAILED && receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT; -// } -// -// public static boolean isCartStatusValid(CartForReceipt cartForReceipt) { -// return cartForReceipt.getStatus() != CartStatusType.FAILED && cartForReceipt.getStatus() != CartStatusType.NOT_QUEUE_SENT; -// } -// -// public static boolean isValidFiscalCode(String fiscalCode) { -// if (fiscalCode != null && !fiscalCode.isEmpty()) { -// Pattern patternCF = Pattern.compile("^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"); -// Pattern patternPIVA = Pattern.compile("/^[0-9]{11}$/"); -// -// return patternCF.matcher(fiscalCode).find() || patternPIVA.matcher(fiscalCode).find(); -// } -// -// return false; -// } -// -// /** -// * Method to check if the content comes from a legacy cart model (see https://pagopa.atlassian.net/browse/VAS-1167) -// * -// * @param bizEvent bizEvent to validate -// * @return flag to determine if it is a manageable cart, or otherwise, will return false if -// * it is considered a legacy cart content (not having a totalNotice field and having amount values != 0) -// */ -// public static boolean isCartMod1(BizEvent bizEvent) { -// if (bizEvent.getPaymentInfo() != null && bizEvent.getPaymentInfo().getTotalNotice() == null) { -// return bizEvent.getTransactionDetails() != null && -// new BigDecimal(bizEvent.getPaymentInfo().getAmount()).subtract( -// formatEuroCentAmount(bizEvent.getTransactionDetails().getTransaction().getAmount())) -// .floatValue() == 0; -// } -// return true; -// } -// -// public static boolean isValidChannelOrigin(BizEvent bizEvent) { -// if (bizEvent.getTransactionDetails() == null) { -// return false; -// } -// -// var transactionDetails = bizEvent.getTransactionDetails(); -// var transaction = transactionDetails.getTransaction(); -// var info = transactionDetails.getInfo(); -// var user = transactionDetails.getUser(); -// -// String origin = (transaction != null) ? transaction.getOrigin() : null; -// String clientId = (info != null) ? info.getClientId() : null; -// UserType userType = (user != null) ? user.getType() : null; -// -// boolean originMatches = origin != null && AUTHENTICATED_CHANNELS.contains(origin); -// boolean clientIdMatches = clientId != null && AUTHENTICATED_CHANNELS.contains(clientId); -// -// boolean isCheckoutOrigin = ECOMMERCE.equalsIgnoreCase(origin); -// boolean isCheckoutClientId = ECOMMERCE.equalsIgnoreCase(clientId); -// boolean isRegisteredUser = UserType.REGISTERED.equals(userType); -// -// if ((isCheckoutOrigin || isCheckoutClientId) && !isRegisteredUser) { -// return false; -// } -// -// return originMatches || clientIdMatches; -// } -// -//// public static MassiveRecoverResult massiveRecoverByStatus( -//// ExecutionContext context, -//// BizEventToReceiptService bizEventToReceiptService, -//// BizEventCosmosClient bizEventCosmosClient, -//// ReceiptCosmosService receiptCosmosService, -//// Logger logger, -//// ReceiptStatusType statusType) { -//// List receiptList = new ArrayList<>(); -//// int successCounter = 0; -//// int errorCounter = 0; -//// String continuationToken = null; -//// do { -//// Iterable> feedResponseIterator = -//// receiptCosmosService.getFailedReceiptByStatus(continuationToken, 100, statusType); -//// -//// for (FeedResponse page : feedResponseIterator) { -//// for (Receipt receipt : page.getResults()) { -//// try { -//// Receipt restored = retrieveBizAndSendReceipt(receipt.getEventId(), context, bizEventToReceiptService, -//// bizEventCosmosClient, receiptCosmosService, receipt, logger); -//// if (isReceiptStatusValid(restored)) { -//// receiptList.add(restored); -//// successCounter++; -//// } else { -//// errorCounter++; -//// } -//// } catch (Exception e) { -//// logger.error(e.getMessage(), e); -//// errorCounter++; -//// } -//// } -//// continuationToken = page.getContinuationToken(); -//// } -//// } while (continuationToken != null); -//// -//// return MassiveRecoverResult.builder() -//// .receiptList(receiptList) -//// .successCounter(successCounter) -//// .errorCounter(errorCounter) -//// .build(); -//// } -//} From 55282234ca117f7020c4c3d3bb54221b64043011 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 15:12:50 +0100 Subject: [PATCH 27/38] [PAGOPA-3343] refactor recover failed receipt function to reduce duplicate code and improve maintainability --- .../enumeration/ReceiptStatusType.java | 14 +- .../http/RecoverFailedCartReceipt.java | 20 +-- .../helpdesk/http/RecoverFailedReceipt.java | 161 +++++++++--------- .../http/RecoverFailedReceiptMassive.java | 106 ++++++------ .../RecoverFailedReceiptScheduled.java | 70 +++----- .../datastore/model/MassiveRecoverResult.java | 2 +- .../datastore/service/HelpdeskService.java | 28 +++ .../service/impl/HelpdeskServiceImpl.java | 101 +++++++++-- .../utils/BizEventToReceiptUtils.java | 148 +++------------- 9 files changed, 310 insertions(+), 340 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java index fe4dab63..1cad30d1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java @@ -1,5 +1,17 @@ package it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration; +import java.util.Set; + public enum ReceiptStatusType { - NOT_QUEUE_SENT, INSERTED, RETRY, GENERATED, SIGNED, FAILED, IO_NOTIFIED, IO_ERROR_TO_NOTIFY, IO_NOTIFIER_RETRY, UNABLE_TO_SEND, NOT_TO_NOTIFY, TO_REVIEW + NOT_QUEUE_SENT, INSERTED, RETRY, GENERATED, SIGNED, FAILED, IO_NOTIFIED, IO_ERROR_TO_NOTIFY, IO_NOTIFIER_RETRY, UNABLE_TO_SEND, NOT_TO_NOTIFY, TO_REVIEW; + + private static final Set DATASTORE_FAILED_STATUS = Set.of( + NOT_QUEUE_SENT, + INSERTED, + FAILED + ); + + public boolean isAFailedDatastoreStatus() { + return DATASTORE_FAILED_STATUS.contains(this); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index d619f004..abc1f924 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -30,6 +30,7 @@ import java.time.LocalDateTime; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; /** @@ -137,30 +138,15 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, errMsg); } - String responseMsg = String.format("Receipt with eventId %s recovered", cartId); + String responseMsg = String.format("Cart receipt with id %s recovered", cartId); return request.createResponseBuilder(HttpStatus.OK) .body(responseMsg) .build(); } - public boolean isCartStatusNotProcessable(CartStatusType status) { + private boolean isCartStatusNotProcessable(CartStatusType status) { return !CartStatusType.INSERTED.equals(status) && !CartStatusType.NOT_QUEUE_SENT.equals(status) && !CartStatusType.FAILED.equals(status); } - - private HttpResponseMessage buildErrorResponse( - HttpRequestMessage> request, - HttpStatus httpStatus, - String errMsg - ) { - return request - .createResponseBuilder(httpStatus) - .body(ProblemJson.builder() - .title(httpStatus.name()) - .detail(errMsg) - .status(httpStatus.value()) - .build()) - .build(); - } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index a1c395e7..c929c6e0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -1,28 +1,35 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.azure.functions.*; -import com.microsoft.azure.functions.annotation.*; -import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDateTime; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isReceiptStatusValid; + /** * Azure Functions with Azure Http trigger. */ @@ -30,47 +37,46 @@ public class RecoverFailedReceipt { private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceipt.class); - private final BizEventToReceiptService bizEventToReceiptService; - private final BizEventCosmosClient bizEventCosmosClient; private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; - public RecoverFailedReceipt(){ - this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); + public RecoverFailedReceipt() { this.receiptCosmosService = new ReceiptCosmosServiceImpl(); - this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverFailedReceipt(BizEventToReceiptService bizEventToReceiptService, - BizEventCosmosClient bizEventCosmosClient, - ReceiptCosmosService receiptCosmosService){ - this.bizEventToReceiptService = bizEventToReceiptService; - this.bizEventCosmosClient = bizEventCosmosClient; + RecoverFailedReceipt(ReceiptCosmosService receiptCosmosService, HelpdeskService helpdeskService) { this.receiptCosmosService = receiptCosmosService; + this.helpdeskService = helpdeskService; } /** * This function will be invoked when an Http Trigger occurs. * The function is responsible for retrieving receipts that are in a FAILED, INSERTED and NOT_QUEUE_SENT state. * For a single receipt, the function should: - * - try to retrieve the biz event -> if it doesn't find it, error - * - check that it's a valid biz: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error - * - check that it's not a cart biz: BizEventToReceiptUtils.getTotalNotice() == 1 -> if cart, error - * - check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error - * - recreate the receipt from the biz: BizEventToReceiptUtils.createReceipt() - * - if everything is OK, it updates the receipt on the cosmos. BizEventToReceiptService.handleSaveReceipt() - * - if everything is OK, send it to the queue BizEventToReceiptService.handleSendMessageToQueue() + *

    + *
  • try to retrieve the biz event -> if it doesn't find it, error
  • + *
  • check that it's a valid biz: BizEventToReceiptUtils.isBizEventInvalid() -> if invalid, error
  • + *
  • check that it's not a cart biz: BizEventToReceiptUtils.getTotalNotice() == 1 -> if cart, error
  • + *
  • check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error
  • + *
  • recreate the receipt from the biz: BizEventToReceiptUtils.createReceipt()
  • + *
  • if everything is OK, it updates the receipt on the cosmos. BizEventToReceiptService.handleSaveReceipt()
  • + *
  • if everything is OK, send it to the queue BizEventToReceiptService.handleSendMessageToQueue()
  • + *
*

* It recovers the receipt with the specified biz event id that has the following status: - * - ({@link ReceiptStatusType#INSERTED}) - * - ({@link ReceiptStatusType#FAILED}) - * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

    + *
  • {@link ReceiptStatusType#INSERTED}
  • + *
  • {@link ReceiptStatusType#FAILED}
  • + *
  • {@link ReceiptStatusType#NOT_QUEUE_SENT}
  • + *
*

* It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. * * @return response with {@link HttpStatus#OK} if the operation succeeded */ @FunctionName("RecoverFailedReceipt") - public HttpResponseMessage run ( + public HttpResponseMessage run( @HttpTrigger(name = "RecoverFailedReceiptTrigger", methods = {HttpMethod.POST}, route = "receipts/{event-id}/recover-failed", @@ -83,60 +89,57 @@ public HttpResponseMessage run ( containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding documentdb, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + Receipt existingReceipt; try { - Receipt receipt = BizEventToReceiptUtils.retrieveBizAndSendReceipt(eventId, context, this.bizEventToReceiptService, - this.bizEventCosmosClient, this.receiptCosmosService, null, logger); + existingReceipt = this.receiptCosmosService.getReceipt(eventId); + } catch (ReceiptNotFoundException e) { + String errMsg = "Receipt not found with the provided event id"; + logger.error(errMsg, e); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); + } - documentdb.setValue(receipt); - if (BizEventToReceiptUtils.isReceiptStatusValid(receipt)) { - String responseMsg = String.format("Receipt with eventId %s recovered", eventId); - return request.createResponseBuilder(HttpStatus.OK) - .body(responseMsg) - .build(); - } else { - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(receipt.getReasonErr().getMessage()) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); - } - } catch (BizEventNotFoundException | BizEventBadRequestException exception) { - String msg = String.format("Unable to retrieve the biz-event with id %s", eventId); - logger.error(msg, exception); - return request - .createResponseBuilder(exception.getHttpStatus()) - .body(ProblemJson.builder() - .title(exception.getHttpStatus().name()) - .detail(msg) - .status(exception.getHttpStatus().value()) - .build()) - .build(); - } catch (PDVTokenizerException | JsonProcessingException e) { + if (isReceiptStatusNotProcessable(existingReceipt.getStatus())) { + String errMsg = String.format( + "The provided receipt is in status %s, which is not among the processable " + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", + existingReceipt.getStatus() + ); + logger.error(errMsg); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); + } + + Receipt receipt; + try { + receipt = this.helpdeskService.recoverReceipt(existingReceipt); + } catch (BizEventUnprocessableEntityException e) { logger.error(e.getMessage(), e); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(e.getMessage()) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); - } catch (ReceiptNotFoundException e) { + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); + } catch (BizEventBadRequestException | BizEventNotFoundException e) { logger.error(e.getMessage(), e); - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } + + if (!isReceiptStatusValid(receipt)) { + String errMsg = String.format("Recover failed for receipt with id %s. Reason: %s", + eventId, receipt.getReasonErr().getMessage()); + logger.error(errMsg); + documentdb.setValue(receipt); + return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, errMsg); } + + String responseMsg = String.format("Receipt with eventId %s recovered", eventId); + return request.createResponseBuilder(HttpStatus.OK) + .body(responseMsg) + .build(); + } + + private boolean isReceiptStatusNotProcessable(ReceiptStatusType status) { + return !ReceiptStatusType.INSERTED.equals(status) + && !ReceiptStatusType.NOT_QUEUE_SENT.equals(status) + && !ReceiptStatusType.FAILED.equals(status); } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index d4ed5d4f..cb579a41 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -1,31 +1,29 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.CosmosDBOutput; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; - -import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDateTime; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.massiveRecoverByStatus; - /** * Azure Functions with Azure Http trigger. @@ -34,31 +32,25 @@ public class RecoverFailedReceiptMassive { private final Logger logger = LoggerFactory.getLogger(RecoverFailedReceiptMassive.class); - private final BizEventToReceiptService bizEventToReceiptService; - private final BizEventCosmosClient bizEventCosmosClient; - private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; public RecoverFailedReceiptMassive() { - this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); - this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverFailedReceiptMassive(BizEventToReceiptService bizEventToReceiptService, - BizEventCosmosClient bizEventCosmosClient, - ReceiptCosmosService receiptCosmosService) { - this.bizEventToReceiptService = bizEventToReceiptService; - this.bizEventCosmosClient = bizEventCosmosClient; - this.receiptCosmosService = receiptCosmosService; + RecoverFailedReceiptMassive(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; } /** * This function will be invoked when an Http Trigger occurs. *

* It recovers all the receipts with the specified status that has to be one of: - * - ({@link ReceiptStatusType#INSERTED}) - * - ({@link ReceiptStatusType#FAILED}) - * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

    + *
  • {@link ReceiptStatusType#INSERTED}
  • + *
  • {@link ReceiptStatusType#FAILED}
  • + *
  • {@link ReceiptStatusType#NOT_QUEUE_SENT}
  • + *
*

* It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. * @@ -77,56 +69,34 @@ public HttpResponseMessage run( containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding> documentdb, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); // Get named parameter - String status = request.getQueryParameters().get("status"); - if (status == null) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a status to recover") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - - ReceiptStatusType statusType; + String statusParam = request.getQueryParameters().get("status"); + ReceiptStatusType status; try { - statusType = ReceiptStatusType.valueOf(status); - } catch (IllegalArgumentException e) { + status = validateStatusParam(statusParam); + } catch (InvalidParameterException e) { return request .createResponseBuilder(HttpStatus.BAD_REQUEST) .body(ProblemJson.builder() .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid status to recover") + .detail(e.getMessage()) .status(HttpStatus.BAD_REQUEST.value()) .build()) .build(); } - MassiveRecoverResult recoverResult; - try { - recoverResult = massiveRecoverByStatus( - context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); - } catch (NoSuchElementException e) { - logger.error("[{}] Unexpected error during recover of failed receipt", context.getFunctionName(), e); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(e.getMessage()) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); - } + MassiveRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); int successCounter = recoverResult.getSuccessCounter(); int errorCounter = recoverResult.getErrorCounter(); if (errorCounter > 0) { + documentdb.setValue(recoverResult.getFailedReceiptList()); + String msg = String.format("Recovered %s receipts but %s encountered an error.", successCounter, errorCounter); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) @@ -142,4 +112,24 @@ public HttpResponseMessage run( .body(responseMsg) .build(); } + + private ReceiptStatusType validateStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + ReceiptStatusType status; + try { + status = ReceiptStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + if (!status.isAFailedDatastoreStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + throw new InvalidParameterException(message); + } + return status; + } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java index 4ca1368c..5dd06e7f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java @@ -5,23 +5,18 @@ import com.microsoft.azure.functions.annotation.CosmosDBOutput; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.TimerTrigger; - -import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.LocalDateTime; -import java.util.*; - -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.massiveRecoverByStatus; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** @@ -33,22 +28,14 @@ public class RecoverFailedReceiptScheduled { private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("FAILED_AUTORECOVER_ENABLED", "true")); - private final BizEventToReceiptService bizEventToReceiptService; - private final BizEventCosmosClient bizEventCosmosClient; - private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; public RecoverFailedReceiptScheduled() { - this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); - this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverFailedReceiptScheduled(BizEventToReceiptService bizEventToReceiptService, - BizEventCosmosClient bizEventCosmosClient, - ReceiptCosmosService receiptCosmosService) { - this.bizEventToReceiptService = bizEventToReceiptService; - this.bizEventCosmosClient = bizEventCosmosClient; - this.receiptCosmosService = receiptCosmosService; + RecoverFailedReceiptScheduled(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; } /** @@ -74,30 +61,27 @@ public void run( ) { if (isEnabled) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - recover(context, ReceiptStatusType.INSERTED); - recover(context, ReceiptStatusType.FAILED); - recover(context, ReceiptStatusType.NOT_QUEUE_SENT); + + List failedReceipts = new ArrayList<>(); + failedReceipts.addAll(recover(ReceiptStatusType.INSERTED)); + failedReceipts.addAll(recover(ReceiptStatusType.FAILED)); + failedReceipts.addAll(recover(ReceiptStatusType.NOT_QUEUE_SENT)); + + documentdb.setValue(failedReceipts); } } - private List recover(ExecutionContext context, ReceiptStatusType statusType) { - try { - MassiveRecoverResult recoverResult = massiveRecoverByStatus( - context, bizEventToReceiptService, bizEventCosmosClient, receiptCosmosService, logger, statusType); - if (recoverResult.getErrorCounter() > 0) { - logger.error("[{}] Error recovering {} failed receipts for status {}", - context.getFunctionName(), recoverResult.getErrorCounter(), statusType); - } - - List idList = recoverResult.getReceiptList().parallelStream().map(Receipt::getId).toList(); - - logger.info("[{}] Recovered {} receipts for status {} with ids: {}", - context.getFunctionName(), idList.size(), statusType, idList); - return recoverResult.getReceiptList(); - } catch (NoSuchElementException e) { - logger.error("[{}] Unexpected error during recover of failed receipt for status {}", - context.getFunctionName(), statusType, e); - return Collections.emptyList(); + private List recover(ReceiptStatusType status) { + MassiveRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); + + int successCounter = recoverResult.getSuccessCounter(); + int errorCounter = recoverResult.getErrorCounter(); + + if (errorCounter > 0) { + logger.error("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); + return recoverResult.getFailedReceiptList(); } + + return Collections.emptyList(); } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java index b87b8e78..68faa8c4 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java @@ -10,7 +10,7 @@ @Builder public class MassiveRecoverResult { - private List receiptList; + private List failedReceiptList; private int errorCounter; private int successCounter; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java index 85549de2..fba9cfc0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java @@ -3,16 +3,36 @@ import com.fasterxml.jackson.core.JsonProcessingException; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; /** * Service that hold receipt helpdesk logic */ public interface HelpdeskService { + /** + * Recover the specified receipt. + *

+ * It retrieves the related biz event, rebuild {@link Receipt} model, saves it and sends message on queue + * for PDF generation + *

+ * + * @param existingReceipt the receipt to recover + * @return the recover receipt + * @throws BizEventUnprocessableEntityException in case the biz event does not have the correct total notice + * @throws BizEventBadRequestException in case the biz event is invalid for receipt generation + * @throws BizEventNotFoundException in case no biz event is found for the specified receipt + */ + Receipt recoverReceipt(Receipt existingReceipt) + throws BizEventUnprocessableEntityException, BizEventBadRequestException, BizEventNotFoundException; + /** * Recover the specified cart. *

@@ -30,6 +50,14 @@ public interface HelpdeskService { CartForReceipt recoverCart(CartForReceipt existingCart) throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException; + /** + * Massive recover all receipt with the specified status {@link ReceiptStatusType} + * + * @param status the status to be recovered + * @return the recover result + */ + MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status); + /** * Massive recover all cart with the specified status {@link CartStatusType} * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index 5d58e28f..d3bb54f2 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -7,10 +7,14 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; @@ -19,9 +23,12 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.createReceipt; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isReceiptStatusValid; public class HelpdeskServiceImpl implements HelpdeskService { @@ -47,6 +54,26 @@ public HelpdeskServiceImpl( this.bizEventCosmosClient = bizEventCosmosClient; } + @Override + public Receipt recoverReceipt(Receipt existingReceipt) + throws BizEventUnprocessableEntityException, BizEventBadRequestException, BizEventNotFoundException { + // retrieve biz-event with the specified cartId + + BizEvent bizEvent = this.bizEventCosmosClient.getBizEventDocument(existingReceipt.getEventId()); + validateBizEvent(bizEvent, 1); + + Receipt receipt = createReceipt(bizEvent, bizEventToReceiptService, logger); + + if (isReceiptStatusValid(receipt)) { + this.bizEventToReceiptService.handleSaveReceipt(receipt); + } + + if (isReceiptStatusValid(receipt)) { + this.bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(bizEvent), receipt); + } + return receipt; + } + @Override public CartForReceipt recoverCart(CartForReceipt existingCart) throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException { @@ -68,6 +95,43 @@ public CartForReceipt recoverCart(CartForReceipt existingCart) return cart; } + @Override + public MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status) { + List failedReceipts = new ArrayList<>(); + int successCounter = 0; + int errorCounter = 0; + String continuationToken = null; + do { + Iterable> feedResponseIterator = + this.receiptCosmosService.getFailedReceiptByStatus(continuationToken, 100, status); + + for (FeedResponse page : feedResponseIterator) { + for (Receipt receipt : page.getResults()) { + try { + Receipt restored = recoverReceipt(receipt); + + if (isReceiptStatusValid(restored)) { + successCounter++; + } else { + failedReceipts.add(restored); + errorCounter++; + } + } catch (Exception e) { + logger.error("Recover for receipt {} failed", receipt.getEventId(), e); + errorCounter++; + } + } + continuationToken = page.getContinuationToken(); + } + } while (continuationToken != null); + + return MassiveRecoverResult.builder() + .failedReceiptList(failedReceipts) + .successCounter(successCounter) + .errorCounter(errorCounter) + .build(); + } + @Override public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { List failedCart = new ArrayList<>(); @@ -83,11 +147,11 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { try { CartForReceipt recoverCart = recoverCart(cart); - if (!isCartStatusValid(recoverCart)) { + if (isCartStatusValid(recoverCart)) { + successCounter++; + } else { failedCart.add(recoverCart); errorCounter++; - } else { - successCounter++; } } catch (Exception e) { logger.error("Recover for cart {} failed", cart.getEventId(), e); @@ -111,20 +175,25 @@ private void validateCartBizEvents(List bizEvents) throws BizEventBadR } for (BizEvent bizEvent : bizEvents) { - // biz-event validation - BizEventToReceiptUtils.BizEventValidityCheck bizEventValidityCheck = BizEventToReceiptUtils.isBizEventInvalid(bizEvent); - if (bizEventValidityCheck.invalid()) { - String errMsg = String.format("Biz-event with id %s is invalid: %s", bizEvent.getId(), bizEventValidityCheck.error()); - throw new BizEventBadRequestException(errMsg); - } + validateBizEvent(bizEvent, bizEvents.size()); + } + } - // total notice check - Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, logger); - if (totalNotice != bizEvents.size()) { - String errMsg = String.format("The expected total notice %s does not match the number of biz events %s", - totalNotice, bizEvents.size()); - throw new BizEventUnprocessableEntityException(errMsg); - } + private void validateBizEvent( + BizEvent bizEvent, int expectedTotalNotice + ) throws BizEventBadRequestException, BizEventUnprocessableEntityException { + BizEventToReceiptUtils.BizEventValidityCheck bizEventValidityCheck = BizEventToReceiptUtils.isBizEventInvalid(bizEvent); + if (bizEventValidityCheck.invalid()) { + String errMsg = String.format("Biz-event with id %s is invalid: %s", bizEvent.getId(), bizEventValidityCheck.error()); + throw new BizEventBadRequestException(errMsg); + } + + // total notice check + Integer totalNotice = BizEventToReceiptUtils.getTotalNotice(bizEvent, logger); + if (totalNotice != expectedTotalNotice) { + String errMsg = String.format("The expected total notice %s does not match the number of biz events %s", + totalNotice, expectedTotalNotice); + throw new BizEventUnprocessableEntityException(errMsg); } } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 1ecc2042..32e8007d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -1,9 +1,8 @@ package it.gov.pagopa.receipt.pdf.datastore.utils; -import com.azure.cosmos.models.FeedResponse; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.azure.functions.ExecutionContext; -import it.gov.pagopa.receipt.pdf.datastore.client.BizEventCosmosClient; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; @@ -14,20 +13,20 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; -import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import lombok.Builder; import org.slf4j.Logger; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,51 +45,6 @@ public class BizEventToReceiptUtils { private BizEventToReceiptUtils() { } - public static Receipt retrieveBizAndSendReceipt( - String eventId, - ExecutionContext context, - BizEventToReceiptService bizEventToReceiptService, - BizEventCosmosClient bizEventCosmosClient, - ReceiptCosmosService receiptCosmosService, - Receipt receipt, - Logger logger - ) throws BizEventNotFoundException, BizEventBadRequestException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { - - BizEvent bizEvent = bizEventCosmosClient.getBizEventDocument(eventId); - - if (isBizEventInvalid(bizEvent, context, logger)) { - throw new BizEventBadRequestException("BizEvent not valid"); - } - - if (!hasValidTotalNotice(bizEvent, context, logger)) { - throw new BizEventBadRequestException("BizEvent has not a valid total notice"); - } - - if (receipt == null) { - receipt = receiptCosmosService.getReceipt(eventId); - } - - // check that the receipt is in one of the 3 manageable states: FAILED, INSERTED, and NOT_QUEUE_SENT -> if not, error - if (receipt != null && ( - receipt.getStatus().equals(ReceiptStatusType.FAILED) || - receipt.getStatus().equals(ReceiptStatusType.INSERTED) || - receipt.getStatus().equals(ReceiptStatusType.NOT_QUEUE_SENT) - )) { - // recreate the receipt from the biz - receipt = createReceipt(bizEvent, bizEventToReceiptService, logger); - if (isReceiptStatusValid(receipt)) { - bizEventToReceiptService.handleSaveReceipt(receipt); - - if (isReceiptStatusValid(receipt)) { - bizEventToReceiptService.handleSendMessageToQueue(Collections.singletonList(bizEvent), receipt); - - return receipt; - } - } - } - return receipt; - } - /** * Creates a new instance of Receipt, using the tokenizer service to mask the PII, based on * the provided BizEvent @@ -187,35 +141,6 @@ public static BizEventValidityCheck isBizEventInvalid(BizEvent bizEvent) { public record BizEventValidityCheck(boolean invalid, String error) { } - private static boolean hasValidTotalNotice(BizEvent bizEvent, ExecutionContext context, Logger logger) { - if (bizEvent.getPaymentInfo() != null) { - String totalNotice = bizEvent.getPaymentInfo().getTotalNotice(); - - if (totalNotice != null) { - int intTotalNotice; - - try { - intTotalNotice = Integer.parseInt(totalNotice); - - } catch (NumberFormatException e) { - logger.error("[{}] event with id {} discarded because has an invalid total notice value: {}", - context.getFunctionName(), bizEvent.getId(), - totalNotice, - e); - return false; - } - - if (intTotalNotice > 1) { - logger.error("[{}] event with id {} discarded because is part of a payment cart ({} total notice)", - context.getFunctionName(), bizEvent.getId(), - intTotalNotice); - return false; - } - } - } - return true; - } - private static boolean hasValidFiscalCode(BizEvent bizEvent) { boolean isValidDebtor = false; boolean isValidPayer = false; @@ -344,7 +269,7 @@ public static boolean isCartStatusValid(CartForReceipt cartForReceipt) { public static boolean isValidFiscalCode(String fiscalCode) { if (fiscalCode != null && !fiscalCode.isEmpty()) { Pattern patternCF = Pattern.compile("^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"); - Pattern patternPIVA = Pattern.compile("/^[0-9]{11}$/"); + Pattern patternPIVA = Pattern.compile("^[0-9]{11}$"); return patternCF.matcher(fiscalCode).find() || patternPIVA.matcher(fiscalCode).find(); } @@ -397,45 +322,18 @@ public static boolean isValidChannelOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } - public static MassiveRecoverResult massiveRecoverByStatus( - ExecutionContext context, - BizEventToReceiptService bizEventToReceiptService, - BizEventCosmosClient bizEventCosmosClient, - ReceiptCosmosService receiptCosmosService, - Logger logger, - ReceiptStatusType statusType) { - List receiptList = new ArrayList<>(); - int successCounter = 0; - int errorCounter = 0; - String continuationToken = null; - do { - Iterable> feedResponseIterator = - receiptCosmosService.getFailedReceiptByStatus(continuationToken, 100, statusType); - - for (FeedResponse page : feedResponseIterator) { - for (Receipt receipt : page.getResults()) { - try { - Receipt restored = retrieveBizAndSendReceipt(receipt.getEventId(), context, bizEventToReceiptService, - bizEventCosmosClient, receiptCosmosService, receipt, logger); - if (isReceiptStatusValid(restored)) { - receiptList.add(restored); - successCounter++; - } else { - errorCounter++; - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - errorCounter++; - } - } - continuationToken = page.getContinuationToken(); - } - } while (continuationToken != null); - - return MassiveRecoverResult.builder() - .receiptList(receiptList) - .successCounter(successCounter) - .errorCounter(errorCounter) + public static HttpResponseMessage buildErrorResponse( + HttpRequestMessage> request, + HttpStatus httpStatus, + String errMsg + ) { + return request + .createResponseBuilder(httpStatus) + .body(ProblemJson.builder() + .title(httpStatus.name()) + .detail(errMsg) + .status(httpStatus.value()) + .build()) .build(); } } From 8103626dc6e4f2e8e678e25c9633da3f8f8e4009 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 15:50:46 +0100 Subject: [PATCH 28/38] [PAGOPA-3343] refactor recover not notified receipt function to reduce duplicate code and improve maintainability --- .../enumeration/ReceiptStatusType.java | 9 ++ .../http/RecoverFailedCartReceipt.java | 4 +- .../helpdesk/http/RecoverFailedReceipt.java | 2 +- .../http/RecoverNotNotifiedReceipt.java | 69 +++++++-------- .../RecoverNotNotifiedReceiptMassive.java | 84 +++++++++---------- .../RecoverNotNotifiedReceiptScheduled.java | 24 ++---- .../datastore/service/HelpdeskService.java | 27 +++++- .../service/impl/HelpdeskServiceImpl.java | 41 ++++++++- .../utils/RecoverNotNotifiedReceiptUtils.java | 46 ---------- 9 files changed, 152 insertions(+), 154 deletions(-) delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java index 1cad30d1..47b03aba 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/enumeration/ReceiptStatusType.java @@ -11,7 +11,16 @@ public enum ReceiptStatusType { FAILED ); + private static final Set NOTIFICATION_FAILED_STATUS = Set.of( + GENERATED, + IO_ERROR_TO_NOTIFY + ); + public boolean isAFailedDatastoreStatus() { return DATASTORE_FAILED_STATUS.contains(this); } + + public boolean isANotificationFailedStatus() { + return NOTIFICATION_FAILED_STATUS.contains(this); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index abc1f924..e57e174d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -14,12 +14,10 @@ import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; @@ -118,7 +116,7 @@ public HttpResponseMessage run( CartForReceipt cartForReceipt; try { - cartForReceipt = this.helpdeskService.recoverCart(existingCart); + cartForReceipt = this.helpdeskService.recoverFailedCart(existingCart); } catch (BizEventUnprocessableEntityException e) { logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index c929c6e0..3b620f66 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -114,7 +114,7 @@ public HttpResponseMessage run( Receipt receipt; try { - receipt = this.helpdeskService.recoverReceipt(existingReceipt); + receipt = this.helpdeskService.recoverFailedReceipt(existingReceipt); } catch (BizEventUnprocessableEntityException e) { logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index c1b153a3..0fb6b9d7 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -1,12 +1,22 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; -import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,8 +26,7 @@ import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.restoreReceipt; - +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; /** * Azure Functions with HTTP Trigger. @@ -27,13 +36,16 @@ public class RecoverNotNotifiedReceipt { private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceipt.class); private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; public RecoverNotNotifiedReceipt() { this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverNotNotifiedReceipt(ReceiptCosmosService receiptCosmosService) { + RecoverNotNotifiedReceipt(ReceiptCosmosService receiptCosmosService, HelpdeskService helpdeskService) { this.receiptCosmosService = receiptCosmosService; + this.helpdeskService = helpdeskService; } /** @@ -61,50 +73,29 @@ public HttpResponseMessage run( containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding> documentReceipts, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - if (eventId == null || eventId.isBlank()) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid biz-event id") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - Receipt receipt; try { receipt = this.receiptCosmosService.getReceipt(eventId); } catch (ReceiptNotFoundException e) { - String responseMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); - logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); - return request - .createResponseBuilder(HttpStatus.NOT_FOUND) - .body(ProblemJson.builder() - .title(HttpStatus.NOT_FOUND.name()) - .detail(responseMsg) - .status(HttpStatus.NOT_FOUND.value()) - .build()) - .build(); + String errMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); + logger.error("[{}] {}", context.getFunctionName(), errMsg, e); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } - if (receipt != null && !receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !receipt.getStatus().equals(ReceiptStatusType.GENERATED)) { - String responseMsg = String.format("The requested receipt with eventId %s is not in the expected status", + if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) + && !receipt.getStatus().equals(ReceiptStatusType.GENERATED) + ) { + String errMsg = String.format("The requested receipt with eventId %s is not in the expected status", receipt.getEventId()); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(responseMsg) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); + logger.error(errMsg); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } - Receipt restoredReceipt = restoreReceipt(receipt); + Receipt restoredReceipt = this.helpdeskService.recoverNoNotifiedReceipt(receipt); documentReceipts.setValue(Collections.singletonList(restoredReceipt)); String responseMsg = String.format("Receipt with id %s and eventId %s restored in status %s with success", diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index aecb1be0..d5946769 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -1,16 +1,21 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.CosmosDBOutput; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; - import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,8 +23,6 @@ import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; - /** * Azure Functions with HTTP Trigger. @@ -28,14 +31,14 @@ public class RecoverNotNotifiedReceiptMassive { private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptMassive.class); - private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; public RecoverNotNotifiedReceiptMassive() { - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverNotNotifiedReceiptMassive(ReceiptCosmosService receiptCosmosService) { - this.receiptCosmosService = receiptCosmosService; + RecoverNotNotifiedReceiptMassive(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; } /** @@ -62,56 +65,53 @@ public HttpResponseMessage run( containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding> documentReceipts, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); // Get named parameter - String status = request.getQueryParameters().get("status"); - if (status == null) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a status to recover") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - - ReceiptStatusType statusType; + String statusParam = request.getQueryParameters().get("status"); + ReceiptStatusType status; try { - statusType = ReceiptStatusType.valueOf(status); - } catch (IllegalArgumentException e) { + status = validateStatusParam(statusParam); + } catch (InvalidParameterException e) { return request .createResponseBuilder(HttpStatus.BAD_REQUEST) .body(ProblemJson.builder() .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid status to recover") + .detail(e.getMessage()) .status(HttpStatus.BAD_REQUEST.value()) .build()) .build(); } - if (!statusType.equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) && !statusType.equals(ReceiptStatusType.GENERATED)) { - String responseMsg = String.format("The requested status to recover %s is not one of the expected status", statusType); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(responseMsg) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); - } - - List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); + List receiptList = this.helpdeskService.massiveRecoverNoNotified(status); if (receiptList.isEmpty()) { - return request.createResponseBuilder(HttpStatus.OK).body("No receipts restored").build(); + return request.createResponseBuilder(HttpStatus.OK).body("No receipts to be recovered").build(); } documentReceipts.setValue(receiptList); - String msg = String.format("Restored %s receipt with success", receiptList.size()); + String msg = String.format("Recovered %s receipt with success", receiptList.size()); return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); } + private ReceiptStatusType validateStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + ReceiptStatusType status; + try { + status = ReceiptStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + if (!status.isANotificationFailedStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + throw new InvalidParameterException(message); + } + return status; + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java index 89fd2a54..1b8c3033 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduled.java @@ -5,11 +5,10 @@ import com.microsoft.azure.functions.annotation.CosmosDBOutput; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.TimerTrigger; - import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,8 +16,6 @@ import java.util.ArrayList; import java.util.List; -import static it.gov.pagopa.receipt.pdf.datastore.utils.RecoverNotNotifiedReceiptUtils.receiptMassiveRestore; - public class RecoverNotNotifiedReceiptScheduled { @@ -26,14 +23,14 @@ public class RecoverNotNotifiedReceiptScheduled { private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceiptScheduled.class); - private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; public RecoverNotNotifiedReceiptScheduled() { - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverNotNotifiedReceiptScheduled(ReceiptCosmosService receiptCosmosService) { - this.receiptCosmosService = receiptCosmosService; + RecoverNotNotifiedReceiptScheduled(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; } /** @@ -44,7 +41,6 @@ public RecoverNotNotifiedReceiptScheduled() { * It recovers the receipt with failed notification ({@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}) or notification * not triggered ({@link ReceiptStatusType#GENERATED} by clearing the errors and update the status to the * previous step ({@link ReceiptStatusType#GENERATED}). - * */ @FunctionName("RecoverNotNotifiedTimerTriggerProcessor") public void processRecoverNotNotifiedScheduledTrigger( @@ -59,10 +55,9 @@ public void processRecoverNotNotifiedScheduledTrigger( containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding> documentReceipts, - final ExecutionContext context) { - + final ExecutionContext context + ) { if (isEnabled) { - logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); List receiptList = new ArrayList<>(); @@ -74,12 +69,11 @@ public void processRecoverNotNotifiedScheduledTrigger( } private List process(ExecutionContext context, ReceiptStatusType statusType) { - List receiptList = receiptMassiveRestore(statusType, receiptCosmosService); + List receiptList = this.helpdeskService.massiveRecoverNoNotified(statusType); List idList = receiptList.parallelStream().map(Receipt::getId).toList(); logger.info("[{}] Recovered {} receipts for status {} with ids: {}", context.getFunctionName(), receiptList.size(), statusType, idList); return receiptList; } - } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java index fba9cfc0..12aed89f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java @@ -12,6 +12,8 @@ import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import java.util.List; + /** * Service that hold receipt helpdesk logic */ @@ -30,7 +32,7 @@ public interface HelpdeskService { * @throws BizEventBadRequestException in case the biz event is invalid for receipt generation * @throws BizEventNotFoundException in case no biz event is found for the specified receipt */ - Receipt recoverReceipt(Receipt existingReceipt) + Receipt recoverFailedReceipt(Receipt existingReceipt) throws BizEventUnprocessableEntityException, BizEventBadRequestException, BizEventNotFoundException; /** @@ -47,11 +49,20 @@ Receipt recoverReceipt(Receipt existingReceipt) * @throws PDVTokenizerException in case an error occur while tokenizing PII data * @throws JsonProcessingException in case an error occur while parsing tokenizer response */ - CartForReceipt recoverCart(CartForReceipt existingCart) + CartForReceipt recoverFailedCart(CartForReceipt existingCart) throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException; /** - * Massive recover all receipt with the specified status {@link ReceiptStatusType} + * Reset notification info and set status to {@link ReceiptStatusType#GENERATED} in order to trigger notification + * process + * + * @param receipt the receipt to reset + * @return the updated receipt + */ + Receipt recoverNoNotifiedReceipt(Receipt receipt); + + /** + * Massive recover all failed receipt with the specified status {@link ReceiptStatusType} * * @param status the status to be recovered * @return the recover result @@ -59,10 +70,18 @@ CartForReceipt recoverCart(CartForReceipt existingCart) MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status); /** - * Massive recover all cart with the specified status {@link CartStatusType} + * Massive recover all failed cart with the specified status {@link CartStatusType} * * @param status the status to be recovered * @return the recover result */ MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status); + + /** + * Massive recover all not notified receipt with the specified status {@link ReceiptStatusType} + * + * @param status the status to be recovered + * @return the recover result + */ + List massiveRecoverNoNotified(ReceiptStatusType status); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index d3bb54f2..d83c122c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -55,7 +55,7 @@ public HelpdeskServiceImpl( } @Override - public Receipt recoverReceipt(Receipt existingReceipt) + public Receipt recoverFailedReceipt(Receipt existingReceipt) throws BizEventUnprocessableEntityException, BizEventBadRequestException, BizEventNotFoundException { // retrieve biz-event with the specified cartId @@ -75,7 +75,7 @@ public Receipt recoverReceipt(Receipt existingReceipt) } @Override - public CartForReceipt recoverCart(CartForReceipt existingCart) + public CartForReceipt recoverFailedCart(CartForReceipt existingCart) throws BizEventUnprocessableEntityException, BizEventBadRequestException, PDVTokenizerException, JsonProcessingException { // retrieve biz-event with the specified cartId List bizEvents = this.bizEventCosmosClient.getAllCartBizEventDocument(existingCart.getEventId()); @@ -95,6 +95,18 @@ public CartForReceipt recoverCart(CartForReceipt existingCart) return cart; } + @Override + public Receipt recoverNoNotifiedReceipt(Receipt receipt) { + receipt.setStatus(ReceiptStatusType.GENERATED); + receipt.setNotificationNumRetry(0); + receipt.setNotified_at(0); + + receipt.setReasonErr(null); + receipt.setReasonErrPayer(null); + + return receipt; + } + @Override public MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status) { List failedReceipts = new ArrayList<>(); @@ -108,7 +120,7 @@ public MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status) { for (FeedResponse page : feedResponseIterator) { for (Receipt receipt : page.getResults()) { try { - Receipt restored = recoverReceipt(receipt); + Receipt restored = recoverFailedReceipt(receipt); if (isReceiptStatusValid(restored)) { successCounter++; @@ -145,7 +157,7 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { for (FeedResponse page : feedResponseIterator) { for (CartForReceipt cart : page.getResults()) { try { - CartForReceipt recoverCart = recoverCart(cart); + CartForReceipt recoverCart = recoverFailedCart(cart); if (isCartStatusValid(recoverCart)) { successCounter++; @@ -169,6 +181,27 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { .build(); } + @Override + public List massiveRecoverNoNotified(ReceiptStatusType status) { + List receiptList = new ArrayList<>(); + String continuationToken = null; + do { + Iterable> feedResponseIterator = + this.receiptCosmosService.getNotNotifiedReceiptByStatus(continuationToken, 100, status); + + for (FeedResponse page : feedResponseIterator) { + for (Receipt receipt : page.getResults()) { + Receipt restoredReceipt = recoverNoNotifiedReceipt(receipt); + receiptList.add(restoredReceipt); + } + continuationToken = page.getContinuationToken(); + + } + } while (continuationToken != null); + + return receiptList; + } + private void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { if (bizEvents.isEmpty()) { throw new BizEventBadRequestException("BizEvents for the specified cart not found"); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java deleted file mode 100644 index 23efd229..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/RecoverNotNotifiedReceiptUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -package it.gov.pagopa.receipt.pdf.datastore.utils; - -import com.azure.cosmos.models.FeedResponse; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; - -import java.util.ArrayList; -import java.util.List; - -public class RecoverNotNotifiedReceiptUtils { - - public static Receipt restoreReceipt(Receipt receipt) { - receipt.setStatus(ReceiptStatusType.GENERATED); - receipt.setNotificationNumRetry(0); - receipt.setNotified_at(0); - - receipt.setReasonErr(null); - receipt.setReasonErrPayer(null); - - return receipt; - } - - public static List receiptMassiveRestore(ReceiptStatusType statusType, ReceiptCosmosService receiptCosmosService) { - - List receiptList = new ArrayList<>(); - String continuationToken = null; - do { - Iterable> feedResponseIterator = - receiptCosmosService.getNotNotifiedReceiptByStatus(continuationToken, 100, statusType); - - for (FeedResponse page : feedResponseIterator) { - for (Receipt receipt : page.getResults()) { - Receipt restoredReceipt = restoreReceipt(receipt); - receiptList.add(restoredReceipt); - } - continuationToken = page.getContinuationToken(); - - } - } while (continuationToken != null); - - return receiptList; - } - - private RecoverNotNotifiedReceiptUtils() {} -} From 3469e01c1b8a6cca6ad6a5846efbcabc5db8acd3 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 17:15:50 +0100 Subject: [PATCH 29/38] [PAGOPA-3343] added recover not notified cart function --- .../client/CartReceiptsCosmosClient.java | 18 +++ .../impl/CartReceiptsCosmosClientImpl.java | 55 ++++++++- .../datastore/entity/cart/CartStatusType.java | 9 ++ .../http/RecoverNotNotifiedCartReceipt.java | 102 ++++++++++++++++ .../RecoverNotNotifiedCartReceiptMassive.java | 111 ++++++++++++++++++ .../http/RecoverNotNotifiedReceipt.java | 8 +- .../RecoverNotNotifiedReceiptMassive.java | 12 +- ...ecoverNotNotifiedCartReceiptScheduled.java | 78 ++++++++++++ .../datastore/service/HelpdeskService.java | 17 +++ .../service/ReceiptCosmosService.java | 14 +++ .../service/impl/HelpdeskServiceImpl.java | 37 ++++++ .../impl/ReceiptCosmosServiceImpl.java | 22 ++++ 12 files changed, 464 insertions(+), 19 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduled.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java index 1a4d6e7c..5332d31b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java @@ -60,4 +60,22 @@ public interface CartReceiptsCosmosClient { * @return receipt documents */ Iterable> getInsertedCartReceiptDocuments(String continuationToken, Integer pageSize); + + /** + * Retrieve the not notified cart receipt documents with {@link CartStatusType#IO_ERROR_TO_NOTIFY} + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return cart receipt documents + */ + Iterable> getIOErrorToNotifyCartReceiptDocuments(String continuationToken, Integer pageSize); + + /** + * Retrieve the not notified cart receipt documents with {@link CartStatusType#GENERATED} + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @return cart receipt documents + */ + Iterable> getGeneratedCartReceiptDocuments(String continuationToken, Integer pageSize); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java index 2afb0bc6..bf5384ae 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java @@ -1,6 +1,10 @@ package it.gov.pagopa.receipt.pdf.datastore.client.impl; -import com.azure.cosmos.*; +import com.azure.cosmos.ConsistencyLevel; +import com.azure.cosmos.CosmosClient; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.CosmosContainer; +import com.azure.cosmos.CosmosDatabase; import com.azure.cosmos.implementation.PreconditionFailedException; import com.azure.cosmos.models.CosmosItemRequestOptions; import com.azure.cosmos.models.CosmosItemResponse; @@ -27,7 +31,9 @@ public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { private static final String DOCUMENT_NOT_FOUND_ERR_MSG = "Document not found in the defined container"; private final String millisDiff = System.getenv("MAX_DATE_DIFF_MILLIS"); + private final String millisNotifyDif = System.getenv("MAX_DATE_DIFF_NOTIFY_MILLIS"); private final String numDaysRecoverFailed = System.getenv().getOrDefault("RECOVER_FAILED_MASSIVE_MAX_DAYS", "0"); + private final String numDaysRecoverNotNotified = System.getenv().getOrDefault("RECOVER_NOT_NOTIFIED_MASSIVE_MAX_DAYS", "0"); private final CosmosClient cosmosClient; @@ -57,7 +63,7 @@ public static CartReceiptsCosmosClientImpl getInstance() { @Override public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { return getDocumentByFilter(cartForReceiptContainerName, "eventId", eventId, CartForReceipt.class) - .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); + .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } @@ -98,7 +104,10 @@ public CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundEx * {@inheritDoc} */ @Override - public Iterable> getFailedCartReceiptDocuments(String continuationToken, Integer pageSize) { + public Iterable> getFailedCartReceiptDocuments( + String continuationToken, + Integer pageSize + ) { String query = "SELECT * FROM c WHERE c.status = 'FAILED'"; return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); } @@ -107,7 +116,10 @@ public Iterable> getFailedCartReceiptDocuments(Stri * {@inheritDoc} */ @Override - public Iterable> getInsertedCartReceiptDocuments(String continuationToken, Integer pageSize) { + public Iterable> getInsertedCartReceiptDocuments( + String continuationToken, + Integer pageSize + ) { OffsetDateTime currentDateTime = OffsetDateTime.now(); long now = currentDateTime.toInstant().toEpochMilli(); long daysAgo = currentDateTime.truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverFailed)).toInstant().toEpochMilli(); @@ -119,11 +131,44 @@ public Iterable> getInsertedCartReceiptDocuments(St return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); } + @Override + public Iterable> getIOErrorToNotifyCartReceiptDocuments( + String continuationToken, + Integer pageSize + ) { + long daysAgo = OffsetDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(); + + String query = String.format("SELECT * FROM c WHERE c.status = '%s' AND c.generated_at >= %s", + ReceiptStatusType.IO_ERROR_TO_NOTIFY, daysAgo); + + return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); + } + + @Override + public Iterable> getGeneratedCartReceiptDocuments( + String continuationToken, + Integer pageSize + ) { + OffsetDateTime currentDateTime = OffsetDateTime.now(); + long now = currentDateTime.toInstant().toEpochMilli(); + long daysAgo = currentDateTime.truncatedTo(ChronoUnit.DAYS).minusDays(Long.parseLong(numDaysRecoverNotNotified)).toInstant().toEpochMilli(); + + String query = String.format("SELECT * FROM c WHERE (c.status = '%s' AND c.generated_at >= %s AND ( %s - c.generated_at) >= %s)", + ReceiptStatusType.GENERATED, daysAgo, now, millisNotifyDif); + + return executePagedQuery(cartForReceiptContainerName, query, continuationToken, pageSize); + } + /** * PRIVATE METHODS */ - private Optional getDocumentByFilter(String containerId, String propertyName, String propertyValue, Class classType) { + private Optional getDocumentByFilter( + String containerId, + String propertyName, + String propertyValue, + Class classType + ) { CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java index 4cb9f143..1b28dfc5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartStatusType.java @@ -24,7 +24,16 @@ public enum CartStatusType { FAILED ); + private static final Set NOTIFICATION_FAILED_STATUS = Set.of( + GENERATED, + IO_ERROR_TO_NOTIFY + ); + public boolean isAFailedDatastoreStatus() { return DATASTORE_FAILED_STATUS.contains(this); } + + public boolean isANotificationFailedStatus() { + return NOTIFICATION_FAILED_STATUS.contains(this); + } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java new file mode 100644 index 00000000..35b793e9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java @@ -0,0 +1,102 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; + +/** + * Azure Functions with HTTP Trigger. + */ +public class RecoverNotNotifiedCartReceipt { + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedCartReceipt.class); + + private final ReceiptCosmosService receiptCosmosService; + private final HelpdeskService helpdeskService; + + public RecoverNotNotifiedCartReceipt() { + this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.helpdeskService = new HelpdeskServiceImpl(); + } + + RecoverNotNotifiedCartReceipt(ReceiptCosmosService receiptCosmosService, HelpdeskService helpdeskService) { + this.receiptCosmosService = receiptCosmosService; + this.helpdeskService = helpdeskService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + *

+ * It recovers the receipt with the specified biz event id + *

+ * It recovers the receipt with failed notification ({@link CartStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link CartStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link CartStatusType#GENERATED}). + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverNotNotifiedCartReceipt") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverNotNotifiedCartTrigger", + methods = {HttpMethod.POST}, + route = "cart-receipts/{cart-id}/recover-not-notified", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @BindingName("cart-id") String cartId, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding documentdb, + final ExecutionContext context + ) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + CartForReceipt cart; + try { + cart = this.receiptCosmosService.getCart(cartId); + } catch (CartNotFoundException e) { + String errMsg = String.format("Unable to retrieve the cart receipt with id %s", cartId); + logger.error("[{}] {}", context.getFunctionName(), errMsg, e); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); + } + + if (!cart.getStatus().isANotificationFailedStatus()) { + String errMsg = String.format("The requested cart receipt with id %s is not in the expected status", + cart.getEventId()); + logger.error(errMsg); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); + } + + CartForReceipt restoredCart = this.helpdeskService.recoverNoNotifiedCart(cart); + + documentdb.setValue(restoredCart); + String responseMsg = String.format("Cart receipt with id %s restored in status %s with success", + cart.getEventId(), ReceiptStatusType.GENERATED); + return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); + } +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java new file mode 100644 index 00000000..ea667f7a --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java @@ -0,0 +1,111 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; + + +/** + * Azure Functions with HTTP Trigger. + */ +public class RecoverNotNotifiedCartReceiptMassive { + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedCartReceiptMassive.class); + + private final HelpdeskService helpdeskService; + + public RecoverNotNotifiedCartReceiptMassive() { + this.helpdeskService = new HelpdeskServiceImpl(); + } + + RecoverNotNotifiedCartReceiptMassive(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; + } + + /** + * This function will be invoked when an Http Trigger occurs. + *

+ * It recovers all receipt with the provided status. + *

+ * It recovers the receipt with failed notification ({@link CartStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link CartStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link CartStatusType#GENERATED}). + * + * @return response with {@link HttpStatus#OK} if the operation succeeded + */ + @FunctionName("RecoverNotNotifiedCartReceiptMassive") + public HttpResponseMessage run( + @HttpTrigger(name = "RecoverNotNotifiedCartMassiveTrigger", + methods = {HttpMethod.POST}, + route = "cart-receipts/recover-not-notified", + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context + ) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + // Get named parameter + String statusParam = request.getQueryParameters().get("status"); + CartStatusType status; + try { + status = validateStatusParam(statusParam); + } catch (InvalidParameterException e) { + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } + + List receiptList = this.helpdeskService.massiveRecoverNoNotified(status); + if (receiptList.isEmpty()) { + return request.createResponseBuilder(HttpStatus.OK).body("No receipts to be recovered").build(); + } + + documentdb.setValue(receiptList); + String msg = String.format("Recovered %s receipt with success", receiptList.size()); + return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); + } + + private CartStatusType validateStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + CartStatusType status; + try { + status = CartStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + if (!status.isANotificationFailedStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + throw new InvalidParameterException(message); + } + return status; + } +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index 0fb6b9d7..5432d2bf 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -72,7 +72,7 @@ public HttpResponseMessage run( databaseName = "db", containerName = "receipts", connection = "COSMOS_RECEIPTS_CONN_STRING") - OutputBinding> documentReceipts, + OutputBinding documentReceipts, final ExecutionContext context ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); @@ -86,9 +86,7 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } - if (!receipt.getStatus().equals(ReceiptStatusType.IO_ERROR_TO_NOTIFY) - && !receipt.getStatus().equals(ReceiptStatusType.GENERATED) - ) { + if (!receipt.getStatus().isANotificationFailedStatus()) { String errMsg = String.format("The requested receipt with eventId %s is not in the expected status", receipt.getEventId()); logger.error(errMsg); @@ -97,7 +95,7 @@ public HttpResponseMessage run( Receipt restoredReceipt = this.helpdeskService.recoverNoNotifiedReceipt(receipt); - documentReceipts.setValue(Collections.singletonList(restoredReceipt)); + documentReceipts.setValue(restoredReceipt); String responseMsg = String.format("Receipt with id %s and eventId %s restored in status %s with success", receipt.getId(), receipt.getEventId(), ReceiptStatusType.GENERATED); return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build(); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index d5946769..3ce21acf 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -13,7 +13,6 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; import org.slf4j.Logger; @@ -23,6 +22,8 @@ import java.util.List; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; + /** * Azure Functions with HTTP Trigger. @@ -75,14 +76,7 @@ public HttpResponseMessage run( try { status = validateStatusParam(statusParam); } catch (InvalidParameterException e) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } List receiptList = this.helpdeskService.massiveRecoverNoNotified(status); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduled.java new file mode 100644 index 00000000..60fb94d0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduled.java @@ -0,0 +1,78 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.TimerTrigger; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class RecoverNotNotifiedCartReceiptScheduled { + + private final boolean isEnabled = Boolean.parseBoolean(System.getenv().getOrDefault("NOT_NOTIFIED_AUTORECOVER_ENABLED", "true")); + + private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedCartReceiptScheduled.class); + + private final HelpdeskService helpdeskService; + + public RecoverNotNotifiedCartReceiptScheduled() { + this.helpdeskService = new HelpdeskServiceImpl(); + } + + RecoverNotNotifiedCartReceiptScheduled(HelpdeskService helpdeskService) { + this.helpdeskService = helpdeskService; + } + + /** + * This function will be invoked on a scheduled basis. + *

+ * It recovers all cart receipt with the provided status. + *

+ * It recovers the cart receipt with failed notification ({@link CartStatusType#IO_ERROR_TO_NOTIFY}) or notification + * not triggered ({@link CartStatusType#GENERATED} by clearing the errors and update the status to the + * previous step ({@link CartStatusType#GENERATED}). + */ + @FunctionName("RecoverNotNotifiedCartTimerTriggerProcessor") + public void processRecoverNotNotifiedScheduledTrigger( + @TimerTrigger( + name = "timerInfoNotNotifiedCart", + schedule = "%TRIGGER_NOTIFY_REC_SCHEDULE%" + ) + String timerInfo, + @CosmosDBOutput( + name = "CartReceiptDatastore", + databaseName = "db", + containerName = "cart-for-receipts", + connection = "COSMOS_RECEIPTS_CONN_STRING") + OutputBinding> documentdb, + final ExecutionContext context + ) { + if (isEnabled) { + logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); + + List receiptList = new ArrayList<>(); + receiptList.addAll(process(context, CartStatusType.IO_ERROR_TO_NOTIFY)); + receiptList.addAll(process(context, CartStatusType.GENERATED)); + + documentdb.setValue(receiptList); + } + } + + private List process(ExecutionContext context, CartStatusType statusType) { + List receiptList = this.helpdeskService.massiveRecoverNoNotified(statusType); + + List idList = receiptList.parallelStream().map(CartForReceipt::getId).toList(); + logger.info("[{}] Recovered {} cart receipts for status {} with ids: {}", + context.getFunctionName(), receiptList.size(), statusType, idList); + return receiptList; + } +} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java index 12aed89f..e68ca7c1 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/HelpdeskService.java @@ -61,6 +61,15 @@ CartForReceipt recoverFailedCart(CartForReceipt existingCart) */ Receipt recoverNoNotifiedReceipt(Receipt receipt); + /** + * Reset notification info and set status to {@link CartStatusType#GENERATED} in order to trigger notification + * process + * + * @param cart the cart to reset + * @return the updated cart + */ + CartForReceipt recoverNoNotifiedCart(CartForReceipt cart); + /** * Massive recover all failed receipt with the specified status {@link ReceiptStatusType} * @@ -84,4 +93,12 @@ CartForReceipt recoverFailedCart(CartForReceipt existingCart) * @return the recover result */ List massiveRecoverNoNotified(ReceiptStatusType status); + + /** + * Massive recover all not notified cart receipt with the specified status {@link CartStatusType} + * + * @param status the status to be recovered + * @return the recover result + */ + List massiveRecoverNoNotified(CartStatusType status); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index 2aeedd74..384b9bc3 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -49,6 +49,20 @@ Iterable> getNotNotifiedReceiptByStatus( ReceiptStatusType statusType ); + /** + * Retrieve the not notified cart receipt with the provided {@link CartStatusType} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the cart receipts + * @return cart receipt documents + */ + Iterable> getNotNotifiedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ); + /** * Retrieve the failed receipt with the provided {@link ReceiptStatusType} status * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index d83c122c..f514a79d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -107,6 +107,22 @@ public Receipt recoverNoNotifiedReceipt(Receipt receipt) { return receipt; } + @Override + public CartForReceipt recoverNoNotifiedCart(CartForReceipt cart) { + cart.setStatus(CartStatusType.GENERATED); + cart.setNotificationNumRetry(0); + cart.setNotified_at(0); + + cart.setReasonErr(null); + if (cart.getPayload() != null + && cart.getPayload().getCart() != null + ) { + cart.getPayload().getCart().forEach(cartPayment -> cartPayment.setReasonErrDebtor(null)); + } + + return cart; + } + @Override public MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status) { List failedReceipts = new ArrayList<>(); @@ -202,6 +218,27 @@ public List massiveRecoverNoNotified(ReceiptStatusType status) { return receiptList; } + @Override + public List massiveRecoverNoNotified(CartStatusType status) { + List carttList = new ArrayList<>(); + String continuationToken = null; + do { + Iterable> feedResponseIterator = + this.receiptCosmosService.getNotNotifiedCartReceiptByStatus(continuationToken, 100, status); + + for (FeedResponse page : feedResponseIterator) { + for (CartForReceipt cart : page.getResults()) { + CartForReceipt restoredReceipt = recoverNoNotifiedCart(cart); + carttList.add(restoredReceipt); + } + continuationToken = page.getContinuationToken(); + + } + } while (continuationToken != null); + + return carttList; + } + private void validateCartBizEvents(List bizEvents) throws BizEventBadRequestException, BizEventUnprocessableEntityException { if (bizEvents.isEmpty()) { throw new BizEventBadRequestException("BizEvents for the specified cart not found"); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index beed5a55..3af26878 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -89,6 +89,28 @@ public Iterable> getNotNotifiedReceiptByStatus( throw new IllegalStateException(errMsg); } + /** + * {@inheritDoc} + */ + @Override + public Iterable> getNotNotifiedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(CartStatusType.IO_ERROR_TO_NOTIFY)) { + return this.cartReceiptsCosmosClient.getIOErrorToNotifyCartReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(CartStatusType.GENERATED)) { + return this.cartReceiptsCosmosClient.getGeneratedCartReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving not notified receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + /** * {@inheritDoc} */ From 02a23ebe04861e562a2267e76286e6d389443a45 Mon Sep 17 00:00:00 2001 From: giomella Date: Tue, 13 Jan 2026 17:44:33 +0100 Subject: [PATCH 30/38] [PAGOPA-3343] minor refactor --- .../helpdesk/http/CartReceiptToReviewed.java | 56 ++++++----------- .../helpdesk/http/ReceiptToReviewed.java | 50 ++++++--------- .../http/RecoverFailedCartReceipt.java | 2 +- .../http/RecoverFailedCartReceiptMassive.java | 40 ++++-------- .../helpdesk/http/RecoverFailedReceipt.java | 2 +- .../http/RecoverFailedReceiptMassive.java | 20 +++--- .../http/RecoverNotNotifiedCartReceipt.java | 2 +- .../RecoverNotNotifiedCartReceiptMassive.java | 32 +++------- .../http/RecoverNotNotifiedReceipt.java | 4 +- .../RecoverNotNotifiedReceiptMassive.java | 31 +++------ .../RecoverFailedReceiptScheduled.java | 8 ++- .../utils/BizEventToReceiptUtils.java | 15 ----- .../pdf/datastore/utils/HelpdeskUtils.java | 63 +++++++++++++++++++ 13 files changed, 148 insertions(+), 177 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/HelpdeskUtils.java diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java index 4e90c0bf..8983368a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java @@ -1,11 +1,19 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; -import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; import org.slf4j.Logger; @@ -15,6 +23,8 @@ import java.util.NoSuchElementException; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; + /** * Azure Functions with Azure Http trigger. */ @@ -36,12 +46,6 @@ public CartReceiptToReviewed() { /** * This function will be invoked when an Http Trigger occurs * - * TODO - * collection cart-receipt-message-errors - * per il carrello arriva l'id del carrello (id collection) - * logica analoga a quanto presente in ReceiptToReviewed - * - * * @return response with HttpStatus.OK */ @FunctionName("CartReceiptToReviewed") @@ -58,20 +62,10 @@ public HttpResponseMessage run( containerName = "cart-receipts-message-errors", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding documentdb, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - if (cartId == null || cartId.isBlank()) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid cart id") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - String responseMsg; CartReceiptError receiptError; @@ -80,29 +74,15 @@ public HttpResponseMessage run( } catch (NoSuchElementException | CartNotFoundException e) { responseMsg = String.format("No cartReceiptError has been found with cartId %s", cartId); logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); - return request - .createResponseBuilder(HttpStatus.NOT_FOUND) - .body(ProblemJson.builder() - .title(HttpStatus.NOT_FOUND.name()) - .detail(responseMsg) - .status(HttpStatus.NOT_FOUND.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, responseMsg); } if (!receiptError.getStatus().equals(ReceiptErrorStatusType.TO_REVIEW)) { responseMsg = String.format("Found cartReceiptError with invalid status %s for cartId %s", receiptError.getStatus(), cartId); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(responseMsg) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, responseMsg); } - receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); + receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); documentdb.setValue(receiptError); responseMsg = String.format("CartReceiptError with cartId %s updated to status %s with success", receiptError.getId(), ReceiptErrorStatusType.REVIEWED); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java index 9c257c73..70f3b455 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java @@ -1,11 +1,19 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; -import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.BindingName; +import com.microsoft.azure.functions.annotation.CosmosDBOutput; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; @@ -15,6 +23,8 @@ import java.util.NoSuchElementException; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; + /** * Azure Functions with Azure Http trigger. */ @@ -52,20 +62,10 @@ public HttpResponseMessage run( containerName = "receipts-message-errors", connection = "COSMOS_RECEIPTS_CONN_STRING") OutputBinding documentdb, - final ExecutionContext context) { + final ExecutionContext context + ) { logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now()); - if (eventId == null || eventId.isBlank()) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail("Please pass a valid biz-event id") - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); - } - String responseMsg; ReceiptError receiptError; @@ -74,29 +74,15 @@ public HttpResponseMessage run( } catch (NoSuchElementException | ReceiptNotFoundException e) { responseMsg = String.format("No receiptError has been found with bizEventId %s", eventId); logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); - return request - .createResponseBuilder(HttpStatus.NOT_FOUND) - .body(ProblemJson.builder() - .title(HttpStatus.NOT_FOUND.name()) - .detail(responseMsg) - .status(HttpStatus.NOT_FOUND.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.NOT_FOUND, responseMsg); } if (!receiptError.getStatus().equals(ReceiptErrorStatusType.TO_REVIEW)) { responseMsg = String.format("Found receiptError with invalid status %s for bizEventId %s", receiptError.getStatus(), eventId); - return request - .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ProblemJson.builder() - .title(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .detail(responseMsg) - .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, responseMsg); } - receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); + receiptError.setStatus(ReceiptErrorStatusType.REVIEWED); documentdb.setValue(receiptError); responseMsg = String.format("ReceiptError with id %s and bizEventId %s updated to status %s with success", receiptError.getId(), receiptError.getBizEventId(), ReceiptErrorStatusType.REVIEWED); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index e57e174d..81f5d167 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -28,8 +28,8 @@ import java.time.LocalDateTime; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isCartStatusValid; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; /** * Azure Functions with Azure Http trigger. diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java index c27e883d..c2288259 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java @@ -24,6 +24,9 @@ import java.util.List; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.validateCartStatusParam; + /** * Azure Functions with Azure Http trigger. @@ -78,16 +81,15 @@ public HttpResponseMessage run( String statusParam = request.getQueryParameters().get("status"); CartStatusType status; try { - status = validateStatusParam(statusParam); + status = validateCartStatusParam(statusParam); } catch (InvalidParameterException e) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } + + if (!status.isAFailedDatastoreStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); } MassiveCartRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); @@ -113,24 +115,4 @@ public HttpResponseMessage run( .body(responseMsg) .build(); } - - private CartStatusType validateStatusParam(String statusParam) throws InvalidParameterException { - if (statusParam == null) { - throw new InvalidParameterException("Please pass a status to recover"); - } - - CartStatusType status; - try { - status = CartStatusType.valueOf(statusParam); - } catch (IllegalArgumentException e) { - throw new InvalidParameterException("Please pass a valid status to recover", e); - } - - if (!status.isAFailedDatastoreStatus()) { - String message = String.format("The provided status %s is not among the processable" + - "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); - throw new InvalidParameterException(message); - } - return status; - } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 3b620f66..0f1d5bba 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -27,8 +27,8 @@ import java.time.LocalDateTime; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.isReceiptStatusValid; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; /** * Azure Functions with Azure Http trigger. diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index cb579a41..79bc9c96 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -24,6 +24,9 @@ import java.util.List; import java.util.Optional; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.validateReceiptStatusParam; + /** * Azure Functions with Azure Http trigger. @@ -77,16 +80,15 @@ public HttpResponseMessage run( String statusParam = request.getQueryParameters().get("status"); ReceiptStatusType status; try { - status = validateStatusParam(statusParam); + status = validateReceiptStatusParam(statusParam); } catch (InvalidParameterException e) { - return request - .createResponseBuilder(HttpStatus.BAD_REQUEST) - .body(ProblemJson.builder() - .title(HttpStatus.BAD_REQUEST.name()) - .detail(e.getMessage()) - .status(HttpStatus.BAD_REQUEST.value()) - .build()) - .build(); + return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); + } + + if (!status.isAFailedDatastoreStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); } MassiveRecoverResult recoverResult = this.helpdeskService.massiveRecoverByStatus(status); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java index 35b793e9..45cd215b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java @@ -25,7 +25,7 @@ import java.time.LocalDateTime; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; /** * Azure Functions with HTTP Trigger. diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java index ea667f7a..19af9d2a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java @@ -22,8 +22,8 @@ import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; - +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.validateCartStatusParam; /** * Azure Functions with HTTP Trigger. @@ -74,11 +74,17 @@ public HttpResponseMessage run( String statusParam = request.getQueryParameters().get("status"); CartStatusType status; try { - status = validateStatusParam(statusParam); + status = validateCartStatusParam(statusParam); } catch (InvalidParameterException e) { return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } + if (!status.isANotificationFailedStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); + } + List receiptList = this.helpdeskService.massiveRecoverNoNotified(status); if (receiptList.isEmpty()) { return request.createResponseBuilder(HttpStatus.OK).body("No receipts to be recovered").build(); @@ -88,24 +94,4 @@ public HttpResponseMessage run( String msg = String.format("Recovered %s receipt with success", receiptList.size()); return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); } - - private CartStatusType validateStatusParam(String statusParam) throws InvalidParameterException { - if (statusParam == null) { - throw new InvalidParameterException("Please pass a status to recover"); - } - - CartStatusType status; - try { - status = CartStatusType.valueOf(statusParam); - } catch (IllegalArgumentException e) { - throw new InvalidParameterException("Please pass a valid status to recover", e); - } - - if (!status.isANotificationFailedStatus()) { - String message = String.format("The provided status %s is not among the processable" + - "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); - throw new InvalidParameterException(message); - } - return status; - } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index 5432d2bf..b805b8a7 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -22,11 +22,9 @@ import org.slf4j.LoggerFactory; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; /** * Azure Functions with HTTP Trigger. diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index 3ce21acf..7527093d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -22,7 +22,8 @@ import java.util.List; import java.util.Optional; -import static it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.buildErrorResponse; +import static it.gov.pagopa.receipt.pdf.datastore.utils.HelpdeskUtils.validateReceiptStatusParam; /** @@ -74,11 +75,17 @@ public HttpResponseMessage run( String statusParam = request.getQueryParameters().get("status"); ReceiptStatusType status; try { - status = validateStatusParam(statusParam); + status = validateReceiptStatusParam(statusParam); } catch (InvalidParameterException e) { return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } + if (!status.isANotificationFailedStatus()) { + String message = String.format("The provided status %s is not among the processable" + + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); + } + List receiptList = this.helpdeskService.massiveRecoverNoNotified(status); if (receiptList.isEmpty()) { return request.createResponseBuilder(HttpStatus.OK).body("No receipts to be recovered").build(); @@ -88,24 +95,4 @@ public HttpResponseMessage run( String msg = String.format("Recovered %s receipt with success", receiptList.size()); return request.createResponseBuilder(HttpStatus.OK).body(msg).build(); } - - private ReceiptStatusType validateStatusParam(String statusParam) throws InvalidParameterException { - if (statusParam == null) { - throw new InvalidParameterException("Please pass a status to recover"); - } - - ReceiptStatusType status; - try { - status = ReceiptStatusType.valueOf(statusParam); - } catch (IllegalArgumentException e) { - throw new InvalidParameterException("Please pass a valid status to recover", e); - } - - if (!status.isANotificationFailedStatus()) { - String message = String.format("The provided status %s is not among the processable" + - "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); - throw new InvalidParameterException(message); - } - return status; - } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java index 5dd06e7f..a7aec503 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java @@ -42,9 +42,11 @@ public RecoverFailedReceiptScheduled() { * This function will be invoked periodically according to the specified schedule. *

* It recovers all the receipts with the following status: - * - ({@link ReceiptStatusType#INSERTED}) - * - ({@link ReceiptStatusType#FAILED}) - * - ({@link ReceiptStatusType#NOT_QUEUE_SENT}) + *

    + *
  • {@link ReceiptStatusType#INSERTED}
  • + *
  • {@link ReceiptStatusType#FAILED}
  • + *
  • {@link ReceiptStatusType#NOT_QUEUE_SENT}
  • + *
*

* It creates the receipts if not exist and send on queue the event in order to proceed with the receipt generation. */ diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index 32e8007d..d4a8255b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -321,19 +321,4 @@ public static boolean isValidChannelOrigin(BizEvent bizEvent) { return originMatches || clientIdMatches; } - - public static HttpResponseMessage buildErrorResponse( - HttpRequestMessage> request, - HttpStatus httpStatus, - String errMsg - ) { - return request - .createResponseBuilder(httpStatus) - .body(ProblemJson.builder() - .title(httpStatus.name()) - .detail(errMsg) - .status(httpStatus.value()) - .build()) - .build(); - } } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/HelpdeskUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/HelpdeskUtils.java new file mode 100644 index 00000000..45b4a3ea --- /dev/null +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/HelpdeskUtils.java @@ -0,0 +1,63 @@ +package it.gov.pagopa.receipt.pdf.datastore.utils; + +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.InvalidParameterException; +import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; + +import java.util.Optional; + +public class HelpdeskUtils { + + public static HttpResponseMessage buildErrorResponse( + HttpRequestMessage> request, + HttpStatus httpStatus, + String errMsg + ) { + return request + .createResponseBuilder(httpStatus) + .body(ProblemJson.builder() + .title(httpStatus.name()) + .detail(errMsg) + .status(httpStatus.value()) + .build()) + .build(); + } + + public static CartStatusType validateCartStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + CartStatusType status; + try { + status = CartStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + return status; + } + + + public static ReceiptStatusType validateReceiptStatusParam(String statusParam) throws InvalidParameterException { + if (statusParam == null) { + throw new InvalidParameterException("Please pass a status to recover"); + } + + ReceiptStatusType status; + try { + status = ReceiptStatusType.valueOf(statusParam); + } catch (IllegalArgumentException e) { + throw new InvalidParameterException("Please pass a valid status to recover", e); + } + + return status; + } + + private HelpdeskUtils() { + } +} From 60cb5ae2c3c233bb20500fae0f6376846d374618 Mon Sep 17 00:00:00 2001 From: giomella Date: Wed, 14 Jan 2026 10:14:42 +0100 Subject: [PATCH 31/38] [PAGOPA-3343] added unit tests on clients --- .../client/CartReceiptsCosmosClient.java | 8 - .../client/impl/BizEventCosmosClientImpl.java | 2 + .../impl/CartReceiptsCosmosClientImpl.java | 14 +- .../impl/BizEventCosmosClientImplTest.java | 141 ++++----- .../CartReceiptsCosmosClientImplTest.java | 182 ++++++++---- .../impl/ReceiptCosmosClientImplTest.java | 279 ++++++++---------- 6 files changed, 309 insertions(+), 317 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java index 5332d31b..2e9bfa80 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/CartReceiptsCosmosClient.java @@ -19,14 +19,6 @@ public interface CartReceiptsCosmosClient { */ CartForReceipt getCartItem(String eventId) throws CartNotFoundException; - /** - * Save Cart For Receipt on CosmosDB database - * - * @param receipt Cart Data to save - * @return the saved cart-for-receipts document - */ - CosmosItemResponse saveCart(CartForReceipt receipt); - /** * Update Cart For Receipt on CosmosDB database * diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java index 75777a0e..d5f980b0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImpl.java @@ -27,10 +27,12 @@ public class BizEventCosmosClientImpl implements BizEventCosmosClient { private BizEventCosmosClientImpl() { String azureKey = System.getenv("COSMOS_BIZ_EVENT_KEY"); String serviceEndpoint = System.getenv("COSMOS_BIZ_EVENT_SERVICE_ENDPOINT"); + String readRegion = System.getenv("COSMOS_BIZ_EVENT_READ_REGION"); this.cosmosClient = new CosmosClientBuilder() .endpoint(serviceEndpoint) .key(azureKey) + .preferredRegions(List.of(readRegion)) .buildClient(); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java index bf5384ae..d468bfe6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImpl.java @@ -19,6 +19,7 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Optional; public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { @@ -40,11 +41,13 @@ public class CartReceiptsCosmosClientImpl implements CartReceiptsCosmosClient { private CartReceiptsCosmosClientImpl() { String azureKey = System.getenv("COSMOS_RECEIPT_KEY"); String serviceEndpoint = System.getenv("COSMOS_RECEIPT_SERVICE_ENDPOINT"); + String readRegion = System.getenv("COSMOS_RECEIPT_READ_REGION"); this.cosmosClient = new CosmosClientBuilder() .endpoint(serviceEndpoint) .key(azureKey) .consistencyLevel(ConsistencyLevel.BOUNDED_STALENESS) + .preferredRegions(List.of(readRegion)) .buildClient(); } @@ -66,17 +69,6 @@ public CartForReceipt getCartItem(String eventId) throws CartNotFoundException { .orElseThrow(() -> new CartNotFoundException(DOCUMENT_NOT_FOUND_ERR_MSG)); } - - /** - * {@inheritDoc} - */ - @Override - public CosmosItemResponse saveCart(CartForReceipt receipt) { - CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId); - CosmosContainer cosmosContainer = cosmosDatabase.getContainer(cartForReceiptContainerName); - return cosmosContainer.createItem(receipt); - } - /** * {@inheritDoc} */ diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java index ed79bc9d..c76719a5 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/BizEventCosmosClientImplTest.java @@ -3,131 +3,108 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; -import com.azure.cosmos.models.CosmosQueryRequestOptions; import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.entity.event.BizEvent; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Iterator; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; +@ExtendWith(MockitoExtension.class) class BizEventCosmosClientImplTest { @Mock - private Iterable> feedResponseIterable; + private CosmosClient cosmosClientMock; + + @Mock + private CosmosDatabase mockDatabase; + @Mock + private CosmosContainer mockContainer; + @Mock + private CosmosPagedIterable mockIterable; + @Mock + private Iterator mockIterator; + @Mock + private Iterable> mockFeedResponse; + + @InjectMocks + private BizEventCosmosClientImpl sut; @Test void testSingletonConnectionError() throws Exception { String mockKey = "mockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeyMK=="; withEnvironmentVariables( "COSMOS_BIZ_EVENT_KEY", mockKey, - "COSMOS_BIZ_EVENT_SERVICE_ENDPOINT", "") - .execute(() -> Assertions.assertThrows(IllegalArgumentException.class, BizEventCosmosClientImpl::getInstance)); + "COSMOS_BIZ_EVENT_SERVICE_ENDPOINT", "", + "COSMOS_BIZ_EVENT_READ_REGION", "") + .execute(() -> assertThrows(IllegalArgumentException.class, BizEventCosmosClientImpl::getInstance)); } @Test - void getAllBizEventDocumentsSuccess() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - - Iterator mockIterator = mock(Iterator.class); - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + void getAllCartBizEventDocumentsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + when(mockIterable.stream()).thenReturn(Stream.of(new BizEvent())); - BizEventCosmosClientImpl client = new BizEventCosmosClientImpl(mockClient); + List result = assertDoesNotThrow(() -> sut.getAllCartBizEventDocument("")); -// Assertions.assertDoesNotThrow(() -> client.getAllBizEventDocument("",null, 100)); + assertNotNull(result); + assertEquals(1, result.size()); } @Test - void getBizEventDocumentSuccess() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - - Iterator mockIterator = mock(Iterator.class); - BizEvent bizEvent = new BizEvent(); - - when(mockIterator.hasNext()).thenReturn(true); - when(mockIterator.next()).thenReturn(bizEvent); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + void getAllCartBizEventDocumentsSuccessQueryResultTruncated() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + when(mockIterable.stream()).thenReturn(Stream.of(new BizEvent(), new BizEvent(), new BizEvent(), new BizEvent(), new BizEvent(), new BizEvent(), new BizEvent())); - BizEventCosmosClientImpl client = new BizEventCosmosClientImpl(mockClient); + List result = assertDoesNotThrow(() -> sut.getAllCartBizEventDocument("")); - Assertions.assertDoesNotThrow(() -> client.getBizEventDocument("1")); + assertNotNull(result); + assertEquals(6, result.size()); } @Test - void getBizEventDocumentError() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - - Iterator mockIterator = mock(Iterator.class); + void getBizEventDocumentSuccess() { BizEvent bizEvent = new BizEvent(); - when(mockIterator.hasNext()).thenReturn(false); - when(mockIterator.next()).thenReturn(bizEvent); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - - BizEventCosmosClientImpl client = new BizEventCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); + when(mockIterable.iterator()).thenReturn(mockIterator); + when(mockIterator.hasNext()).thenReturn(true); + when(mockIterator.next()).thenReturn(bizEvent); - Assertions.assertThrows(BizEventNotFoundException.class, () -> client.getBizEventDocument("1")); + assertDoesNotThrow(() -> sut.getBizEventDocument("1")); } @Test - void getAllBizEventDocument_Success() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - - CosmosContainer mockContainer = mock(CosmosContainer.class); + void getBizEventDocumentError() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - - Iterator mockIterator = mock(Iterator.class); - BizEvent bizEvent = new BizEvent(); - - when(mockIterator.hasNext()).thenReturn(false); - when(mockIterator.next()).thenReturn(bizEvent); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + when(mockContainer.queryItems(anyString(), any(), eq(BizEvent.class))).thenReturn(mockIterable); when(mockIterable.iterator()).thenReturn(mockIterator); - when(mockContainer.queryItems(anyString(), any(CosmosQueryRequestOptions.class), eq(BizEvent.class))).thenReturn(mockIterable); - - BizEventCosmosClientImpl client = new BizEventCosmosClientImpl(mockClient); - - Assertions.assertDoesNotThrow(() -> client.getAllBizEventDocument("1", "asdf", 100)); + when(mockIterator.hasNext()).thenReturn(false); + assertThrows(BizEventNotFoundException.class, () -> sut.getBizEventDocument("1")); } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImplTest.java index 77dea21e..5c6b0369 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/CartReceiptsCosmosClientImplTest.java @@ -3,111 +3,181 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Iterator; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.mock; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; +@ExtendWith(MockitoExtension.class) class CartReceiptsCosmosClientImplTest { + private static final String CART_ID = "1"; + + @Mock + private CosmosClient cosmosClientMock; + + @Mock + private CosmosDatabase mockDatabase; + @Mock + private CosmosContainer mockContainer; + @Mock + private CosmosPagedIterable mockCartIterable; + @Mock + private Iterable> mockCartIterableByPage; + @Mock + private CosmosPagedIterable mockCartErrorIterable; + @Mock + private Iterator mockIterator; + @Mock + private Stream mockCartStream; + @Mock + private Stream mockCartErrorStream; + + @InjectMocks + private CartReceiptsCosmosClientImpl sut; + @Test void testSingletonConnectionError() throws Exception { String mockKey = "mockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeyMK=="; withEnvironmentVariables( "COSMOS_RECEIPT_KEY", mockKey, - "COSMOS_RECEIPT_SERVICE_ENDPOINT", "" - ).execute(() -> Assertions.assertThrows(IllegalArgumentException.class, CartReceiptsCosmosClientImpl::getInstance) + "COSMOS_RECEIPT_SERVICE_ENDPOINT", "", + "COSMOS_RECEIPT_READ_REGION", "" + ).execute(() -> assertThrows(IllegalArgumentException.class, CartReceiptsCosmosClientImpl::getInstance) ); } @Test - void runOk_Cart() throws CartNotFoundException { - String CART_ID = "1"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - - Iterator mockIterator = mock(Iterator.class); + void getCartItemSuccess() { CartForReceipt cartForReceipt = new CartForReceipt(); cartForReceipt.setId(CART_ID); - when(mockIterator.hasNext()).thenReturn(true); - when(mockIterator.next()).thenReturn(cartForReceipt); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.stream()).thenReturn(mockCartStream); + when(mockCartStream.findFirst()).thenReturn(Optional.of(cartForReceipt)); - when(mockIterable.iterator()).thenReturn(mockIterator); + CartForReceipt result = assertDoesNotThrow(() -> sut.getCartItem(CART_ID)); - when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))).thenReturn( - mockIterable - ); + assertEquals(CART_ID, result.getId()); + } + + @Test + void getCartItemFail() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.stream()).thenReturn(mockCartStream); + when(mockCartStream.findFirst()).thenReturn(Optional.empty()); + + assertThrows(CartNotFoundException.class, () -> sut.getCartItem("an invalid receipt id")); + } - CartReceiptsCosmosClientImpl client = new CartReceiptsCosmosClientImpl(mockClient); + @Test + void updateCartSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - Assertions.assertDoesNotThrow(() -> client.getCartItem(CART_ID)); + assertDoesNotThrow(() -> sut.updateCart(new CartForReceipt())); - CartForReceipt cartResponse = client.getCartItem(CART_ID); - Assertions.assertEquals(CART_ID, cartResponse.getId()); + verify(mockContainer).upsertItem(any(), any()); } @Test - void runKo_Cart() { - CosmosClient mockClient = mock(CosmosClient.class); + void getCartReceiptErrorSuccess() { + CartReceiptError receiptError = new CartReceiptError(); + receiptError.setId(CART_ID); - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(CartReceiptError.class))) + .thenReturn(mockCartErrorIterable); + when(mockCartErrorIterable.stream()).thenReturn(mockCartErrorStream); + when(mockCartErrorStream.findFirst()).thenReturn(Optional.of(receiptError)); - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + CartReceiptError result = assertDoesNotThrow(() -> sut.getCartReceiptError(CART_ID)); - Iterator mockIterator = mock(Iterator.class); + assertEquals(CART_ID, result.getId()); + } - when(mockIterator.hasNext()).thenReturn(false); + @Test + void getFailedCartReceiptDocumentsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.iterableByPage(anyString(), anyInt())).thenReturn(mockCartIterableByPage); - when(mockIterable.iterator()).thenReturn(mockIterator); + Iterable> result = + assertDoesNotThrow(() -> sut.getFailedCartReceiptDocuments("1", 0)); - when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))).thenReturn( - mockIterable - ); + assertNotNull(result); + } + + @Test + void getInsertedCartReceiptDocumentsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.iterableByPage(anyString(), anyInt())).thenReturn(mockCartIterableByPage); - CartReceiptsCosmosClientImpl client = new CartReceiptsCosmosClientImpl(mockClient); + Iterable> result = + assertDoesNotThrow(() -> sut.getInsertedCartReceiptDocuments("1", 0)); - Assertions.assertThrows(CartNotFoundException.class, () -> client.getCartItem("an invalid receipt id")); + assertNotNull(result); } @Test - void runOk_SaveCart() throws CartNotFoundException { - String CART_ID = "1"; - - CosmosClient mockClient = mock(CosmosClient.class); + void getIOErrorToNotifyCartReceiptDocumentsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.iterableByPage(anyString(), anyInt())).thenReturn(mockCartIterableByPage); - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); + Iterable> result = + assertDoesNotThrow(() -> sut.getIOErrorToNotifyCartReceiptDocuments("1", 0)); - CartForReceipt cartForReceipt = new CartForReceipt(); - cartForReceipt.setId(CART_ID); + assertNotNull(result); + } + @Test + void getGeneratedCartReceiptDocumentsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - - CartReceiptsCosmosClientImpl client = new CartReceiptsCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(CartForReceipt.class))) + .thenReturn(mockCartIterable); + when(mockCartIterable.iterableByPage(anyString(), anyInt())).thenReturn(mockCartIterableByPage); - Assertions.assertDoesNotThrow(() -> client.saveCart(cartForReceipt)); + Iterable> result = + assertDoesNotThrow(() -> sut.getGeneratedCartReceiptDocuments("1", 0)); + assertNotNull(result); } - } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java index 412b2969..7f906a74 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/client/impl/ReceiptCosmosClientImplTest.java @@ -3,246 +3,205 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.CosmosDatabase; +import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.util.CosmosPagedIterable; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; -import java.util.Iterator; +import java.util.Optional; import java.util.stream.Stream; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; +@ExtendWith(MockitoExtension.class) class ReceiptCosmosClientImplTest { + public static final String RECEIPT_ID = "a valid receipt id"; + public static final String IO_MESSAGE_ID = "id"; + + @Mock + private CosmosClient cosmosClientMock; + @Mock + private CosmosDatabase mockDatabase; + @Mock + private CosmosContainer mockContainer; + @Mock + private CosmosPagedIterable mockIterable; + @Mock + private CosmosPagedIterable mockReceiptErrorIterable; + @Mock + private Iterable> mockReceiptIterableByPage; + @Mock + private CosmosPagedIterable mockIOMessageIterable; + @Mock + private Stream mockReceiptStream; + @Mock + private Stream mockReceiptErrorStream; + @Mock + private Stream mockIOMessageStream; + + @InjectMocks + private ReceiptCosmosClientImpl sut; + @Test void testSingletonConnectionError() throws Exception { String mockKey = "mockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeyMK=="; withEnvironmentVariables( "COSMOS_RECEIPT_KEY", mockKey, - "COSMOS_RECEIPT_SERVICE_ENDPOINT", "" + "COSMOS_RECEIPT_SERVICE_ENDPOINT", "", + "COSMOS_RECEIPT_READ_REGION", "" ).execute(() -> Assertions.assertThrows(IllegalArgumentException.class, ReceiptCosmosClientImpl::getInstance) ); } @Test - void runOk() throws ReceiptNotFoundException { - String receiptId = "a valid receipt id"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); + void getReceiptDocumentOk() { Receipt receipt = new Receipt(); - receipt.setId(receiptId); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); + receipt.setId(RECEIPT_ID); - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.stream()).thenReturn(mockReceiptStream); + when(mockReceiptStream.findFirst()).thenReturn(Optional.of(receipt)); - Assertions.assertDoesNotThrow(() -> client.getReceiptDocument(receiptId)); + Receipt result = assertDoesNotThrow(() -> sut.getReceiptDocument(RECEIPT_ID)); - Receipt receiptResponse = client.getReceiptDocument(receiptId); - Assertions.assertEquals(receiptId, receiptResponse.getId()); + assertEquals(RECEIPT_ID, result.getId()); } @Test - void runKo() { - CosmosClient mockClient = mock(CosmosClient.class); + void getReceiptDocumentKo() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.stream()).thenReturn(mockReceiptStream); + when(mockReceiptStream.findFirst()).thenReturn(Optional.empty()); - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); + assertThrows(ReceiptNotFoundException.class, () -> sut.getReceiptDocument("an invalid receipt id")); + } - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); + @Test + void getReceiptErrorOk() { + ReceiptError receiptError = new ReceiptError(); + receiptError.setId(RECEIPT_ID); - Iterator mockIterator = mock(Iterator.class); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); + when(mockDatabase.getContainer(any())).thenReturn(mockContainer); + when(mockContainer.queryItems(anyString(), any(), eq(ReceiptError.class))).thenReturn(mockReceiptErrorIterable); + when(mockReceiptErrorIterable.stream()).thenReturn(mockReceiptErrorStream); + when(mockReceiptErrorStream.findFirst()).thenReturn(Optional.of(receiptError)); - when(mockIterator.hasNext()).thenReturn(false); + ReceiptError result = assertDoesNotThrow(() -> sut.getReceiptError(RECEIPT_ID)); - when(mockIterable.iterator()).thenReturn(mockIterator); + assertEquals(RECEIPT_ID, result.getId()); + } - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn( - mockIterable - ); + @Test + void saveReceiptsSuccess() { + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + assertDoesNotThrow(() -> sut.saveReceipts(new Receipt())); - Assertions.assertThrows(ReceiptNotFoundException.class, () -> client.getReceiptDocument("an invalid receipt id")); + verify(mockContainer).createItem(any()); } @Test void runOk_FailedQueryClient() { - String receiptId = "a valid receipt id"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - - Iterator mockIterator = mock(Iterator.class); - Receipt receipt = new Receipt(); - receipt.setId(receiptId); - - when(mockIterator.hasNext()).thenReturn(true); - when(mockIterator.next()).thenReturn(receipt); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn( - mockIterable - ); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.iterableByPage(null, 1)).thenReturn(mockReceiptIterableByPage); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + Iterable> result = + assertDoesNotThrow(() -> sut.getFailedReceiptDocuments(null, 1)); - Assertions.assertDoesNotThrow(() -> client.getFailedReceiptDocuments(null, 100)); + assertNotNull(result); } @Test void getGeneratedReceiptDocuments_Success() { - String receiptId = "a valid receipt id"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); - Receipt receipt = new Receipt(); - receipt.setId(receiptId); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) - .thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.iterableByPage(null, 1)).thenReturn(mockReceiptIterableByPage); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + Iterable> result = + assertDoesNotThrow(() -> sut.getGeneratedReceiptDocuments(null, 1)); - Assertions.assertDoesNotThrow(() -> client.getGeneratedReceiptDocuments("scsdf", 100)); + assertNotNull(result); } @Test void getIOErrorToNotifyReceiptDocuments_Success() { - String receiptId = "a valid receipt id"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); - Receipt receipt = new Receipt(); - receipt.setId(receiptId); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) - .thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.iterableByPage(null, 1)).thenReturn(mockReceiptIterableByPage); - Assertions.assertDoesNotThrow(() -> client.getIOErrorToNotifyReceiptDocuments("scsdf", 100)); + Iterable> result = + assertDoesNotThrow(() -> sut.getIOErrorToNotifyReceiptDocuments(null, 1)); + + assertNotNull(result); } @Test void getInsertedReceiptDocuments_Success() { - String receiptId = "a valid receipt id"; - - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); - Receipt receipt = new Receipt(); - receipt.setId(receiptId); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(receipt)); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))) - .thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable); + when(mockIterable.iterableByPage(null, 1)).thenReturn(mockReceiptIterableByPage); + + Iterable> result = + assertDoesNotThrow(() -> sut.getInsertedReceiptDocuments(null, 1)); - Assertions.assertDoesNotThrow(() -> client.getInsertedReceiptDocuments("scsdf", 100)); + assertNotNull(result); } @Test void getIoMessage_Success() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); - IOMessage ioMessage = new IOMessage(); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.of(ioMessage)); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIOMessageIterable); + when(mockIOMessageIterable.stream()).thenReturn(mockIOMessageStream); + when(mockIOMessageStream.findFirst()).thenReturn(Optional.of(IOMessage.builder().id(IO_MESSAGE_ID).build())); + + IOMessage result = assertDoesNotThrow(() -> sut.getIoMessage(IO_MESSAGE_ID)); - Assertions.assertDoesNotThrow(() -> client.getIoMessage("scsdf")); + assertEquals(IO_MESSAGE_ID, result.getId()); } @Test void getIoMessage_NotFound() { - CosmosClient mockClient = mock(CosmosClient.class); - - CosmosDatabase mockDatabase = mock(CosmosDatabase.class); - CosmosContainer mockContainer = mock(CosmosContainer.class); - - Iterator mockIterator = mock(Iterator.class); - - CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class); - when(mockIterable.stream()).thenAnswer(invocation -> Stream.empty()); - - when(mockIterable.iterator()).thenReturn(mockIterator); - - when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIterable); + when(cosmosClientMock.getDatabase(any())).thenReturn(mockDatabase); when(mockDatabase.getContainer(any())).thenReturn(mockContainer); - when(mockClient.getDatabase(any())).thenReturn(mockDatabase); - ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient); + when(mockContainer.queryItems(anyString(), any(), eq(IOMessage.class))).thenReturn(mockIOMessageIterable); + when(mockIOMessageIterable.stream()).thenReturn(mockIOMessageStream); + when(mockIOMessageStream.findFirst()).thenReturn(Optional.empty()); + + IoMessageNotFoundException e = + assertThrows(IoMessageNotFoundException.class, () -> sut.getIoMessage(IO_MESSAGE_ID)); - Assertions.assertThrows(IoMessageNotFoundException.class, () -> client.getIoMessage("asdf")); + assertNotNull(e); } } \ No newline at end of file From 5305518c52c87e631924271f3839a39308238976 Mon Sep 17 00:00:00 2001 From: giomella Date: Wed, 14 Jan 2026 17:41:39 +0100 Subject: [PATCH 32/38] [PAGOPA-3343] refactor to reduce code coupling --- .../http/RecoverFailedCartReceipt.java | 24 +++--- .../helpdesk/http/RecoverFailedReceipt.java | 2 +- .../http/RecoverFailedReceiptMassive.java | 20 ----- .../http/RecoverNotNotifiedCartReceipt.java | 14 ++-- .../model/MassiveCartRecoverResult.java | 4 + .../datastore/model/MassiveRecoverResult.java | 4 + .../service/CartReceiptCosmosService.java | 40 ++++++++++ .../service/ReceiptCosmosService.java | 57 +------------ .../impl/CartReceiptCosmosServiceImpl.java | 66 +++++++++++++++- .../service/impl/HelpdeskServiceImpl.java | 12 ++- .../impl/ReceiptCosmosServiceImpl.java | 79 ------------------- 11 files changed, 144 insertions(+), 178 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index 81f5d167..a031b208 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -18,10 +18,10 @@ import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,19 +38,19 @@ public class RecoverFailedCartReceipt { private final Logger logger = LoggerFactory.getLogger(RecoverFailedCartReceipt.class); - private final ReceiptCosmosService receiptCosmosService; + private final CartReceiptCosmosService cartReceiptCosmosService; private final HelpdeskService helpdeskService; public RecoverFailedCartReceipt() { - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); this.helpdeskService = new HelpdeskServiceImpl(); } RecoverFailedCartReceipt( - ReceiptCosmosService receiptCosmosService, + CartReceiptCosmosService cartReceiptCosmosService, HelpdeskService helpdeskService ) { - this.receiptCosmosService = receiptCosmosService; + this.cartReceiptCosmosService = cartReceiptCosmosService; this.helpdeskService = helpdeskService; } @@ -97,7 +97,7 @@ public HttpResponseMessage run( CartForReceipt existingCart; try { - existingCart = this.receiptCosmosService.getCart(cartId); + existingCart = this.cartReceiptCosmosService.getCart(cartId); } catch (CartNotFoundException e) { String errMsg = "Cart receipt not found with the provided cart id"; logger.error(errMsg, e); @@ -114,9 +114,9 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } - CartForReceipt cartForReceipt; + CartForReceipt cart; try { - cartForReceipt = this.helpdeskService.recoverFailedCart(existingCart); + cart = this.helpdeskService.recoverFailedCart(existingCart); } catch (BizEventUnprocessableEntityException e) { logger.error(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); @@ -128,11 +128,11 @@ public HttpResponseMessage run( return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } - if (!isCartStatusValid(cartForReceipt)) { + if (!isCartStatusValid(cart)) { String errMsg = String.format("Recover failed for cart with id %s. Reason: %s", - cartId, cartForReceipt.getReasonErr().getMessage()); + cartId, cart.getReasonErr() != null ? cart.getReasonErr().getMessage() : "Detail unavailable"); logger.error(errMsg); - documentdb.setValue(cartForReceipt); + documentdb.setValue(cart); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, errMsg); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 0f1d5bba..0ddba3e8 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -125,7 +125,7 @@ public HttpResponseMessage run( if (!isReceiptStatusValid(receipt)) { String errMsg = String.format("Recover failed for receipt with id %s. Reason: %s", - eventId, receipt.getReasonErr().getMessage()); + eventId, receipt.getReasonErr() != null ? receipt.getReasonErr().getMessage() : "Detail unavailable"); logger.error(errMsg); documentdb.setValue(receipt); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, errMsg); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index 79bc9c96..a7d08ab0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -114,24 +114,4 @@ public HttpResponseMessage run( .body(responseMsg) .build(); } - - private ReceiptStatusType validateStatusParam(String statusParam) throws InvalidParameterException { - if (statusParam == null) { - throw new InvalidParameterException("Please pass a status to recover"); - } - - ReceiptStatusType status; - try { - status = ReceiptStatusType.valueOf(statusParam); - } catch (IllegalArgumentException e) { - throw new InvalidParameterException("Please pass a valid status to recover", e); - } - - if (!status.isAFailedDatastoreStatus()) { - String message = String.format("The provided status %s is not among the processable" + - "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); - throw new InvalidParameterException(message); - } - return status; - } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java index 45cd215b..7b8f65aa 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java @@ -15,10 +15,10 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.impl.CartReceiptCosmosServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.service.impl.HelpdeskServiceImpl; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,16 +34,16 @@ public class RecoverNotNotifiedCartReceipt { private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedCartReceipt.class); - private final ReceiptCosmosService receiptCosmosService; + private final CartReceiptCosmosService cartReceiptCosmosService; private final HelpdeskService helpdeskService; public RecoverNotNotifiedCartReceipt() { - this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); this.helpdeskService = new HelpdeskServiceImpl(); } - RecoverNotNotifiedCartReceipt(ReceiptCosmosService receiptCosmosService, HelpdeskService helpdeskService) { - this.receiptCosmosService = receiptCosmosService; + RecoverNotNotifiedCartReceipt(CartReceiptCosmosService cartReceiptCosmosService, HelpdeskService helpdeskService) { + this.cartReceiptCosmosService = cartReceiptCosmosService; this.helpdeskService = helpdeskService; } @@ -78,7 +78,7 @@ public HttpResponseMessage run( CartForReceipt cart; try { - cart = this.receiptCosmosService.getCart(cartId); + cart = this.cartReceiptCosmosService.getCart(cartId); } catch (CartNotFoundException e) { String errMsg = String.format("Unable to retrieve the cart receipt with id %s", cartId); logger.error("[{}] {}", context.getFunctionName(), errMsg, e); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java index 4f4405f8..5f8499d2 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveCartRecoverResult.java @@ -1,12 +1,16 @@ package it.gov.pagopa.receipt.pdf.datastore.model; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.List; @Data +@AllArgsConstructor +@NoArgsConstructor @Builder public class MassiveCartRecoverResult { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java index 68faa8c4..7ec3c4f0 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/model/MassiveRecoverResult.java @@ -1,12 +1,16 @@ package it.gov.pagopa.receipt.pdf.datastore.model; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.List; @Data +@AllArgsConstructor +@NoArgsConstructor @Builder public class MassiveRecoverResult { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java index 5d6a6573..f7f62cff 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/CartReceiptCosmosService.java @@ -1,6 +1,9 @@ package it.gov.pagopa.receipt.pdf.datastore.service; +import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; @@ -18,4 +21,41 @@ public interface CartReceiptCosmosService { */ CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundException; + /** + * Retrieve the not notified cart receipt with the provided {@link CartStatusType} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the cart receipts + * @return cart receipt documents + */ + Iterable> getNotNotifiedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ); + + /** + * Retrieve the failed cart receipt with the provided {@link CartStatusType} status + * + * @param continuationToken Paged query continuation token + * @param pageSize the page size + * @param statusType the status of the receipts + * @return receipt documents + */ + Iterable> getFailedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ); + + + /** + * Retrieve the cart with the provided id + * + * @param cartId the cart id + * @return the cart + * @throws CartNotFoundException if the cart was not found or the retrieved cart is null + */ + CartForReceipt getCart(String cartId) throws CartNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java index 384b9bc3..3695ef6c 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/ReceiptCosmosService.java @@ -2,14 +2,9 @@ import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; /** @@ -39,8 +34,8 @@ public interface ReceiptCosmosService { * Retrieve the not notified receipt with the provided {@link ReceiptStatusType} status * * @param continuationToken Paged query continuation token - * @param pageSize the page size - * @param statusType the status of the receipts + * @param pageSize the page size + * @param statusType the status of the receipts * @return receipt documents */ Iterable> getNotNotifiedReceiptByStatus( @@ -49,61 +44,17 @@ Iterable> getNotNotifiedReceiptByStatus( ReceiptStatusType statusType ); - /** - * Retrieve the not notified cart receipt with the provided {@link CartStatusType} status - * - * @param continuationToken Paged query continuation token - * @param pageSize the page size - * @param statusType the status of the cart receipts - * @return cart receipt documents - */ - Iterable> getNotNotifiedCartReceiptByStatus( - String continuationToken, - Integer pageSize, - CartStatusType statusType - ); - /** * Retrieve the failed receipt with the provided {@link ReceiptStatusType} status * * @param continuationToken Paged query continuation token - * @param pageSize the page size - * @param statusType the status of the receipts - * @return receipt documents - */ - Iterable> getFailedReceiptByStatus( - String continuationToken, - Integer pageSize, - ReceiptStatusType statusType - ); - - /** - * Retrieve the failed cart receipt with the provided {@link CartStatusType} status - * - * @param continuationToken Paged query continuation token * @param pageSize the page size * @param statusType the status of the receipts * @return receipt documents */ - Iterable> getFailedCartReceiptByStatus( + Iterable> getFailedReceiptByStatus( String continuationToken, Integer pageSize, - CartStatusType statusType + ReceiptStatusType statusType ); - - /** - * @param messageId - * @return - */ - IOMessage getReceiptMessage(String messageId) throws IoMessageNotFoundException; - - - /** - * Retrieve the cart with the provided id - * - * @param cartId the cart id - * @return the cart - * @throws CartNotFoundException if the cart was not found or the retrieved cart is null - */ - CartForReceipt getCart(String cartId) throws CartNotFoundException; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java index 89012320..dd05b7c9 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImpl.java @@ -1,12 +1,14 @@ package it.gov.pagopa.receipt.pdf.datastore.service.impl; +import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartReceiptsCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; - public class CartReceiptCosmosServiceImpl implements CartReceiptCosmosService { private final CartReceiptsCosmosClient cartReceiptsCosmosClient; @@ -19,7 +21,6 @@ public CartReceiptCosmosServiceImpl() { this.cartReceiptsCosmosClient = cartReceiptsCosmosClient; } - /** * {@inheritDoc} */ @@ -39,4 +40,65 @@ public CartReceiptError getCartReceiptError(String cartId) throws CartNotFoundEx } return receipt; } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getNotNotifiedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(CartStatusType.IO_ERROR_TO_NOTIFY)) { + return this.cartReceiptsCosmosClient.getIOErrorToNotifyCartReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(CartStatusType.GENERATED)) { + return this.cartReceiptsCosmosClient.getGeneratedCartReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving not notified receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable> getFailedCartReceiptByStatus( + String continuationToken, + Integer pageSize, + CartStatusType statusType + ) { + if (statusType == null) { + throw new IllegalArgumentException("at least one status must be specified"); + } + if (statusType.equals(CartStatusType.FAILED) || statusType.equals(CartStatusType.NOT_QUEUE_SENT)) { + return this.cartReceiptsCosmosClient.getFailedCartReceiptDocuments(continuationToken, pageSize); + } + if (statusType.equals(CartStatusType.INSERTED)) { + return this.cartReceiptsCosmosClient.getInsertedCartReceiptDocuments(continuationToken, pageSize); + } + String errMsg = String.format("Unexpected status for retrieving failed receipt: %s", statusType); + throw new IllegalStateException(errMsg); + } + + @Override + public CartForReceipt getCart(String cartId) throws CartNotFoundException { + CartForReceipt cartForReceipt; + try { + cartForReceipt = this.cartReceiptsCosmosClient.getCartItem(cartId); + } catch (CartNotFoundException e) { + String errorMsg = String.format("Receipt not found with the biz-event id %s", cartId); + throw new CartNotFoundException(errorMsg, e); + } + + if (cartForReceipt == null) { + String errorMsg = String.format("Receipt retrieved with the biz-event id %s is null", cartId); + throw new CartNotFoundException(errorMsg); + } + return cartForReceipt; + } } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index f514a79d..661ea49a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -16,6 +16,7 @@ import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.utils.BizEventToReceiptUtils; @@ -35,21 +36,24 @@ public class HelpdeskServiceImpl implements HelpdeskService { private final Logger logger = LoggerFactory.getLogger(HelpdeskServiceImpl.class); private final ReceiptCosmosService receiptCosmosService; + private final CartReceiptCosmosService cartReceiptCosmosService; private final BizEventToReceiptService bizEventToReceiptService; private final BizEventCosmosClient bizEventCosmosClient; public HelpdeskServiceImpl() { - this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); this.receiptCosmosService = new ReceiptCosmosServiceImpl(); + this.cartReceiptCosmosService = new CartReceiptCosmosServiceImpl(); + this.bizEventToReceiptService = new BizEventToReceiptServiceImpl(); this.bizEventCosmosClient = BizEventCosmosClientImpl.getInstance(); } public HelpdeskServiceImpl( - ReceiptCosmosService receiptCosmosService, + ReceiptCosmosService receiptCosmosService, CartReceiptCosmosService cartReceiptCosmosService, BizEventToReceiptService bizEventToReceiptService, BizEventCosmosClient bizEventCosmosClient ) { this.receiptCosmosService = receiptCosmosService; + this.cartReceiptCosmosService = cartReceiptCosmosService; this.bizEventToReceiptService = bizEventToReceiptService; this.bizEventCosmosClient = bizEventCosmosClient; } @@ -168,7 +172,7 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { String continuationToken = null; do { Iterable> feedResponseIterator = - this.receiptCosmosService.getFailedCartReceiptByStatus(continuationToken, 100, status); + this.cartReceiptCosmosService.getFailedCartReceiptByStatus(continuationToken, 100, status); for (FeedResponse page : feedResponseIterator) { for (CartForReceipt cart : page.getResults()) { @@ -224,7 +228,7 @@ public List massiveRecoverNoNotified(CartStatusType status) { String continuationToken = null; do { Iterable> feedResponseIterator = - this.receiptCosmosService.getNotNotifiedCartReceiptByStatus(continuationToken, 100, status); + this.cartReceiptCosmosService.getNotNotifiedCartReceiptByStatus(continuationToken, 100, status); for (FeedResponse page : feedResponseIterator) { for (CartForReceipt cart : page.getResults()) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java index 3af26878..15fb5046 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImpl.java @@ -89,28 +89,6 @@ public Iterable> getNotNotifiedReceiptByStatus( throw new IllegalStateException(errMsg); } - /** - * {@inheritDoc} - */ - @Override - public Iterable> getNotNotifiedCartReceiptByStatus( - String continuationToken, - Integer pageSize, - CartStatusType statusType - ) { - if (statusType == null) { - throw new IllegalArgumentException("at least one status must be specified"); - } - if (statusType.equals(CartStatusType.IO_ERROR_TO_NOTIFY)) { - return this.cartReceiptsCosmosClient.getIOErrorToNotifyCartReceiptDocuments(continuationToken, pageSize); - } - if (statusType.equals(CartStatusType.GENERATED)) { - return this.cartReceiptsCosmosClient.getGeneratedCartReceiptDocuments(continuationToken, pageSize); - } - String errMsg = String.format("Unexpected status for retrieving not notified receipt: %s", statusType); - throw new IllegalStateException(errMsg); - } - /** * {@inheritDoc} */ @@ -132,61 +110,4 @@ public Iterable> getFailedReceiptByStatus( String errMsg = String.format("Unexpected status for retrieving failed receipt: %s", statusType); throw new IllegalStateException(errMsg); } - - /** - * {@inheritDoc} - */ - @Override - public Iterable> getFailedCartReceiptByStatus( - String continuationToken, - Integer pageSize, - CartStatusType statusType - ) { - if (statusType == null) { - throw new IllegalArgumentException("at least one status must be specified"); - } - if (statusType.equals(CartStatusType.FAILED) || statusType.equals(CartStatusType.NOT_QUEUE_SENT)) { - return this.cartReceiptsCosmosClient.getFailedCartReceiptDocuments(continuationToken, pageSize); - } - if (statusType.equals(CartStatusType.INSERTED)) { - return this.cartReceiptsCosmosClient.getInsertedCartReceiptDocuments(continuationToken, pageSize); - } - String errMsg = String.format("Unexpected status for retrieving failed receipt: %s", statusType); - throw new IllegalStateException(errMsg); - } - - @Override - public IOMessage getReceiptMessage(String messageId) throws IoMessageNotFoundException { - IOMessage message; - try { - message = this.receiptCosmosClient.getIoMessage(messageId); - } catch (IoMessageNotFoundException e) { - String errorMsg = String.format("Receipt Message to IO not found with the message id %s", messageId); - throw new IoMessageNotFoundException(errorMsg, e); - } - - if (message == null) { - String errorMsg = String.format("Receipt retrieved with the message id %s is null", messageId); - throw new IoMessageNotFoundException(errorMsg); - } - return message; - } - - @Override - public CartForReceipt getCart(String cartId) throws CartNotFoundException { - CartForReceipt cartForReceipt; - try { - cartForReceipt = this.cartReceiptsCosmosClient.getCartItem(cartId); - } catch (CartNotFoundException e) { - String errorMsg = String.format("Receipt not found with the biz-event id %s", cartId); - throw new CartNotFoundException(errorMsg, e); - } - - if (cartForReceipt == null) { - String errorMsg = String.format("Receipt retrieved with the biz-event id %s is null", cartId); - throw new CartNotFoundException(errorMsg); - } - return cartForReceipt; - } - } \ No newline at end of file From 9857f54ef851e1fd22576b7824b21e5cd8bee72d Mon Sep 17 00:00:00 2001 From: giomella Date: Wed, 14 Jan 2026 17:43:50 +0100 Subject: [PATCH 33/38] [PAGOPA-3343] added and updated unit tests --- .../http/CartReceiptToReviewedTest.java | 107 +++++ .../helpdesk/http/ReceiptToReviewedTest.java | 85 ++-- .../RecoverFailedCartReceiptMassiveTest.java | 159 +++++++ .../http/RecoverFailedCartReceiptTest.java | 237 +++++++++++ .../http/RecoverFailedReceiptMassiveTest.java | 271 +++--------- .../http/RecoverFailedReceiptTest.java | 400 ++++-------------- ...overNotNotifiedCartReceiptMassiveTest.java | 149 +++++++ .../RecoverNotNotifiedCartReceiptTest.java | 136 ++++++ .../RecoverNotNotifiedReceiptMassiveTest.java | 230 +++------- .../http/RecoverNotNotifiedReceiptTest.java | 162 +++---- ...RecoverFailedCartReceiptScheduledTest.java | 106 +++++ .../RecoverFailedReceiptScheduledTest.java | 184 +++----- ...erNotNotifiedCartReceiptScheduledTest.java | 97 +++++ ...ecoverNotNotifiedReceiptScheduledTest.java | 144 +++---- .../CartReceiptCosmosServiceImplTest.java | 51 +++ .../impl/ReceiptCosmosServiceImplTest.java | 65 +-- 16 files changed, 1446 insertions(+), 1137 deletions(-) create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewedTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassiveTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassiveTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduledTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduledTest.java create mode 100644 src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewedTest.java new file mode 100644 index 00000000..89e29463 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewedTest.java @@ -0,0 +1,107 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; +import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CartReceiptToReviewedTest { + + private static final String CART_ID = "cart_id"; + + @Mock + private ExecutionContext executionContextMock; + @Mock + private CartReceiptCosmosService cartReceiptCosmosServiceMock; + @Captor + private ArgumentCaptor cartErrorCaptor; + @Mock + private HttpRequestMessage> request; + @Spy + private OutputBinding documentdb; + + @InjectMocks + private CartReceiptToReviewed function; + + @BeforeEach + void setUp() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(request).createResponseBuilder(any(HttpStatus.class)); + } + + @Test + @SneakyThrows + void requestWithValidBizEventSaveReceiptErrorInReviewed() { + CartReceiptError receiptError = CartReceiptError.builder() + .id(CART_ID) + .status(ReceiptErrorStatusType.TO_REVIEW) + .build(); + when(cartReceiptCosmosServiceMock.getCartReceiptError(CART_ID)).thenReturn(receiptError); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, CART_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.OK, response.getStatus()); + + verify(documentdb).setValue(cartErrorCaptor.capture()); + CartReceiptError captured = cartErrorCaptor.getValue(); + assertEquals(CART_ID, captured.getId()); + assertEquals(ReceiptErrorStatusType.REVIEWED, captured.getStatus()); + } + + @Test + @SneakyThrows + void requestWithValidBizEventIdButReceiptNotFound() { + when(cartReceiptCosmosServiceMock.getCartReceiptError(CART_ID)).thenThrow(CartNotFoundException.class); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, CART_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); + + verifyNoInteractions(documentdb); + } + + @Test + @SneakyThrows + void requestWithValidBizEventIdButReceiptWrongStatusReturnsInternalServerError() { + when(cartReceiptCosmosServiceMock.getCartReceiptError(CART_ID)).thenReturn(CartReceiptError.builder() + .id(CART_ID) + .status(ReceiptErrorStatusType.REQUEUED) + .build()); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, CART_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + + verifyNoInteractions(documentdb); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java index 7a5d2e56..65e27c19 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewedTest.java @@ -1,60 +1,74 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptErrorStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.service.impl.ReceiptCosmosServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ReceiptToReviewedTest { private static final String BIZ_EVENT_ID = "valid_biz_event_id"; - private final ExecutionContext executionContextMock = mock(ExecutionContext.class); - private ReceiptToReviewed function; + + @Mock + private ExecutionContext executionContextMock; @Mock private ReceiptCosmosServiceImpl receiptCosmosService; @Captor private ArgumentCaptor receiptErrorCaptor; @Mock - HttpRequestMessage> request; - @SuppressWarnings("unchecked") - OutputBinding documentdb = (OutputBinding) spy(OutputBinding.class); + private HttpRequestMessage> request; + @Spy + private OutputBinding documentdb; - @Test - void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFoundException { + @InjectMocks + private ReceiptToReviewed function; + + @BeforeEach + void setUp() { doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(request).createResponseBuilder(any(HttpStatus.class)); + } + @Test + void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFoundException { ReceiptError receiptError = ReceiptError.builder() .bizEventId(BIZ_EVENT_ID) .status(ReceiptErrorStatusType.TO_REVIEW) .build(); when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(receiptError); - function = spy(new ReceiptToReviewed(receiptCosmosService)); - // test execution - AtomicReference responseMessage = new AtomicReference<>(); - assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb,executionContextMock ))); - assertEquals(HttpStatus.OK, responseMessage.get().getStatus()); + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.OK, response.getStatus()); verify(documentdb).setValue(receiptErrorCaptor.capture()); ReceiptError captured = receiptErrorCaptor.getValue(); @@ -64,58 +78,25 @@ void requestWithValidBizEventSaveReceiptErrorInReviewed() throws ReceiptNotFound @Test void requestWithValidBizEventIdButReceiptNotFound() throws ReceiptNotFoundException { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(request).createResponseBuilder(any(HttpStatus.class)); - when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - function = spy(new ReceiptToReviewed(receiptCosmosService)); - // test execution - AtomicReference responseMessage = new AtomicReference<>(); - assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock))); - assertEquals(HttpStatus.NOT_FOUND, responseMessage.get().getStatus()); + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); verifyNoInteractions(documentdb); } @Test void requestWithValidBizEventIdButReceiptWrongStatusReturnsInternalServerError() throws ReceiptNotFoundException { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(request).createResponseBuilder(any(HttpStatus.class)); - when(receiptCosmosService.getReceiptError(BIZ_EVENT_ID)).thenReturn(ReceiptError.builder() .bizEventId(BIZ_EVENT_ID) .status(ReceiptErrorStatusType.REQUEUED) .build()); - function = spy(new ReceiptToReviewed(receiptCosmosService)); - - // test execution - AtomicReference responseMessage = new AtomicReference<>(); - assertDoesNotThrow(() -> responseMessage.set(function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock))); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, responseMessage.get().getStatus()); - - verifyNoInteractions(documentdb); - } - - @Test - void requestWithoutEventIdReturnsBadRequest() { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(request).createResponseBuilder(any(HttpStatus.class)); - - function = spy(new ReceiptToReviewed(receiptCosmosService)); - // test execution - AtomicReference responseMessage = new AtomicReference<>(); - assertDoesNotThrow(() -> responseMessage.set(function.run(request, null, documentdb, executionContextMock))); - assertEquals(HttpStatus.BAD_REQUEST, responseMessage.get().getStatus()); + HttpResponseMessage response = assertDoesNotThrow(() -> function.run(request, BIZ_EVENT_ID, documentdb, executionContextMock)); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); verifyNoInteractions(documentdb); } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassiveTest.java new file mode 100644 index 00000000..8bba6168 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassiveTest.java @@ -0,0 +1,159 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RecoverFailedCartReceiptMassiveTest { + + @Mock + private ExecutionContext contextMock; + @Mock + private HelpdeskService helpdeskServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Captor + private ArgumentCaptor> cartCaptor; + + @Spy + private OutputBinding> documentdb; + + @InjectMocks + private RecoverFailedCartReceiptMassive sut; + + @BeforeEach + void openMocks() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverFailedCartReceiptMassiveSuccess(CartStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(new MassiveCartRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(any(CartStatusType.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptMassiveFailParamNull() { + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(CartStatusType.class)); + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptMassiveFailParamNotAStatus() { + doReturn(Collections.singletonMap("status", "random")).when(requestMock).getQueryParameters(); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(CartStatusType.class)); + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverFailedCartReceiptMassiveFailStatusParamUnprocessable(CartStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); + + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(CartStatusType.class)); + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptMassiveFailRecoverError() { + MassiveCartRecoverResult recoverResult = MassiveCartRecoverResult.builder() + .successCounter(1) + .errorCounter(1) + .failedCartList(List.of(new CartForReceipt())) + .build(); + + doReturn(Collections.singletonMap("status", CartStatusType.FAILED.name())).when(requestMock).getQueryParameters(); + doReturn(recoverResult).when(helpdeskServiceMock).massiveRecoverByStatus(any(CartStatusType.class)); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(cartCaptor.capture()); + assertNotNull(cartCaptor.getValue()); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptTest.java new file mode 100644 index 00000000..97adc7ab --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptTest.java @@ -0,0 +1,237 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RecoverFailedCartReceiptTest { + + private static final String CART_ID = "a valid id"; + + @Mock + private ExecutionContext contextMock; + @Mock + private CartReceiptCosmosService cartReceiptCosmosServiceMock; + @Mock + private HttpRequestMessage> requestMock; + @Mock + private HelpdeskService helpdeskServiceMock; + + @Captor + private ArgumentCaptor cartCaptor; + + @Spy + private OutputBinding documentdb; + + @InjectMocks + private RecoverFailedCartReceipt sut; + + @BeforeEach + void setup() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverFailedCartReceiptSuccess(CartStatusType status) { + CartForReceipt failedReceipt = createFailedCart(); + failedReceipt.setStatus(status); + CartForReceipt recovered = CartForReceipt.builder().status(CartStatusType.INSERTED).build(); + + doReturn(failedReceipt).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doReturn(recovered).when(helpdeskServiceMock).recoverFailedCart(failedReceipt); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailNoReceiptFound() { + doThrow(CartNotFoundException.class).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); + assertNotNull(response.getBody()); + + verify(helpdeskServiceMock, never()).recoverFailedCart(any()); + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverFailedCartReceiptFailReceiptWithUnexpectedStatus(CartStatusType status) { + CartForReceipt failedReceipt = createFailedCart(); + failedReceipt.setStatus(status); + + doReturn(failedReceipt).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); + + verify(helpdeskServiceMock, never()).recoverFailedCart(any()); + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailBizEvenUnprocessable() { + CartForReceipt failedCart = createFailedCart(); + + doReturn(failedCart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doThrow(BizEventUnprocessableEntityException.class).when(helpdeskServiceMock).recoverFailedCart(failedCart); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailBizEvenInvalid() { + CartForReceipt failedCart = createFailedCart(); + + doReturn(failedCart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doThrow(BizEventBadRequestException.class).when(helpdeskServiceMock).recoverFailedCart(failedCart); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailPDVTokenizerError() { + CartForReceipt failedCart = createFailedCart(); + + doReturn(failedCart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doThrow(PDVTokenizerException.class).when(helpdeskServiceMock).recoverFailedCart(failedCart); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailJsonProcessingError() { + CartForReceipt failedCart = createFailedCart(); + + doReturn(failedCart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doThrow(JsonProcessingException.class).when(helpdeskServiceMock).recoverFailedCart(failedCart); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptFailRecoverError() { + CartForReceipt failedCart = createFailedCart(); + CartForReceipt recovered = CartForReceipt.builder().status(CartStatusType.FAILED).build(); + + doReturn(failedCart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doReturn(recovered).when(helpdeskServiceMock).recoverFailedCart(failedCart); + + // test execution + HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, CART_ID, documentdb, contextMock)); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(cartCaptor.capture()); + assertNotNull(cartCaptor.getValue()); + } + + private CartForReceipt createFailedCart() { + return CartForReceipt.builder() + .status(CartStatusType.FAILED) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java index dc49cd56..907ca15e 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassiveTest.java @@ -1,52 +1,49 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.azure.cosmos.models.ModelBridgeInternal; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import lombok.SneakyThrows; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.time.LocalDateTime; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class RecoverFailedReceiptMassiveTest { - private final String tokenizedDebtorFiscalCode = "tokenizedDebtorFiscalCode"; - private final String tokenizedPayerFiscalCode = "tokenizedPayerFiscalCode"; - private final String eventId = "a valid id"; - @Mock private ExecutionContext contextMock; @Mock - private ReceiptCosmosService receiptCosmosServiceMock; - @Mock - private BizEventCosmosClientImpl bizEventCosmosClientMock; - @Mock - private BizEventToReceiptService bizEventToReceiptServiceMock; + private HelpdeskService helpdeskServiceMock; @Mock private HttpRequestMessage> requestMock; @@ -57,50 +54,23 @@ class RecoverFailedReceiptMassiveTest { @Spy private OutputBinding> documentdb; - private AutoCloseable closeable; - + @InjectMocks private RecoverFailedReceiptMassive sut; @BeforeEach void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - sut = spy(new RecoverFailedReceiptMassive(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); - } - - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); - } - - @Test - void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); - - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) - .thenReturn(Collections.singletonList(ModelBridgeInternal - .createFeedResponse(Collections.singletonList(createFailedReceipt()), - Collections.emptyMap()))); - - when(bizEventCosmosClientMock.getBizEventDocument(eventId)) - .thenReturn(generateValidBizEvent(eventId)); - - Answer successAnswer = invocation -> { - // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData - EventData eventDataArg = invocation.getArgument(2); - - // simulate tokenization - eventDataArg.setPayerFiscalCode(tokenizedPayerFiscalCode); - eventDataArg.setDebtorFiscalCode(tokenizedDebtorFiscalCode); - return null; - }; - - doAnswer(successAnswer).when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); - doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverFailedReceiptMassiveSuccess(ReceiptStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(new MassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(any(ReceiptStatusType.class)); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); @@ -108,43 +78,30 @@ void recoverFailedReceiptMassiveSuccess() throws BizEventNotFoundException, PDVT // test assertion assertNotNull(response); assertEquals(HttpStatus.OK, response.getStatus()); - assertEquals("Recovered 1 receipts", response.getBody()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test @SneakyThrows - void recoverFailedReceiptMassiveFailMissingStatusParam() { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - + void recoverFailedReceiptMassiveFailParamNull() { // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); // test assertion assertNotNull(response); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(ReceiptStatusType.class)); verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test @SneakyThrows - void recoverFailedReceiptMassiveFailInvalidStatusParam() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", "INVALID_STATUS")); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + void recoverFailedReceiptMassiveFailParamNotAStatus() { + doReturn(Collections.singletonMap("status", "random")).when(requestMock).getQueryParameters(); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); @@ -152,91 +109,41 @@ void recoverFailedReceiptMassiveFailInvalidStatusParam() { // test assertion assertNotNull(response); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(ReceiptStatusType.class)); verify(documentdb, never()).setValue(receiptCaptor.capture()); } - @Test + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.EXCLUDE) @SneakyThrows - void recoverFailedReceiptMassivePartialOK() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); - - List receiptList = new ArrayList<>(); - Receipt receipt1 = createFailedReceipt(); - receiptList.add(receipt1); - - Receipt receipt2 = createFailedReceipt(); - receipt2.setStatus(ReceiptStatusType.INSERTED); - receipt2.setEventData(null); - receiptList.add(receipt2); - - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) - .thenReturn(Collections.singletonList(ModelBridgeInternal - .createFeedResponse(receiptList, Collections.emptyMap()))); - - Answer successAnswer = invocation -> { - // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData - EventData eventDataArg = invocation.getArgument(2); - - // simulate tokenization - eventDataArg.setPayerFiscalCode(tokenizedPayerFiscalCode); - eventDataArg.setDebtorFiscalCode(tokenizedDebtorFiscalCode); - return null; - }; - - doThrow(PDVTokenizerException.class) // 1. first call: error - .doAnswer(successAnswer) // 2. next call: success - .when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); - - String bizOk = "a valid id 2"; - when(bizEventCosmosClientMock.getBizEventDocument(anyString())) - .thenReturn(generateValidBizEvent(eventId)) - .thenReturn(generateValidBizEvent(bizOk)); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - + void recoverFailedReceiptMassiveFailStatusParamUnprocessable(ReceiptStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); // test assertion assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.MULTI_STATUS.value(), problemJson.getStatus()); // 207 - assertEquals("Partial OK", problemJson.getTitle()); - assertTrue(problemJson.getDetail().contains("Recovered 1 receipts but 1 encountered an error")); + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(ReceiptStatusType.class)); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test @SneakyThrows - void recoverFailedReceiptMassiveFailNoSuchElementInIterator() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); - - Iterable iterableMock = mock(Iterable.class); - Iterator iteratorMock = mock(Iterator.class); - when(iterableMock.iterator()).thenReturn(iteratorMock); - when(iteratorMock.hasNext()).thenThrow(new NoSuchElementException("")); - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), any())) - .thenReturn(iterableMock); + void recoverFailedReceiptMassiveFailRecoverError() { + MassiveRecoverResult recoverResult = MassiveRecoverResult.builder() + .successCounter(1) + .errorCounter(1) + .failedReceiptList(List.of(new Receipt())) + .build(); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(Collections.singletonMap("status", ReceiptStatusType.FAILED.name())).when(requestMock).getQueryParameters(); + doReturn(recoverResult).when(helpdeskServiceMock).massiveRecoverByStatus(any(ReceiptStatusType.class)); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, documentdb, contextMock)); @@ -244,59 +151,9 @@ void recoverFailedReceiptMassiveFailNoSuchElementInIterator() { // test assertion assertNotNull(response); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertNotNull(response.getBody()); - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - - verify(documentdb, never()).setValue(receiptCaptor.capture()); - } - - private BizEvent generateValidBizEvent(String eventId){ - BizEvent item = new BizEvent(); - - Payer payer = new Payer(); - payer.setEntityUniqueIdentifierValue("AAAAAA00A00A000P"); - Debtor debtor = new Debtor(); - debtor.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); - - TransactionDetails transactionDetails = new TransactionDetails(); - Transaction transaction = new Transaction(); - transaction.setCreationDate(String.valueOf(LocalDateTime.now())); - transactionDetails.setTransaction(transaction); - - PaymentInfo paymentInfo = new PaymentInfo(); - paymentInfo.setTotalNotice("1"); - - item.setEventStatus(BizEventStatusType.DONE); - item.setId(eventId); - item.setPayer(payer); - item.setDebtor(debtor); - item.setTransactionDetails(transactionDetails); - item.setPaymentInfo(paymentInfo); - - return item; - } - - private Receipt createFailedReceipt() { - Receipt receipt = new Receipt(); - - receipt.setId("a valid id"); - receipt.setEventId("a valid id"); - receipt.setVersion("1"); - - receipt.setStatus(ReceiptStatusType.FAILED); - EventData eventData = new EventData(); - eventData.setDebtorFiscalCode(tokenizedDebtorFiscalCode); - eventData.setPayerFiscalCode(tokenizedPayerFiscalCode); - receipt.setEventData(eventData); - - CartItem item = new CartItem(); - List cartItems = Collections.singletonList(item); - eventData.setCart(cartItems); - - return receipt; + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java index 6ea049dd..5a86a0db 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptTest.java @@ -1,76 +1,62 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.azure.core.http.rest.Response; -import com.azure.cosmos.models.CosmosItemResponse; -import com.azure.storage.queue.models.SendMessageResult; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.ReceiptQueueClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.CartQueueClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventBadRequestException; import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventUnprocessableEntityException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.PDVTokenizerServiceRetryWrapper; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import it.gov.pagopa.receipt.pdf.datastore.service.impl.BizEventToReceiptServiceImpl; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; import lombok.SneakyThrows; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class RecoverFailedReceiptTest { - private final String PAYER_FISCAL_CODE = "AAAAAA00A00A000D"; - private final String DEBTOR_FISCAL_CODE = "AAAAAA00A00A000P"; - private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; - private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; - private final String EVENT_ID = "a valid id"; - - public static final String HTTP_MESSAGE_ERROR = "an error occured"; + private static final String EVENT_ID = "a valid id"; @Mock private ExecutionContext contextMock; @Mock - private PDVTokenizerServiceRetryWrapper pdvTokenizerServiceMock; - @Mock private ReceiptCosmosService receiptCosmosServiceMock; @Mock - private CartReceiptsCosmosClient cartReceiptsCosmosClientMock; - @Mock - private ReceiptQueueClient queueClientMock; - @Mock - private BizEventCosmosClientImpl bizEventCosmosClientMock; - @Mock - private ReceiptCosmosClientImpl receiptCosmosClient; - @Mock private HttpRequestMessage> requestMock; @Mock - private CartQueueClientImpl cartQueueClientMock; + private HelpdeskService helpdeskServiceMock; @Captor private ArgumentCaptor receiptCaptor; @@ -78,216 +64,103 @@ class RecoverFailedReceiptTest { @Spy private OutputBinding documentdb; - private AutoCloseable closeable; - + @InjectMocks private RecoverFailedReceipt sut; @BeforeEach - void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - BizEventToReceiptServiceImpl receiptService = new BizEventToReceiptServiceImpl( - pdvTokenizerServiceMock, - receiptCosmosClient, - cartReceiptsCosmosClientMock, - bizEventCosmosClientMock, - queueClientMock, - cartQueueClientMock - ); - sut = spy(new RecoverFailedReceipt(receiptService, bizEventCosmosClientMock, receiptCosmosServiceMock)); - } - - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); - } - - @Test - @SneakyThrows - void requestOnValidBizEventShouldNotCreateRequest() { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent("1")); - - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - + void setup() { doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); - assertNotNull(response.getBody()); } - @Test - void requestOnValidBizEventAndFailedReceiptShouldResend() throws BizEventNotFoundException, ReceiptNotFoundException, PDVTokenizerException, JsonProcessingException { - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(createFailedReceipt()); - - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent("1")); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverFailedReceiptSuccess(ReceiptStatusType status) { + Receipt failedReceipt = createFailedReceipt(); + failedReceipt.setStatus(status); + Receipt recovered = Receipt.builder().status(ReceiptStatusType.INSERTED).build(); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doReturn(recovered).when(helpdeskServiceMock).recoverFailedReceipt(failedReceipt); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); // test assertion assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); + assertEquals(HttpStatus.OK, response.getStatus()); assertNotNull(response.getBody()); - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.FAILED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test @SneakyThrows - void requestOnValidBizEventAndFailedReceiptWithoutEventDataShouldUpdateWithToken() { - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenReturn(TOKENIZED_DEBTOR_FISCAL_CODE); - - when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(PAYER_FISCAL_CODE)) - .thenReturn(TOKENIZED_PAYER_FISCAL_CODE); - - Receipt receipt = createFailedReceipt(); - receipt.setEventData(null); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - - BizEvent bizEvent = generateValidBizEvent("1"); - bizEvent.getTransactionDetails().getTransaction().setOrigin("IO"); - - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(bizEvent); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + void recoverFailedReceiptFailNoReceiptFound() { + doThrow(ReceiptNotFoundException.class).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); // test assertion - assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentdb).setValue(receiptCaptor.capture()); - Receipt captured = receiptCaptor.getValue(); - assertEquals(ReceiptStatusType.FAILED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(TOKENIZED_DEBTOR_FISCAL_CODE, captured.getEventData().getDebtorFiscalCode()); - assertEquals(TOKENIZED_PAYER_FISCAL_CODE, captured.getEventData().getPayerFiscalCode()); - assertNotNull(captured.getEventData().getCart()); - assertEquals(1, captured.getEventData().getCart().size()); - } - - @Test - void requestWithMissingBizEventOnRequestIdShouldReturnNotFound() throws BizEventNotFoundException { - BizEventNotFoundException exception = new BizEventNotFoundException("BizEvent not found"); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenThrow(exception); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - assertNotNull(response); assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); assertNotNull(response.getBody()); - } - - @Test - void recoverFailedReceiptFailMissingEventId() { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, null, documentdb, contextMock)); - assertNotNull(response); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); + verify(helpdeskServiceMock, never()).recoverFailedReceipt(any()); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } - @Test - void runDiscardedWithEventNotDONE() throws BizEventNotFoundException { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateNotDoneBizEvent()); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"INSERTED", "NOT_QUEUE_SENT", "FAILED"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverFailedReceiptFailReceiptWithUnexpectedStatus(ReceiptStatusType status) { + Receipt failedReceipt = createFailedReceipt(); + failedReceipt.setStatus(status); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); // test assertion assertNotNull(response); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); assertNotNull(response.getBody()); - verifyNoInteractions(receiptCosmosServiceMock); - verifyNoInteractions(queueClientMock); + verify(helpdeskServiceMock, never()).recoverFailedReceipt(any()); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } - @Test - void generateAnonymousDebtorBizEvent() throws BizEventNotFoundException { - BizEvent bizEvent = generateAnonymDebtorBizEvent(); - bizEvent.setPayer(null); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(bizEvent); + @SneakyThrows + void recoverFailedReceiptFailBizEvenUnprocessable() { + Receipt failedReceipt = createFailedReceipt(); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doThrow(BizEventUnprocessableEntityException.class).when(helpdeskServiceMock).recoverFailedReceipt(failedReceipt); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); // test assertion assertNotNull(response); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); assertNotNull(response.getBody()); - verifyNoInteractions(receiptCosmosServiceMock); - verifyNoInteractions(queueClientMock); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test - void runDiscardedWithEventNull() throws BizEventNotFoundException { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(null); + @SneakyThrows + void recoverFailedReceiptFailBizEvenNotFound() { + Receipt failedReceipt = createFailedReceipt(); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doThrow(BizEventNotFoundException.class).when(helpdeskServiceMock).recoverFailedReceipt(failedReceipt); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); @@ -297,19 +170,16 @@ void runDiscardedWithEventNull() throws BizEventNotFoundException { assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); - verifyNoInteractions(receiptCosmosServiceMock); - verifyNoInteractions(queueClientMock); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test - void runDiscardedEventWithInvalidTotalNotice() throws BizEventNotFoundException { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)) - .thenReturn(generateValidBizEvent("invalid string")); + @SneakyThrows + void recoverFailedReceiptFailBizEvenInvalid() { + Receipt failedReceipt = createFailedReceipt(); - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doThrow(BizEventBadRequestException.class).when(helpdeskServiceMock).recoverFailedReceipt(failedReceipt); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); @@ -319,66 +189,17 @@ void runDiscardedEventWithInvalidTotalNotice() throws BizEventNotFoundException assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); assertNotNull(response.getBody()); - verifyNoInteractions(receiptCosmosServiceMock); - verifyNoInteractions(queueClientMock); - } - - @Test - @SneakyThrows - void errorTokenizingFiscalCodes() { - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateValidBizEvent("1")); - - Receipt receipt = createFailedReceipt(); - receipt.setEventData(null); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - - lenient().when(pdvTokenizerServiceMock.generateTokenForFiscalCodeWithRetry(DEBTOR_FISCAL_CODE)) - .thenThrow(new PDVTokenizerException(HTTP_MESSAGE_ERROR, org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR)); - - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - - verifyNoInteractions(queueClientMock); + verify(documentdb, never()).setValue(receiptCaptor.capture()); } @Test @SneakyThrows - void errorAddingMessageToQueue() { - CosmosItemResponse cosmosResponse = mock(CosmosItemResponse.class); - when(cosmosResponse.getStatusCode()).thenReturn(HttpStatus.CREATED.value()); - - when(receiptCosmosClient.saveReceipts(any(Receipt.class))).thenReturn(cosmosResponse); - - Response queueResponse = mock(Response.class); - when(queueResponse.getStatusCode()).thenReturn(HttpStatus.FORBIDDEN.value()); - when(queueClientMock.sendMessageToQueue(anyString())).thenReturn(queueResponse); - - Receipt receipt = createFailedReceipt(); - receipt.setEventData(null); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); + void recoverFailedReceiptFailRecoverError() { + Receipt failedReceipt = createFailedReceipt(); + Receipt recovered = Receipt.builder().status(ReceiptStatusType.FAILED).build(); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID)).thenReturn(generateValidBizEvent("1")); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + doReturn(failedReceipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doReturn(recovered).when(helpdeskServiceMock).recoverFailedReceipt(failedReceipt); // test execution HttpResponseMessage response = assertDoesNotThrow(() -> sut.run(requestMock, EVENT_ID, documentdb, contextMock)); @@ -386,35 +207,10 @@ void errorAddingMessageToQueue() { // test assertion assertNotNull(response); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - } + assertNotNull(response.getBody()); - private BizEvent generateValidBizEvent(String totalNotice){ - BizEvent item = new BizEvent(); - - Payer payer = new Payer(); - payer.setEntityUniqueIdentifierValue(PAYER_FISCAL_CODE); - Debtor debtor = new Debtor(); - debtor.setEntityUniqueIdentifierValue(DEBTOR_FISCAL_CODE); - - TransactionDetails transactionDetails = new TransactionDetails(); - Transaction transaction = new Transaction(); - transaction.setCreationDate(String.valueOf(LocalDateTime.now())); - transaction.setAmount(10000); - transactionDetails.setTransaction(transaction); - transactionDetails.setOrigin("INFO"); - - PaymentInfo paymentInfo = new PaymentInfo(); - paymentInfo.setTotalNotice(totalNotice); - paymentInfo.setAmount("100.0"); - - item.setEventStatus(BizEventStatusType.DONE); - item.setId(EVENT_ID); - item.setPayer(payer); - item.setDebtor(debtor); - item.setTransactionDetails(transactionDetails); - item.setPaymentInfo(paymentInfo); - - return item; + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); } private Receipt createFailedReceipt() { @@ -427,8 +223,8 @@ private Receipt createFailedReceipt() { receipt.setStatus(ReceiptStatusType.FAILED); EventData eventData = new EventData(); - eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); - eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); + eventData.setDebtorFiscalCode("tokenizedDebtorFiscalCode"); + eventData.setPayerFiscalCode("tokenizedPayerFiscalCode"); receipt.setEventData(eventData); CartItem item = new CartItem(); @@ -437,36 +233,4 @@ private Receipt createFailedReceipt() { return receipt; } - - private BizEvent generateAnonymDebtorBizEvent(){ - BizEvent item = new BizEvent(); - - Payer payer = new Payer(); - payer.setEntityUniqueIdentifierValue(PAYER_FISCAL_CODE); - Debtor debtor = new Debtor(); - debtor.setEntityUniqueIdentifierValue("ANONIMO"); - - TransactionDetails transactionDetails = new TransactionDetails(); - Transaction transaction = new Transaction(); - transaction.setCreationDate(String.valueOf(LocalDateTime.now())); - transactionDetails.setTransaction(transaction); - - PaymentInfo paymentInfo = new PaymentInfo(); - paymentInfo.setTotalNotice("1"); - - item.setEventStatus(BizEventStatusType.DONE); - item.setId(EVENT_ID); - item.setPayer(payer); - item.setDebtor(debtor); - item.setTransactionDetails(transactionDetails); - item.setPaymentInfo(paymentInfo); - - return item; - } - - private BizEvent generateNotDoneBizEvent(){ - BizEvent item = new BizEvent(); - item.setEventStatus(BizEventStatusType.NA); - return item; - } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassiveTest.java new file mode 100644 index 00000000..da0fd2bc --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassiveTest.java @@ -0,0 +1,149 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RecoverNotNotifiedCartReceiptMassiveTest { + + @Mock + private ExecutionContext executionContextMock; + @Mock + private HelpdeskService helpdeskServiceMock; + @Mock + private HttpRequestMessage> requestMock; + + @Spy + private OutputBinding> documentdb; + + @Captor + private ArgumentCaptor> cartCaptor; + + @InjectMocks + private RecoverNotNotifiedCartReceiptMassive sut; + + @BeforeEach + void openMocks() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedCartReceiptMassiveSuccess(CartStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(List.of(new CartForReceipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(status); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(cartCaptor.capture()); + + assertEquals(1, cartCaptor.getValue().size()); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedCartReceiptMassiveSuccessWithoutAction(CartStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(status); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverNotNotifiedCartReceiptMassiveFailParamNull() { + // test execution + HttpResponseMessage response = sut.run(requestMock, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @Test + @SneakyThrows + void recoverNotNotifiedCartReceiptMassiveFailParamNotAStatus() { + doReturn(Collections.singletonMap("status", "random")).when(requestMock).getQueryParameters(); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptMassiveFailParamNotAProcessableStatus(CartStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + + // test execution + HttpResponseMessage response = sut.run(requestMock, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptTest.java new file mode 100644 index 00000000..58f61dd4 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptTest.java @@ -0,0 +1,136 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import it.gov.pagopa.receipt.pdf.datastore.service.CartReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RecoverNotNotifiedCartReceiptTest { + + private static final String CART_ID = "cartId"; + + @Mock + private ExecutionContext executionContextMock; + @Mock + private CartReceiptCosmosService cartReceiptCosmosServiceMock; + @Mock + private HelpdeskService helpdeskServiceMock; + + @Mock + private HttpRequestMessage> requestMock; + + @Spy + private OutputBinding documentdb; + + @Captor + private ArgumentCaptor cartCaptor; + + @InjectMocks + private RecoverNotNotifiedCartReceipt sut; + + + @BeforeEach + void setUp() { + doAnswer((Answer) invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedCartReceiptSuccess(CartStatusType status) { + CartForReceipt cart = createFailedCart(); + cart.setStatus(status); + doReturn(cart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + doReturn(new CartForReceipt()).when(helpdeskServiceMock).recoverNoNotifiedCart(cart); + + // test execution + HttpResponseMessage response = sut.run(requestMock, CART_ID, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb).setValue(cartCaptor.capture()); + + assertNotNull(cartCaptor.getValue()); + } + + @Test + @SneakyThrows + void recoverNotNotifiedCartReceiptFailReceiptNotFound() { + doThrow(CartNotFoundException.class).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + + // test execution + HttpResponseMessage response = sut.run(requestMock, CART_ID, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.NOT_FOUND, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + verify(helpdeskServiceMock, never()).recoverNoNotifiedCart(any()); + } + + @ParameterizedTest + @EnumSource(value = CartStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptFailReceiptWithUnexpectedStatus(CartStatusType status) { + CartForReceipt cart = createFailedCart(); + cart.setStatus(status); + doReturn(cart).when(cartReceiptCosmosServiceMock).getCart(CART_ID); + + // test execution + HttpResponseMessage response = sut.run(requestMock, CART_ID, documentdb, executionContextMock); + + // test assertion + assertNotNull(response); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); + + verify(documentdb, never()).setValue(cartCaptor.capture()); + verify(helpdeskServiceMock, never()).recoverNoNotifiedCart(any()); + } + + private CartForReceipt createFailedCart() { + return CartForReceipt.builder() + .status(CartStatusType.FAILED) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java index 64c83200..26f4aced 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassiveTest.java @@ -1,40 +1,47 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.azure.cosmos.models.FeedResponse; -import com.microsoft.azure.functions.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; -import org.junit.jupiter.api.AfterEach; +import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class RecoverNotNotifiedReceiptMassiveTest { - private static final String EVENT_ID = "eventId"; - - private final ExecutionContext executionContextMock = mock(ExecutionContext.class); - @Mock - private ReceiptCosmosService receiptCosmosServiceMock; - + private ExecutionContext executionContextMock; + @Mock + private HelpdeskService helpdeskServiceMock; @Mock private HttpRequestMessage> requestMock; @@ -44,77 +51,23 @@ class RecoverNotNotifiedReceiptMassiveTest { @Captor private ArgumentCaptor> receiptCaptor; + @InjectMocks private RecoverNotNotifiedReceiptMassive sut; - private AutoCloseable closeable; - @BeforeEach void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - sut = spy(new RecoverNotNotifiedReceiptMassive(receiptCosmosServiceMock)); - } - - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); - } - - @Test - void recoverNotNotifiedReceiptMassiveForIOErrorToNotifySuccess() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); - - FeedResponse feedResponseMock = mock(FeedResponse.class); - List receiptList = getReceiptList(ReceiptStatusType.IO_ERROR_TO_NOTIFY); - when(feedResponseMock.getResults()).thenReturn(receiptList); - when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) - .thenReturn(Collections.singletonList(feedResponseMock)); - doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatus()); - assertNotNull(response.getBody()); - - verify(documentReceipts).setValue(receiptCaptor.capture()); - - assertEquals(receiptList.size(), receiptCaptor.getValue().size()); - Receipt captured1 = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); - assertEquals(EVENT_ID, captured1.getEventId()); - assertEquals(0, captured1.getNotificationNumRetry()); - assertNull(captured1.getReasonErr()); - assertNull(captured1.getReasonErrPayer()); - Receipt captured2 = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); - assertEquals(EVENT_ID, captured2.getEventId()); - assertEquals(0, captured2.getNotificationNumRetry()); - assertNull(captured2.getReasonErr()); - assertNull(captured2.getReasonErrPayer()); } - @Test - void recoverNotNotifiedReceiptMassiveForGeneratedSuccess() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.GENERATED.name())); - - FeedResponse feedResponseMock = mock(FeedResponse.class); - List receiptList = getReceiptList(ReceiptStatusType.GENERATED); - when(feedResponseMock.getResults()).thenReturn(receiptList); - when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) - .thenReturn(Collections.singletonList(feedResponseMock)); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptMassiveSuccess(ReceiptStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(List.of(new Receipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(status); // test execution HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); @@ -126,35 +79,15 @@ void recoverNotNotifiedReceiptMassiveForGeneratedSuccess() { verify(documentReceipts).setValue(receiptCaptor.capture()); - assertEquals(receiptList.size(), receiptCaptor.getValue().size()); - Receipt captured1 = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); - assertEquals(EVENT_ID, captured1.getEventId()); - assertEquals(0, captured1.getNotificationNumRetry()); - assertNull(captured1.getReasonErr()); - assertNull(captured1.getReasonErrPayer()); - Receipt captured2 = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); - assertEquals(EVENT_ID, captured2.getEventId()); - assertEquals(0, captured2.getNotificationNumRetry()); - assertNull(captured2.getReasonErr()); - assertNull(captured2.getReasonErrPayer()); + assertEquals(1, receiptCaptor.getValue().size()); } - @Test - void recoverNotNotifiedReceiptMassiveSuccessWithNoReceiptUpdated() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.IO_ERROR_TO_NOTIFY.name())); - - FeedResponse feedResponseMock = mock(FeedResponse.class); - when(feedResponseMock.getResults()).thenReturn(Collections.emptyList()); - when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), any())) - .thenReturn(Collections.singletonList(feedResponseMock)); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptMassiveSuccessWithoutAction(ReceiptStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(status); // test execution HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); @@ -168,39 +101,23 @@ void recoverNotNotifiedReceiptMassiveSuccessWithNoReceiptUpdated() { } @Test - void recoverReceiptFailMissingQueryParam() { - when(requestMock.getQueryParameters()).thenReturn(Collections.emptyMap()); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - + @SneakyThrows + void recoverNotNotifiedReceiptMassiveFailParamNull() { // test execution HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); // test assertion assertNotNull(response); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); + assertNotNull(response.getBody()); verify(documentReceipts, never()).setValue(receiptCaptor.capture()); } @Test - void recoverReceiptFailInvalidStatusType() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", "INVALID_STATUS")); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @SneakyThrows + void recoverNotNotifiedReceiptMassiveFailParamNotAStatus() { + doReturn(Collections.singletonMap("status", "random")).when(requestMock).getQueryParameters(); // test execution HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); @@ -208,70 +125,25 @@ void recoverReceiptFailInvalidStatusType() { // test assertion assertNotNull(response); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); + assertNotNull(response.getBody()); verify(documentReceipts, never()).setValue(receiptCaptor.capture()); } - @Test - void recoverReceiptFailInvalidRestoreStatusRequested() { - when(requestMock.getQueryParameters()) - .thenReturn(Collections.singletonMap("status", ReceiptStatusType.FAILED.name())); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptMassiveFailParamNotAProcessableStatus(ReceiptStatusType status) { + doReturn(Collections.singletonMap("status", status.name())).when(requestMock).getQueryParameters(); // test execution HttpResponseMessage response = sut.run(requestMock, documentReceipts, executionContextMock); // test assertion assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); verify(documentReceipts, never()).setValue(receiptCaptor.capture()); } - - - - private Receipt buildReceipt(ReceiptStatusType statusType) { - return Receipt.builder() - .eventId(EVENT_ID) - .status(statusType) - .reasonErr(ReasonError.builder() - .code(500) - .message("error message") - .build()) - .reasonErrPayer(ReasonError.builder() - .code(500) - .message("error message") - .build()) - .numRetry(0) - .notificationNumRetry(6) - .inserted_at(0) - .generated_at(0) - .notified_at(0) - .build(); - } - - private List getReceiptList(ReceiptStatusType statusType) { - List receiptList = new ArrayList<>(); - Receipt receipt1 = buildReceipt(statusType); - Receipt receipt2 = buildReceipt(statusType); - receiptList.add(receipt1); - receiptList.add(receipt2); - return receiptList; - } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java index db7bc0bd..e3155cb9 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptTest.java @@ -1,71 +1,83 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.http; -import com.microsoft.azure.functions.*; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.OutputBinding; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.model.ProblemJson; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; import it.gov.pagopa.receipt.pdf.datastore.utils.HttpResponseMessageMock; -import org.junit.jupiter.api.AfterEach; +import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class RecoverNotNotifiedReceiptTest { private static final String EVENT_ID = "eventId"; - private final ExecutionContext executionContextMock = mock(ExecutionContext.class); - + @Mock + private ExecutionContext executionContextMock; @Mock private ReceiptCosmosService receiptCosmosServiceMock; + @Mock + private HelpdeskService helpdeskServiceMock; @Mock private HttpRequestMessage> requestMock; @Spy - private OutputBinding> documentReceipts; + private OutputBinding documentReceipts; @Captor - private ArgumentCaptor> receiptCaptor; + private ArgumentCaptor receiptCaptor; + @InjectMocks private RecoverNotNotifiedReceipt sut; - private AutoCloseable closeable; @BeforeEach - void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - sut = spy(new RecoverNotNotifiedReceipt(receiptCosmosServiceMock)); - } - - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); - } - - @Test - void recoverNotNotifiedReceiptSuccess() throws ReceiptNotFoundException { - Receipt receipt = buildReceipt(); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - + void setUp() { doAnswer((Answer) invocation -> { HttpStatus status = (HttpStatus) invocation.getArguments()[0]; return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + } + + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.INCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptSuccess(ReceiptStatusType status) { + Receipt receipt = buildReceipt(); + receipt.setStatus(status); + doReturn(receipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); + doReturn(new Receipt()).when(helpdeskServiceMock).recoverNoNotifiedReceipt(receipt); // test execution HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); @@ -77,46 +89,13 @@ void recoverNotNotifiedReceiptSuccess() throws ReceiptNotFoundException { verify(documentReceipts).setValue(receiptCaptor.capture()); - assertEquals(1, receiptCaptor.getValue().size()); - Receipt captured = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured.getStatus()); - assertEquals(EVENT_ID, captured.getEventId()); - assertEquals(0, captured.getNotificationNumRetry()); - assertNull(captured.getReasonErr()); - assertNull(captured.getReasonErrPayer()); + assertNotNull(receiptCaptor.getValue()); } @Test - void recoverReceiptFailForMissingEventId() { - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = sut.run(requestMock, "", documentReceipts, executionContextMock); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.BAD_REQUEST.value(), problemJson.getStatus()); - assertEquals(HttpStatus.BAD_REQUEST.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - - verify(documentReceipts, never()).setValue(receiptCaptor.capture()); - } - - @Test - void recoverReceiptFailReceiptNotFound() throws ReceiptNotFoundException { - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenThrow(ReceiptNotFoundException.class); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @SneakyThrows + void recoverNotNotifiedReceiptFailReceiptNotFound() { + doThrow(ReceiptNotFoundException.class).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); // test execution HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); @@ -127,60 +106,27 @@ void recoverReceiptFailReceiptNotFound() throws ReceiptNotFoundException { assertNotNull(response.getBody()); verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + verify(helpdeskServiceMock, never()).recoverNoNotifiedReceipt(any()); } - @Test - void recoverReceiptFailReceiptInInsertedButOnlyGenerated() throws ReceiptNotFoundException { - Receipt receipt = new Receipt(); - receipt.setStatus(ReceiptStatusType.INSERTED); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); - - // test execution - HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); - - // test assertion - assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); - - verify(documentReceipts, never()).setValue(receiptCaptor.capture()); - } - - @Test - void recoverReceiptFailReceiptInInsertedButOnlyIOErrorToNotify() throws ReceiptNotFoundException { - Receipt receipt = new Receipt(); - receipt.setStatus(ReceiptStatusType.INSERTED); - when(receiptCosmosServiceMock.getReceipt(EVENT_ID)).thenReturn(receipt); - - doAnswer((Answer) invocation -> { - HttpStatus status = (HttpStatus) invocation.getArguments()[0]; - return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); - }).when(requestMock).createResponseBuilder(any(HttpStatus.class)); + @ParameterizedTest + @EnumSource(value = ReceiptStatusType.class, names = {"GENERATED", "IO_ERROR_TO_NOTIFY"}, mode = EnumSource.Mode.EXCLUDE) + @SneakyThrows + void recoverNotNotifiedReceiptFailReceiptWithUnexpectedStatus(ReceiptStatusType status) { + Receipt receipt = buildReceipt(); + receipt.setStatus(status); + doReturn(receipt).when(receiptCosmosServiceMock).getReceipt(EVENT_ID); // test execution HttpResponseMessage response = sut.run(requestMock, EVENT_ID, documentReceipts, executionContextMock); // test assertion assertNotNull(response); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus()); - - ProblemJson problemJson = (ProblemJson) response.getBody(); - assertNotNull(problemJson); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), problemJson.getStatus()); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.name(), problemJson.getTitle()); - assertNotNull(problemJson.getDetail()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, response.getStatus()); + assertNotNull(response.getBody()); verify(documentReceipts, never()).setValue(receiptCaptor.capture()); + verify(helpdeskServiceMock, never()).recoverNoNotifiedReceipt(any()); } private Receipt buildReceipt() { diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduledTest.java new file mode 100644 index 00000000..bc303b37 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduledTest.java @@ -0,0 +1,106 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveCartRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) +class RecoverFailedCartReceiptScheduledTest { + + @Mock + private ExecutionContext contextMock; + @Mock + private HelpdeskService helpdeskServiceMock; + + @Captor + private ArgumentCaptor> receiptCaptor; + + @Spy + private OutputBinding> documentdb; + + @SystemStub + private EnvironmentVariables environment; + + private RecoverFailedCartReceiptScheduled sut; + + @Test + @SneakyThrows + void recoverFailedCartReceiptScheduledSuccess() { + sut = new RecoverFailedCartReceiptScheduled(helpdeskServiceMock); + + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.FAILED); + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.INSERTED); + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.NOT_QUEUE_SENT); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(3, receiptCaptor.getValue().size()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptScheduledSuccessWithoutAction() { + sut = new RecoverFailedCartReceiptScheduled(helpdeskServiceMock); + + doReturn(new MassiveCartRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.FAILED); + doReturn(new MassiveCartRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.INSERTED); + doReturn(new MassiveCartRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(CartStatusType.NOT_QUEUE_SENT); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(0, receiptCaptor.getValue().size()); + } + + @Test + @SneakyThrows + void recoverFailedCartReceiptScheduledDisabled() { + environment.set("FAILED_AUTORECOVER_ENABLED", "false"); + sut = new RecoverFailedCartReceiptScheduled(helpdeskServiceMock); + + assertEquals("false", System.getenv("FAILED_AUTORECOVER_ENABLED")); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb, never()).setValue(any()); + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(CartStatusType.class)); + } + + private MassiveCartRecoverResult createMassiveRecoverResult() { + return MassiveCartRecoverResult.builder() + .successCounter(1) + .errorCounter(1) + .failedCartList(List.of(new CartForReceipt())) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java index c34aae18..ae594663 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduledTest.java @@ -1,55 +1,40 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; -import com.azure.cosmos.models.ModelBridgeInternal; -import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.OutputBinding; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.BizEventCosmosClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.*; -import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.BizEventStatusType; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartItem; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.EventData; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.BizEventNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.PDVTokenizerException; -import it.gov.pagopa.receipt.pdf.datastore.service.BizEventToReceiptService; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import it.gov.pagopa.receipt.pdf.datastore.model.MassiveRecoverResult; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.stubbing.Answer; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; import uk.org.webcompere.systemstubs.jupiter.SystemStub; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; -@ExtendWith(SystemStubsExtension.class) +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) class RecoverFailedReceiptScheduledTest { - private final String TOKENIZED_DEBTOR_FISCAL_CODE = "tokenizedDebtorFiscalCode"; - private final String TOKENIZED_PAYER_FISCAL_CODE = "tokenizedPayerFiscalCode"; - private final String EVENT_ID_1 = "a valid id 1"; - private final String EVENT_ID_2 = "a valid id 2"; - private final String EVENT_ID_3 = "a valid id 3"; - @Mock private ExecutionContext contextMock; @Mock - private ReceiptCosmosService receiptCosmosServiceMock; - @Mock - private BizEventCosmosClientImpl bizEventCosmosClientMock; - @Mock - private BizEventToReceiptService bizEventToReceiptServiceMock; + private HelpdeskService helpdeskServiceMock; @Captor private ArgumentCaptor> receiptCaptor; @@ -60,74 +45,47 @@ class RecoverFailedReceiptScheduledTest { @SystemStub private EnvironmentVariables environment; - private AutoCloseable closeable; - private RecoverFailedReceiptScheduled sut; - @BeforeEach - void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - } + @Test + @SneakyThrows + void recoverFailedReceiptScheduledSuccess() { + sut = new RecoverFailedReceiptScheduled(helpdeskServiceMock); - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.FAILED); + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.INSERTED); + doReturn(createMassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.NOT_QUEUE_SENT); + + // test execution + assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(3, receiptCaptor.getValue().size()); } @Test - void recoverFailedReceiptScheduledSuccess() throws BizEventNotFoundException, PDVTokenizerException, JsonProcessingException { - sut = spy(new RecoverFailedReceiptScheduled(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); - - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.FAILED))) - .thenReturn(Collections.singletonList(ModelBridgeInternal - .createFeedResponse(Collections.singletonList( - createFailedReceipt(EVENT_ID_1, ReceiptStatusType.FAILED)), - Collections.emptyMap()))); - - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.INSERTED))) - .thenReturn(Collections.singletonList(ModelBridgeInternal - .createFeedResponse(Collections.singletonList( - createFailedReceipt(EVENT_ID_2, ReceiptStatusType.INSERTED)), - Collections.emptyMap()))); - when(receiptCosmosServiceMock.getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.NOT_QUEUE_SENT))) - .thenReturn(Collections.singletonList(ModelBridgeInternal - .createFeedResponse(Collections.singletonList( - createFailedReceipt(EVENT_ID_3, ReceiptStatusType.NOT_QUEUE_SENT)), - Collections.emptyMap()))); - - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_1)) - .thenReturn(generateValidBizEvent(EVENT_ID_1)); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_2)) - .thenReturn(generateValidBizEvent(EVENT_ID_2)); - when(bizEventCosmosClientMock.getBizEventDocument(EVENT_ID_3)) - .thenReturn(generateValidBizEvent(EVENT_ID_3)); - - Answer successAnswer = invocation -> { - // arg 0: BizEvent, arg 1: Receipt, arg 2: EventData - EventData eventDataArg = invocation.getArgument(2); - - // simulate tokenization - eventDataArg.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); - eventDataArg.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); - return null; - }; - - doAnswer(successAnswer).when(bizEventToReceiptServiceMock).tokenizeFiscalCodes(any(), any(), any()); + @SneakyThrows + void recoverFailedReceiptScheduledSuccessWithoutAction() { + sut = new RecoverFailedReceiptScheduled(helpdeskServiceMock); + + doReturn(new MassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.FAILED); + doReturn(new MassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.INSERTED); + doReturn(new MassiveRecoverResult()).when(helpdeskServiceMock).massiveRecoverByStatus(ReceiptStatusType.NOT_QUEUE_SENT); // test execution assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); - verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.INSERTED)); - verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.FAILED)); - verify(receiptCosmosServiceMock).getFailedReceiptByStatus(any(), any(), eq(ReceiptStatusType.NOT_QUEUE_SENT)); - - verify(bizEventCosmosClientMock, times(3)).getBizEventDocument(anyString()); + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(0, receiptCaptor.getValue().size()); } @Test - void recoverFailedReceiptScheduledDisabled() throws BizEventNotFoundException { + @SneakyThrows + void recoverFailedReceiptScheduledDisabled() { environment.set("FAILED_AUTORECOVER_ENABLED", "false"); - sut = spy(new RecoverFailedReceiptScheduled(bizEventToReceiptServiceMock, bizEventCosmosClientMock, receiptCosmosServiceMock)); + sut = new RecoverFailedReceiptScheduled(helpdeskServiceMock); assertEquals("false", System.getenv("FAILED_AUTORECOVER_ENABLED")); @@ -135,54 +93,14 @@ void recoverFailedReceiptScheduledDisabled() throws BizEventNotFoundException { assertDoesNotThrow(() -> sut.run("info", documentdb, contextMock)); verify(documentdb, never()).setValue(any()); - verify(receiptCosmosServiceMock, never()).getFailedReceiptByStatus(any(), any(), any()); - verify(bizEventCosmosClientMock, never()).getBizEventDocument(anyString()); - - } - - private BizEvent generateValidBizEvent(String eventId) { - BizEvent item = new BizEvent(); - - Payer payer = new Payer(); - payer.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); - Debtor debtor = new Debtor(); - debtor.setEntityUniqueIdentifierValue("AAAAAA00A00A000D"); - - TransactionDetails transactionDetails = new TransactionDetails(); - Transaction transaction = new Transaction(); - transaction.setCreationDate(String.valueOf(LocalDateTime.now())); - transactionDetails.setTransaction(transaction); - - PaymentInfo paymentInfo = new PaymentInfo(); - paymentInfo.setTotalNotice("1"); - - item.setEventStatus(BizEventStatusType.DONE); - item.setId(eventId); - item.setPayer(payer); - item.setDebtor(debtor); - item.setTransactionDetails(transactionDetails); - item.setPaymentInfo(paymentInfo); - - return item; + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(ReceiptStatusType.class)); } - private Receipt createFailedReceipt(String id, ReceiptStatusType statusType) { - Receipt receipt = new Receipt(); - - receipt.setId(id); - receipt.setEventId(id); - receipt.setVersion("1"); - - receipt.setStatus(statusType); - EventData eventData = new EventData(); - eventData.setDebtorFiscalCode(TOKENIZED_DEBTOR_FISCAL_CODE); - eventData.setPayerFiscalCode(TOKENIZED_PAYER_FISCAL_CODE); - receipt.setEventData(eventData); - - CartItem item = new CartItem(); - List cartItems = Collections.singletonList(item); - eventData.setCart(cartItems); - - return receipt; + private MassiveRecoverResult createMassiveRecoverResult() { + return MassiveRecoverResult.builder() + .successCounter(1) + .errorCounter(1) + .failedReceiptList(List.of(new Receipt())) + .build(); } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduledTest.java new file mode 100644 index 00000000..3db2dc87 --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedCartReceiptScheduledTest.java @@ -0,0 +1,97 @@ +package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) +class RecoverNotNotifiedCartReceiptScheduledTest { + + @Mock + private ExecutionContext contextMock; + + @Mock + private HelpdeskService helpdeskServiceMock; + + @Spy + private OutputBinding> documentdb; + + @Captor + private ArgumentCaptor> receiptCaptor; + + @SystemStub + private EnvironmentVariables environment; + + private RecoverNotNotifiedCartReceiptScheduled sut; + + @Test + @SneakyThrows + void recoverFailedReceiptScheduledSuccess() { + sut = new RecoverNotNotifiedCartReceiptScheduled(helpdeskServiceMock); + + doReturn(List.of(new CartForReceipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(CartStatusType.IO_ERROR_TO_NOTIFY); + doReturn(List.of(new CartForReceipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(CartStatusType.GENERATED); + + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(2, receiptCaptor.getValue().size()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptScheduledSuccessWithoutAction() { + sut = new RecoverNotNotifiedCartReceiptScheduled(helpdeskServiceMock); + + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(CartStatusType.IO_ERROR_TO_NOTIFY); + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(CartStatusType.GENERATED); + + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); + + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(0, receiptCaptor.getValue().size()); + } + + @Test + @SneakyThrows + void recoverFailedReceiptScheduledDisabled() { + environment.set("NOT_NOTIFIED_AUTORECOVER_ENABLED", "false"); + sut = new RecoverNotNotifiedCartReceiptScheduled(helpdeskServiceMock); + + assertEquals("false", System.getenv("NOT_NOTIFIED_AUTORECOVER_ENABLED")); + + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); + + verify(documentdb, never()).setValue(any()); + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(CartStatusType.class)); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java index 956aea3e..f57dc58f 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverNotNotifiedReceiptScheduledTest.java @@ -1,123 +1,97 @@ package it.gov.pagopa.receipt.pdf.datastore.helpdesk.schedule; -import com.azure.cosmos.models.FeedResponse; import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.OutputBinding; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReasonError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.service.ReceiptCosmosService; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import it.gov.pagopa.receipt.pdf.datastore.service.HelpdeskService; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; -import org.mockito.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) class RecoverNotNotifiedReceiptScheduledTest { - private static final String EVENT_ID = "eventId"; - - private final ExecutionContext executionContextMock = mock(ExecutionContext.class); - @Mock - private ReceiptCosmosService receiptCosmosServiceMock; + private ExecutionContext contextMock; @Mock - private HttpRequestMessage> requestMock; + private HelpdeskService helpdeskServiceMock; @Spy - private OutputBinding> documentReceipts; + private OutputBinding> documentdb; @Captor private ArgumentCaptor> receiptCaptor; + @SystemStub + private EnvironmentVariables environment; + private RecoverNotNotifiedReceiptScheduled sut; - private AutoCloseable closeable; + @Test + @SneakyThrows + void recoverFailedReceiptScheduledSuccess() { + sut = new RecoverNotNotifiedReceiptScheduled(helpdeskServiceMock); - @BeforeEach - void openMocks() { - closeable = MockitoAnnotations.openMocks(this); - sut = spy(new RecoverNotNotifiedReceiptScheduled(receiptCosmosServiceMock)); - } + doReturn(List.of(new Receipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(ReceiptStatusType.IO_ERROR_TO_NOTIFY); + doReturn(List.of(new Receipt())).when(helpdeskServiceMock).massiveRecoverNoNotified(ReceiptStatusType.GENERATED); + + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); - @AfterEach - void releaseMocks() throws Exception { - closeable.close(); + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(2, receiptCaptor.getValue().size()); } @Test - void scheduledTriggerShouldReturnAllValidReceiptsProcessed() { - FeedResponse feedResponseMock = mock(FeedResponse.class); - List receiptList = getReceiptList(ReceiptStatusType.IO_ERROR_TO_NOTIFY); - when(feedResponseMock.getResults()).thenReturn(receiptList); - when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), eq(ReceiptStatusType.IO_ERROR_TO_NOTIFY))) - .thenReturn(Collections.singletonList(feedResponseMock)); - - FeedResponse feedResponseGenMock = mock(FeedResponse.class); - List receiptGenList = getReceiptList(ReceiptStatusType.GENERATED); - when(feedResponseGenMock.getResults()).thenReturn(receiptGenList); - when(receiptCosmosServiceMock.getNotNotifiedReceiptByStatus(any(), any(), eq(ReceiptStatusType.GENERATED))) - .thenReturn(Collections.singletonList(feedResponseGenMock)); - - sut.processRecoverNotNotifiedScheduledTrigger("info", documentReceipts, executionContextMock); - - verify(documentReceipts).setValue(receiptCaptor.capture()); - - assertEquals(receiptList.size()+receiptGenList.size(), receiptCaptor.getValue().size()); - Receipt captured1 = receiptCaptor.getValue().get(0); - assertEquals(ReceiptStatusType.GENERATED, captured1.getStatus()); - assertEquals(EVENT_ID, captured1.getEventId()); - assertEquals(0, captured1.getNotificationNumRetry()); - assertNull(captured1.getReasonErr()); - assertNull(captured1.getReasonErrPayer()); - Receipt captured2 = receiptCaptor.getValue().get(1); - assertEquals(ReceiptStatusType.GENERATED, captured2.getStatus()); - assertEquals(EVENT_ID, captured2.getEventId()); - assertEquals(0, captured2.getNotificationNumRetry()); - assertNull(captured2.getReasonErr()); - assertNull(captured2.getReasonErrPayer()); - } + @SneakyThrows + void recoverFailedReceiptScheduledSuccessWithoutAction() { + sut = new RecoverNotNotifiedReceiptScheduled(helpdeskServiceMock); + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(ReceiptStatusType.IO_ERROR_TO_NOTIFY); + doReturn(Collections.emptyList()).when(helpdeskServiceMock).massiveRecoverNoNotified(ReceiptStatusType.GENERATED); - private Receipt buildReceipt(ReceiptStatusType statusType) { - return Receipt.builder() - .eventId(EVENT_ID) - .status(statusType) - .reasonErr(ReasonError.builder() - .code(500) - .message("error message") - .build()) - .reasonErrPayer(ReasonError.builder() - .code(500) - .message("error message") - .build()) - .numRetry(0) - .notificationNumRetry(6) - .inserted_at(0) - .generated_at(0) - .notified_at(0) - .build(); - } + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); - private List getReceiptList(ReceiptStatusType statusType) { - List receiptList = new ArrayList<>(); - Receipt receipt1 = buildReceipt(statusType); - Receipt receipt2 = buildReceipt(statusType); - receiptList.add(receipt1); - receiptList.add(receipt2); - return receiptList; + verify(documentdb).setValue(receiptCaptor.capture()); + assertNotNull(receiptCaptor.getValue()); + assertEquals(0, receiptCaptor.getValue().size()); } + @Test + @SneakyThrows + void recoverFailedReceiptScheduledDisabled() { + environment.set("NOT_NOTIFIED_AUTORECOVER_ENABLED", "false"); + sut = new RecoverNotNotifiedReceiptScheduled(helpdeskServiceMock); + + assertEquals("false", System.getenv("NOT_NOTIFIED_AUTORECOVER_ENABLED")); + + // test execution + assertDoesNotThrow(() -> sut.processRecoverNotNotifiedScheduledTrigger("info", documentdb, contextMock)); + + verify(documentdb, never()).setValue(any()); + verify(helpdeskServiceMock, never()).massiveRecoverByStatus(any(ReceiptStatusType.class)); + } } \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java new file mode 100644 index 00000000..5aa3386f --- /dev/null +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java @@ -0,0 +1,51 @@ +package it.gov.pagopa.receipt.pdf.datastore.service.impl; + +import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; +import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; +import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; +import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; + +class CartReceiptCosmosServiceImplTest { + + @Mock + private ReceiptCosmosClientImpl receiptCosmosClient; + @Mock + private CartReceiptsCosmosClient cartReceiptsCosmosClient; + + @InjectMocks + private CartReceiptCosmosServiceImpl sut; + + @Test + void getCart_OK() throws CartNotFoundException { + doReturn(new CartForReceipt()).when(cartReceiptsCosmosClient).getCartItem(anyString()); + + assertDoesNotThrow(() -> sut.getCart(anyString())); + } + + @Test + void getCart_KO_NotFound() throws CartNotFoundException { + doThrow(CartNotFoundException.class).when(cartReceiptsCosmosClient).getCartItem(anyString()); + CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); + + assertNotNull(e); + } + + @Test + void getCart_KO_Null() throws CartNotFoundException { + doReturn(null).when(cartReceiptsCosmosClient).getCartItem(anyString()); + CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); + + assertNotNull(e); + } + +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java index 86eb919d..43081e4e 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/ReceiptCosmosServiceImplTest.java @@ -1,15 +1,10 @@ package it.gov.pagopa.receipt.pdf.datastore.service.impl; import com.azure.cosmos.models.FeedResponse; -import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; -import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; -import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.IOMessage; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.Receipt; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.ReceiptError; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.enumeration.ReceiptStatusType; -import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; -import it.gov.pagopa.receipt.pdf.datastore.exception.IoMessageNotFoundException; import it.gov.pagopa.receipt.pdf.datastore.exception.ReceiptNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,10 +13,18 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) class ReceiptCosmosServiceImplTest { @@ -31,8 +34,6 @@ class ReceiptCosmosServiceImplTest { @Mock private ReceiptCosmosClientImpl receiptCosmosClient; - @Mock - private CartReceiptsCosmosClient cartReceiptsCosmosClient; @InjectMocks private ReceiptCosmosServiceImpl sut; @@ -164,50 +165,4 @@ void getFailedReceiptByStatus_OK_Inserted() { verify(receiptCosmosClient).getInsertedReceiptDocuments(CONTINUATION_TOKEN, PAGE_SIZE); verify(receiptCosmosClient, never()).getFailedReceiptDocuments(any(), any()); } - - @Test - void getReceiptMessage_OK() throws IoMessageNotFoundException { - doReturn(new IOMessage()).when(receiptCosmosClient).getIoMessage(anyString()); - - assertDoesNotThrow(() -> sut.getReceiptMessage(anyString())); - } - - @Test - void getReceiptMessage_KO_NotFound() throws IoMessageNotFoundException { - doThrow(IoMessageNotFoundException.class).when(receiptCosmosClient).getIoMessage(anyString()); - IoMessageNotFoundException e = assertThrows(IoMessageNotFoundException.class, () -> sut.getReceiptMessage(anyString())); - - assertNotNull(e); - } - - @Test - void getReceiptMessage_KO_Null() throws IoMessageNotFoundException { - doReturn(null).when(receiptCosmosClient).getIoMessage(anyString()); - IoMessageNotFoundException e = assertThrows(IoMessageNotFoundException.class, () -> sut.getReceiptMessage(anyString())); - - assertNotNull(e); - } - - @Test - void getCart_OK() throws CartNotFoundException { - doReturn(new CartForReceipt()).when(cartReceiptsCosmosClient).getCartItem(anyString()); - - assertDoesNotThrow(() -> sut.getCart(anyString())); - } - - @Test - void getCart_KO_NotFound() throws CartNotFoundException { - doThrow(CartNotFoundException.class).when(cartReceiptsCosmosClient).getCartItem(anyString()); - CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); - - assertNotNull(e); - } - - @Test - void getCart_KO_Null() throws CartNotFoundException { - doReturn(null).when(cartReceiptsCosmosClient).getCartItem(anyString()); - CartNotFoundException e = assertThrows(CartNotFoundException.class, () -> sut.getCart(anyString())); - - assertNotNull(e); - } } \ No newline at end of file From cee528097d63bb908e55dd4fd4c6f810299dc86c Mon Sep 17 00:00:00 2001 From: Jacopo Carlini Date: Thu, 15 Jan 2026 15:20:06 +0100 Subject: [PATCH 34/38] log level --- .../java/it/gov/pagopa/receipt/pdf/datastore/Info.java | 2 +- .../schedule/RecoverFailedCartReceiptScheduled.java | 2 +- .../schedule/RecoverFailedReceiptScheduled.java | 2 +- .../service/impl/BizEventToReceiptServiceImpl.java | 10 +++++----- .../datastore/service/impl/HelpdeskServiceImpl.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/Info.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/Info.java index 38dd45a6..4fe18726 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/Info.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/Info.java @@ -49,7 +49,7 @@ public synchronized AppInfo getInfo() { name = properties.getProperty("name", null); } } catch (Exception e) { - logger.error("Impossible to retrieve information from pom.properties file.", e); + logger.warn("Impossible to retrieve information from pom.properties file.", e); } return AppInfo.builder().version(version).environment("azure-fn").name(name).build(); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java index 0bd8ff26..c48292bb 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedCartReceiptScheduled.java @@ -80,7 +80,7 @@ private List recover(CartStatusType status) { int errorCounter = recoverResult.getErrorCounter(); if (errorCounter > 0) { - logger.error("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); + logger.warn("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); return recoverResult.getFailedCartList(); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java index a7aec503..186d642a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/schedule/RecoverFailedReceiptScheduled.java @@ -80,7 +80,7 @@ private List recover(ReceiptStatusType status) { int errorCounter = recoverResult.getErrorCounter(); if (errorCounter > 0) { - logger.error("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); + logger.warn("Recovered {} cart receipts but {} encountered an error.", successCounter, errorCounter); return recoverResult.getFailedReceiptList(); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index bebe6495..9dd43e25 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -90,9 +90,9 @@ public void handleSendMessageToQueue(List bizEventList, Receipt receip } catch (Exception e) { statusCode = ReasonErrorCode.ERROR_QUEUE.getCode(); if (bizEventList.size() == 1) { - logger.error("Sending BizEvent with id {} to queue failed", bizEventList.get(0).getId(), e); + logger.warn("Sending BizEvent with id {} to queue failed", bizEventList.get(0).getId(), e); } else { - logger.error("Failed to enqueue cart with id {}", + logger.warn("Failed to enqueue cart with id {}", bizEventList.get(0).getTransactionDetails().getTransaction().getIdTransaction(), e); } } @@ -120,7 +120,7 @@ public void handleSendCartMessageToQueue(List bizEventList, CartForRec statusCode = sendMessageResult.getStatusCode(); } catch (Exception e) { statusCode = ReasonErrorCode.ERROR_QUEUE.getCode(); - logger.error("Failed to enqueue cart with id {}", cartForReceipt.getEventId(), e); + logger.warn("Failed to enqueue cart with id {}", cartForReceipt.getEventId(), e); } if (statusCode != HttpStatus.CREATED.value()) { @@ -170,7 +170,7 @@ public void handleSaveReceipt(Receipt receipt) { statusCode = response.getStatusCode(); } catch (Exception e) { statusCode = ReasonErrorCode.ERROR_COSMOS.getCode(); - logger.error("Save receipt with eventId {} on cosmos failed", receipt.getEventId(), e); + logger.warn("Save receipt with eventId {} on cosmos failed", receipt.getEventId(), e); } if (statusCode != (HttpStatus.CREATED.value())) { @@ -305,7 +305,7 @@ public CartForReceipt saveCartForReceipt(CartForReceipt cartForReceipt, BizEvent cartForReceipt = buildCartForReceipt(bizEvent); if (!isCartStatusValid(cartForReceipt)) { - logger.error("Cart build after fetch failed"); + logger.warn("Cart build after fetch failed"); return cartForReceipt; } statusCode = trySaveCart(cartForReceipt); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java index 661ea49a..56c283cd 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/HelpdeskServiceImpl.java @@ -149,7 +149,7 @@ public MassiveRecoverResult massiveRecoverByStatus(ReceiptStatusType status) { errorCounter++; } } catch (Exception e) { - logger.error("Recover for receipt {} failed", receipt.getEventId(), e); + logger.warn("Recover for receipt {} failed", receipt.getEventId(), e); errorCounter++; } } @@ -186,7 +186,7 @@ public MassiveCartRecoverResult massiveRecoverByStatus(CartStatusType status) { errorCounter++; } } catch (Exception e) { - logger.error("Recover for cart {} failed", cart.getEventId(), e); + logger.warn("Recover for cart {} failed", cart.getEventId(), e); errorCounter++; } } From e61184fb01ba6b0b1c30476f244ab65bcf2614b2 Mon Sep 17 00:00:00 2001 From: Jacopo Carlini Date: Wed, 21 Jan 2026 17:24:51 +0100 Subject: [PATCH 35/38] test: fix junit --- .../service/impl/CartReceiptCosmosServiceImplTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java index 5aa3386f..f4d26bf5 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java @@ -5,8 +5,11 @@ import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.exception.CartNotFoundException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,6 +18,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) class CartReceiptCosmosServiceImplTest { @Mock From 960ef2520d9d359f3efca623846402149d3c9504 Mon Sep 17 00:00:00 2001 From: Jacopo Carlini Date: Thu, 22 Jan 2026 14:34:56 +0100 Subject: [PATCH 36/38] rollback --- .../step_definitions/blob_storage_client.js | 46 ------------------- .../datastore/entity/cart/CartForReceipt.java | 4 -- .../datastore/entity/receipt/IOMessage.java | 17 ------- .../pdf/datastore/entity/receipt/Receipt.java | 1 - .../exception/IoMessageNotFoundException.java | 26 ----------- .../impl/BizEventToReceiptServiceImpl.java | 1 - .../utils/BizEventToReceiptUtils.java | 1 - .../CartReceiptCosmosServiceImplTest.java | 6 +-- 8 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 integration-test/src/step_definitions/blob_storage_client.js delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java delete mode 100644 src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js deleted file mode 100644 index dacf864a..00000000 --- a/integration-test/src/step_definitions/blob_storage_client.js +++ /dev/null @@ -1,46 +0,0 @@ -const { BlobServiceClient } = require('@azure/storage-blob'); - -const blobStorageConnString = process.env.RECEIPTS_STORAGE_CONN_STRING; -const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; - -const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); -const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName || ""); - -async function uploadBlobFromLocalPath(fileName, localFilePath) { - const blobClient = containerClient.getBlockBlobClient(fileName); - - try { - return await blobClient.uploadFile(localFilePath); - } catch (err) { - return { status: 500 } - } -} - -async function deleteBlob(blobName) { - // include: Delete the base blob and all of its snapshots. - // only: Delete only the blob's snapshots and not the blob itself. - const options = { - deleteSnapshots: 'include' // or 'only' - } - - // Create blob client from container client - const blockBlobClient = containerClient.getBlockBlobClient(blobName); - - await blockBlobClient.deleteIfExists(options); -} - -async function receiptPDFExist(blobName) { - let blobs = containerClient.listBlobsFlat(); - for await (const blob of blobs) { - if (blob.name === blobName) { - return true; - } - } - return false; -} - -module.exports = { - uploadBlobFromLocalPath, - deleteBlob, - receiptPDFExist -} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java index 60a4f12c..6ff841f5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/cart/CartForReceipt.java @@ -6,8 +6,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.Set; - @AllArgsConstructor @NoArgsConstructor @Builder(toBuilder = true) @@ -19,10 +17,8 @@ public class CartForReceipt { private String version; private Payload payload; private CartStatusType status; - private Set cartPaymentId; private int numRetry; private int notificationNumRetry; - private Integer totalNotice; private ReasonError reasonErr; private long inserted_at; private long generated_at; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java deleted file mode 100644 index dbd1a737..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/IOMessage.java +++ /dev/null @@ -1,17 +0,0 @@ -package it.gov.pagopa.receipt.pdf.datastore.entity.receipt; - -import it.gov.pagopa.receipt.pdf.datastore.entity.event.enumeration.UserType; -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class IOMessage { - - String id; - String messageId; - String eventId; - UserType userType; -} diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java index 615b7494..226af72a 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/entity/receipt/Receipt.java @@ -27,5 +27,4 @@ public class Receipt { private long inserted_at; private long generated_at; private long notified_at; - private Boolean isCart; } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java deleted file mode 100644 index a915792d..00000000 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/exception/IoMessageNotFoundException.java +++ /dev/null @@ -1,26 +0,0 @@ -package it.gov.pagopa.receipt.pdf.datastore.exception; - -/** Thrown in case no receipt message to IO is found in the CosmosDB container */ -public class IoMessageNotFoundException extends Exception{ - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - */ - public IoMessageNotFoundException(String message) { - super(message); - } - - /** - * Constructs new exception with provided message and cause - * - * @param message Detail message - * @param cause Exception thrown - */ - public IoMessageNotFoundException(String message, Throwable cause) { - super(message, cause); - } -} - - diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java index 491d7ff0..c0310761 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/BizEventToReceiptServiceImpl.java @@ -2,7 +2,6 @@ import com.azure.core.http.rest.Response; import com.azure.cosmos.models.CosmosItemResponse; -import com.azure.cosmos.models.FeedResponse; import com.azure.storage.queue.models.SendMessageResult; import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.azure.functions.HttpStatus; diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java index d4a8255b..664caf10 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/utils/BizEventToReceiptUtils.java @@ -82,7 +82,6 @@ public static Receipt createReceipt(BizEvent bizEvent, BizEventToReceiptService eventData.setCart(cartItems); receipt.setEventData(eventData); - receipt.setStatus(ReceiptStatusType.INSERTED); return receipt; } diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java index 83859b7b..f0955ec3 100644 --- a/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/receipt/pdf/datastore/service/impl/CartReceiptCosmosServiceImplTest.java @@ -2,7 +2,6 @@ import com.azure.cosmos.models.FeedResponse; import it.gov.pagopa.receipt.pdf.datastore.client.CartReceiptsCosmosClient; -import it.gov.pagopa.receipt.pdf.datastore.client.impl.ReceiptCosmosClientImpl; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartForReceipt; import it.gov.pagopa.receipt.pdf.datastore.entity.cart.CartStatusType; import it.gov.pagopa.receipt.pdf.datastore.entity.receipt.CartReceiptError; @@ -12,7 +11,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -24,11 +22,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) +@ExtendWith(MockitoExtension.class) class CartReceiptCosmosServiceImplTest { - @Mock - private ReceiptCosmosClientImpl receiptCosmosClient; @Mock private CartReceiptsCosmosClient cartReceiptsCosmosClient; @Mock From 14a3f638ad3362e4f6536bd698e3428cda1b73c3 Mon Sep 17 00:00:00 2001 From: Jacopo Carlini Date: Thu, 22 Jan 2026 16:12:11 +0100 Subject: [PATCH 37/38] service name --- helm/values-dev.yaml | 2 ++ helm/values-prod.yaml | 2 ++ helm/values-uat.yaml | 2 ++ src/main/resources/logback.xml | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index d6467704..975fd00e 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -87,6 +87,7 @@ microservice-chart: µservice-chart ENV: "dev" TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights + SERVICE_NAME: "pagopa-receipt-pdf-datastore" ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" RECEIPT_QUEUE_TOPIC: "pagopa-d-weu-receipts-queue-receipt-waiting-4-gen" @@ -199,6 +200,7 @@ pagopa-receipt-pdf-datastore-helpdesk: envConfig: !!merge <<: *envConfig WEBSITE_SITE_NAME: 'pagopareceiptpdfdatastorehelpdesk' + SERVICE_NAME: "pagopa-receipt-pdf-datastore-helpdesk" AzureWebJobs.BizEventToReceiptProcessor.Disabled: "true" AzureWebJobs.CartReceiptToReviewed.Disabled: "false" AzureWebJobs.ReceiptToReviewed.Disabled: "false" diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 7de525be..af8d1513 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -87,6 +87,7 @@ microservice-chart: µservice-chart ENV: "prod" TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights + SERVICE_NAME: "pagopa-receipt-pdf-datastore" ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" RECEIPT_QUEUE_TOPIC: "pagopa-p-weu-receipts-queue-receipt-waiting-4-gen" @@ -214,6 +215,7 @@ pagopa-receipt-pdf-datastore-helpdesk: envConfig: !!merge <<: *envConfig WEBSITE_SITE_NAME: 'pagopareceiptpdfdatastorehelpdesk' + SERVICE_NAME: "pagopa-receipt-pdf-datastore-helpdesk" COSMOS_BIZ_EVENT_READ_REGION: "North Europe" COSMOS_RECEIPT_READ_REGION: "North Europe" AzureWebJobs.BizEventToReceiptProcessor.Disabled: "true" diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index 86320609..e2250bee 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -87,6 +87,7 @@ microservice-chart: µservice-chart ENV: "uat" TZ: "Europe/Rome" WEBSITE_SITE_NAME: "pagopareceiptpdfdatastore" # required to show cloud role name in application insights + SERVICE_NAME: "pagopa-receipt-pdf-datastore" ASPNETCORE_URLS: "http://*:8080" FUNCTIONS_WORKER_RUNTIME: "java" RECEIPT_QUEUE_TOPIC: "pagopa-u-weu-receipts-queue-receipt-waiting-4-gen" @@ -199,6 +200,7 @@ pagopa-receipt-pdf-datastore-helpdesk: envConfig: !!merge <<: *envConfig WEBSITE_SITE_NAME: 'pagopareceiptpdfdatastorehelpdesk' + SERVICE_NAME: "pagopa-receipt-pdf-datastore-helpdesk" AzureWebJobs.BizEventToReceiptProcessor.Disabled: "true" AzureWebJobs.CartReceiptToReviewed.Disabled: "false" AzureWebJobs.ReceiptToReviewed.Disabled: "false" diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 94a3bea6..b88e09c3 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -13,7 +13,7 @@ - ${name} + ${SERVICE_NAME} ${version} ${ENV} From 2a2188609b65bc75b396b06b84f5de1829842248 Mon Sep 17 00:00:00 2001 From: Jacopo Carlini Date: Thu, 22 Jan 2026 16:51:23 +0100 Subject: [PATCH 38/38] helpdesk alert --- .../datastore/helpdesk/http/CartReceiptToReviewed.java | 3 ++- .../pdf/datastore/helpdesk/http/ReceiptToReviewed.java | 3 ++- .../datastore/helpdesk/http/RecoverFailedCartReceipt.java | 8 ++++---- .../helpdesk/http/RecoverFailedCartReceiptMassive.java | 3 +++ .../pdf/datastore/helpdesk/http/RecoverFailedReceipt.java | 8 ++++---- .../helpdesk/http/RecoverFailedReceiptMassive.java | 3 +++ .../helpdesk/http/RecoverNotNotifiedCartReceipt.java | 4 ++-- .../http/RecoverNotNotifiedCartReceiptMassive.java | 2 ++ .../helpdesk/http/RecoverNotNotifiedReceipt.java | 4 ++-- .../helpdesk/http/RecoverNotNotifiedReceiptMassive.java | 2 ++ 10 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java index c7ab6f91..9360c825 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/CartReceiptToReviewed.java @@ -77,12 +77,13 @@ public HttpResponseMessage run( receiptError = cartReceiptCosmosService.getCartReceiptError(cartId); } catch (NoSuchElementException | CartNotFoundException e) { responseMsg = String.format("No cartReceiptError has been found with cartId %s", cartId); - logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); + logger.warn("[{}] {}", context.getFunctionName(), responseMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, responseMsg); } if (!ReceiptErrorStatusType.TO_REVIEW.equals(receiptError.getStatus())) { responseMsg = String.format("Found cartReceiptError with invalid status %s for cartId %s", receiptError.getStatus(), cartId); + logger.error("[{}] {}", context.getFunctionName(), responseMsg); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, responseMsg); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java index 70348eb3..3df3bf20 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/ReceiptToReviewed.java @@ -77,12 +77,13 @@ public HttpResponseMessage run( receiptError = receiptCosmosService.getReceiptError(eventId); } catch (NoSuchElementException | ReceiptNotFoundException e) { responseMsg = String.format("No receiptError has been found with bizEventId %s", eventId); - logger.error("[{}] {}", context.getFunctionName(), responseMsg, e); + logger.warn("[{}] {}", context.getFunctionName(), responseMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, responseMsg); } if (!ReceiptErrorStatusType.TO_REVIEW.equals(receiptError.getStatus())) { responseMsg = String.format("Found receiptError with invalid status %s for bizEventId %s", receiptError.getStatus(), eventId); + logger.error("[{}] {}", context.getFunctionName(), responseMsg); return buildErrorResponse(request, HttpStatus.INTERNAL_SERVER_ERROR, responseMsg); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java index e00d175a..518b022f 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceipt.java @@ -106,7 +106,7 @@ public HttpResponseMessage run( existingCart = this.cartReceiptCosmosService.getCart(cartId); } catch (CartNotFoundException e) { String errMsg = "Cart receipt not found with the provided cart id"; - logger.error(errMsg, e); + logger.warn(errMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } @@ -116,7 +116,7 @@ public HttpResponseMessage run( "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", existingCart.getStatus() ); - logger.error(errMsg); + logger.warn(errMsg); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } @@ -124,10 +124,10 @@ public HttpResponseMessage run( try { cart = this.helpdeskService.recoverFailedCart(existingCart); } catch (BizEventUnprocessableEntityException e) { - logger.error(e.getMessage(), e); + logger.warn(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); } catch (BizEventBadRequestException e) { - logger.error(e.getMessage(), e); + logger.warn(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } catch (PDVTokenizerException | JsonProcessingException e) { logger.error(e.getMessage(), e); diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java index e1d41502..02460702 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedCartReceiptMassive.java @@ -82,12 +82,14 @@ public HttpResponseMessage run( try { status = validateCartStatusParam(statusParam); } catch (InvalidParameterException e) { + logger.warn("[{}]", context.getFunctionName(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } if (status == null || !status.isAFailedDatastoreStatus()) { String message = String.format("The provided status %s is not among the processable" + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + logger.warn("[{}] {}", context.getFunctionName(), message); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); } @@ -100,6 +102,7 @@ public HttpResponseMessage run( documentdb.setValue(recoverResult.getFailedCartList()); String msg = String.format("Recovered %s receipts but %s encountered an error.", successCounter, errorCounter); + logger.error("[{}] {}", context.getFunctionName(), msg); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) .body(ProblemJson.builder() diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java index 41f08f78..63e9e61b 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceipt.java @@ -104,7 +104,7 @@ public HttpResponseMessage run( existingReceipt = this.receiptCosmosService.getReceipt(eventId); } catch (ReceiptNotFoundException e) { String errMsg = "Receipt not found with the provided event id"; - logger.error(errMsg, e); + logger.warn(errMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } @@ -114,7 +114,7 @@ public HttpResponseMessage run( "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", existingReceipt.getStatus() ); - logger.error(errMsg); + logger.warn(errMsg); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } @@ -122,10 +122,10 @@ public HttpResponseMessage run( try { receipt = this.helpdeskService.recoverFailedReceipt(existingReceipt); } catch (BizEventUnprocessableEntityException e) { - logger.error(e.getMessage(), e); + logger.warn(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage()); } catch (BizEventBadRequestException | BizEventNotFoundException e) { - logger.error(e.getMessage(), e); + logger.warn(e.getMessage(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java index fb8985e9..ef2f7289 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverFailedReceiptMassive.java @@ -82,12 +82,14 @@ public HttpResponseMessage run( try { status = validateReceiptStatusParam(statusParam); } catch (InvalidParameterException e) { + logger.warn("[{}]", context.getFunctionName(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } if (status == null || !status.isAFailedDatastoreStatus()) { String message = String.format("The provided status %s is not among the processable" + "statuses (INSERTED, NOT_QUEUE_SENT, FAILED).", status); + logger.warn("[{}] {}", context.getFunctionName(), message); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); } @@ -100,6 +102,7 @@ public HttpResponseMessage run( documentdb.setValue(recoverResult.getFailedReceiptList()); String msg = String.format("Recovered %s receipts but %s encountered an error.", successCounter, errorCounter); + logger.error("[{}] {}", context.getFunctionName(), msg); return request .createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) .body(ProblemJson.builder() diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java index c8e98471..44addd3d 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceipt.java @@ -81,14 +81,14 @@ public HttpResponseMessage run( cart = this.cartReceiptCosmosService.getCart(cartId); } catch (CartNotFoundException e) { String errMsg = String.format("Unable to retrieve the cart receipt with id %s", cartId); - logger.error("[{}] {}", context.getFunctionName(), errMsg, e); + logger.warn("[{}] {}", context.getFunctionName(), errMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } if (cart.getStatus() == null || !cart.getStatus().isANotificationFailedStatus()) { String errMsg = String.format("The requested cart receipt with id %s is not in the expected status", cart.getEventId()); - logger.error(errMsg); + logger.warn(errMsg); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java index c6f0a5cb..b27599db 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedCartReceiptMassive.java @@ -76,12 +76,14 @@ public HttpResponseMessage run( try { status = validateCartStatusParam(statusParam); } catch (InvalidParameterException e) { + logger.warn("[{}]", context.getFunctionName(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } if (status == null || !status.isANotificationFailedStatus()) { String message = String.format("The provided status %s is not among the processable" + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + logger.warn("[{}] {}", context.getFunctionName(), message); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java index 1c9d9040..7363fce6 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceipt.java @@ -80,14 +80,14 @@ public HttpResponseMessage run( receipt = this.receiptCosmosService.getReceipt(eventId); } catch (ReceiptNotFoundException e) { String errMsg = String.format("Unable to retrieve the receipt with eventId %s", eventId); - logger.error("[{}] {}", context.getFunctionName(), errMsg, e); + logger.warn("[{}] {}", context.getFunctionName(), errMsg, e); return buildErrorResponse(request, HttpStatus.NOT_FOUND, errMsg); } if (receipt.getStatus() == null || !receipt.getStatus().isANotificationFailedStatus()) { String errMsg = String.format("The requested receipt with eventId %s is not in the expected status", receipt.getEventId()); - logger.error(errMsg); + logger.warn(errMsg); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, errMsg); } diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java index 8b610176..5075cc01 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/datastore/helpdesk/http/RecoverNotNotifiedReceiptMassive.java @@ -77,12 +77,14 @@ public HttpResponseMessage run( try { status = validateReceiptStatusParam(statusParam); } catch (InvalidParameterException e) { + logger.warn("[{}]", context.getFunctionName(), e); return buildErrorResponse(request, HttpStatus.BAD_REQUEST, e.getMessage()); } if (status == null || !status.isANotificationFailedStatus()) { String message = String.format("The provided status %s is not among the processable" + "statuses (GENERATED, IO_ERROR_TO_NOTIFY).", status); + logger.warn("[{}] {}", context.getFunctionName(), message); return buildErrorResponse(request, HttpStatus.UNPROCESSABLE_ENTITY, message); }