diff --git a/README.md b/README.md
index 0f6354b..4388b3e 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,147 @@
-# pagoPA Functions template
+# pagoPA Receipt-pdf-helpdesk
-Java template to create an Azure Function.
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pagopa_pagopa-receipt-pdf-helpdesk&metric=alert_status)](https://sonarcloud.io/dashboard?id=pagopa_pagopa-receipt-pdf-helpdesk)
-## Function examples
-There is an example of a Http Trigger function.
+Java Azure Functions that exposed the following recover APIs:
+- ReceiptToReviewed
+- RecoverFailedReceipt
+- RecoverNotNotifiedReceipt
---
-## Run locally with Docker
-`docker build -t pagopa-functions-template .`
+## Summary ๐
-`docker run -p 8999:80 pagopa-functions-template`
+- [Api Documentation ๐](#api-documentation-)
+- [Start Project Locally ๐](#start-project-locally-)
+ * [Run locally with Docker](#run-locally-with-docker)
+ + [Prerequisites](#prerequisites)
+ + [Run docker container](#run-docker-container)
+ * [Run locally with Maven](#run-locally-with-maven)
+ + [Prerequisites](#prerequisites-1)
+ + [Set environment variables](#set-environment-variables)
+ + [Run the project](#run-the-project)
+ * [Test](#test)
+- [Develop Locally ๐ป](#develop-locally-)
+ * [Prerequisites](#prerequisites-2)
+ * [Testing ๐งช](#testing-)
+ + [Unit testing](#unit-testing)
+ + [Integration testing](#integration-testing)
+ + [Performance testing](#performance-testing)
+- [Contributors ๐ฅ](#contributors-)
+ * [Maintainers](#maintainers)
-### Test
-`curl http://localhost:8999/example`
+---
+
+## Api Documentation ๐
+
+See
+the [OpenApi 3 here](https://editor.swagger.io/?url=https://raw.githubusercontent.com/pagopa/pagopa-receipt-pdf-helpdesk/main/openapi/openapi.json)
+
+## Start Project Locally ๐
+
+### Run locally with Docker
+
+#### Prerequisites
+
+- docker
+
+#### Set environment variables
+
+`docker build -t pagopa-receip-pdf-helpdesk .`
+
+`cp .env.example .env`
+
+and replace in `.env` with correct values
+
+#### Run docker container
+
+then type :
+
+`docker run -p 80:80 --env-file=./.env pagopa-receip-pdf-helpdesk`
+
+### Run locally with Maven
+
+#### Prerequisites
+
+- maven
+
+#### Set environment variables
-## Run locally with Maven
+On terminal type:
+
+`cp local.settings.json.example local.settings.json`
+
+then replace env variables with correct values
+(if there is NO default value, the variable HAS to be defined)
+
+| VARIABLE | USAGE | DEFAULT VALUE |
+|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------:|
+| `RECEIPT_QUEUE_CONN_STRING` | Connection string to the Receipt Queue | |
+| `RECEIPT_QUEUE_TOPIC` | Topic name of the Receipt Queue | |
+| `COSMOS_BIZ_EVENT_CONN_STRING` | Connection string to the BizEvent CosmosDB | |
+| `COSMOS_BIZ_EVENT_SERVICE_ENDPOINT` | Endpoint to the BizEvent CosmosDB | |
+| `COSMOS_BIZ_EVENT_DB_NAME` | Database name of the BizEvent database in CosmosDB | |
+| `COSMOS_BIZ_EVENT_CONTAINER_NAME` | Container name of the BizEvent container in CosmosDB | |
+| `COSMOS_RECEIPTS_CONN_STRING` | Connection string to the Receipt CosmosDB | |
+| `COSMOS_RECEIPT_SERVICE_ENDPOINT` | Endpoint to the Receipt CosmosDB | |
+| `COSMOS_RECEIPT_KEY` | Key to the Receipt CosmosDB | |
+| `COSMOS_RECEIPT_DB_NAME` | Database name of the Receipt database in CosmosDB | |
+| `COSMOS_RECEIPT_CONTAINER_NAME` | Container name of the Receipt container in CosmosDB | |
+| `COSMOS_RECEIPT_ERROR_CONTAINER_NAME` | Container name of the Receipt-message-error container in CosmosDB | |
+| `PDV_TOKENIZER_BASE_PATH` | PDV Tokenizer API base path | "https://api.uat.tokenizer.pdv.pagopa.it/tokenizer/v1" |
+| `PDV_TOKENIZER_SEARCH_TOKEN_ENDPOINT` | PDV Tokenizer API search token endpoint | "/tokens/search" |
+| `PDV_TOKENIZER_FIND_PII_ENDPOINT` | PDV Tokenizer API find pii endpoint | "/tokens/%s/pii" |
+| `PDV_TOKENIZER_CREATE_TOKEN_ENDPOINT` | PDV Tokenizer API create token endpoint | "/tokens" |
+| `PDV_TOKENIZER_SUBSCRIPTION_KEY` | API azure ocp apim subscription key | |
+| `PDV_TOKENIZER_INITIAL_INTERVAL` | PDV Tokenizer initial interval for retry a request that fail with 429 status code | 200 |
+| `PDV_TOKENIZER_MULTIPLIER` | PDV Tokenizer interval multiplier for subsequent request retry | 2.0 |
+| `PDV_TOKENIZER_RANDOMIZATION_FACTOR` | PDV Tokenizer randomization factor for interval retry calculation | 0.6 |
+| `PDV_TOKENIZER_MAX_RETRIES` | PDV Tokenizer max request retry | 3 |
+| `TOKENIZER_APIM_HEADER_KEY` | Tokenizer APIM header key | x-api-key |
+| `MAX_DATE_DIFF_MILLIS` | Difference in millis between the current time and the date from witch the receipts will be fetched in massive recover operation | 360000 |
+
+> to doc details about AZ fn config
+> see [here](https://stackoverflow.com/questions/62669672/azure-functions-what-is-the-purpose-of-having-host-json-and-local-settings-jso)
+
+
+#### Run the project
`mvn clean package`
`mvn azure-functions:run`
### Test
-`curl http://localhost:7071/example`
+
+`curl http://localhost:8080/info`
---
+## Develop Locally ๐ป
+
+### Prerequisites
+
+- git
+- maven
+- jdk-17
+
+### Testing ๐งช
+
+#### Unit testing
+
+To run the **Junit** tests:
+
+`mvn clean verify`
+
+#### Integration testing
+
+#### Performance testing
+
+---
+
+## Contributors ๐ฅ
+
+Made with โค๏ธ by PagoPa S.p.A.
-## TODO
-Once cloned the repo, you should:
-- to deploy on standard Azure service:
- - rename `deploy-pipelines-standard.yml` to `deploy-pipelines.yml`
- - remove `helm` folder
-- to deploy on Kubernetes:
- - rename `deploy-pipelines-aks.yml` to `deploy-pipelines.yml`
- - customize `helm` configuration
-- configure the following GitHub action in `.github` folder:
- - `deploy.yml`
- - `sonar_analysis.yml`
+### Maintainers
-Configure the SonarCloud project :point_right: [guide](https://pagopa.atlassian.net/wiki/spaces/DEVOPS/pages/147193860/SonarCloud+experimental).
\ No newline at end of file
+See `CODEOWNERS` file
\ No newline at end of file
diff --git a/openapi/openapi.json b/openapi/openapi.json
index 5b7b8d7..e38e583 100644
--- a/openapi/openapi.json
+++ b/openapi/openapi.json
@@ -233,6 +233,148 @@
}
}
]
+ },
+ "/recoverNotNotified": {
+ "put": {
+ "tags": [
+ "Receipts REST APIs"
+ ],
+ "summary": "Recover a receipt, or group of, in IO_ERROR_TO_NOTIFY or GENERATED status",
+ "operationId": "recoverNotNotified",
+ "parameters": [],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/NotNotifiedRecoveryRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Succesfull Calls.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad request invalid input.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Wrong or missing function key.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Requested receipt not found.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "429": {
+ "description": "Too many requests.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "The requested receipts is not in a status that can be elaborated.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error.",
+ "headers": {
+ "X-Request-Id": {
+ "description": "This header identifies the call",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "ApiKey": []
+ }
+ ]
+ },
+ "parameters": [
+ {
+ "name": "X-Request-Id",
+ "in": "header",
+ "description": "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ]
}
},
"components": {
@@ -260,6 +402,24 @@
}
}
},
+ "NotNotifiedRecoveryRequest": {
+ "type": "object",
+ "description": "The request body for recoverNotNotified API, at least one of generatedStatus or ioErrorToNotifyStatus must be true. The field eventId when not provided enable the massive operation",
+ "properties": {
+ "eventId": {
+ "type": "string",
+ "description": "Id of the event to start recovering (optional)"
+ },
+ "generatedStatus": {
+ "type": "boolean",
+ "description": "True to recover the receipts in GENERATED status, false otherwise"
+ },
+ "ioErrorToNotifyStatus": {
+ "type": "boolean",
+ "description": "True to recover the receipts in IO_ERROR_TO_NOTIFY status, false otherwise"
+ }
+ }
+ },
"ProblemJson": {
"type": "object",
"properties": {
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverFailedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverFailedReceipt.java
index cbbaa67..61d49d8 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverFailedReceipt.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverFailedReceipt.java
@@ -163,7 +163,7 @@ private void getEvent(String eventId, ExecutionContext context,
bizEventToReceiptService.handleSendMessageToQueue(bizEvent, receipt);
if(receipt.getStatus() != ReceiptStatusType.NOT_QUEUE_SENT){
receipt.setStatus(ReceiptStatusType.INSERTED);
- receipt.setInserted_at(System.currentTimeMillis());
+ receipt.setInsertedAt(System.currentTimeMillis());
receipt.setReasonErr(null);
receipt.setReasonErrPayer(null);
}
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceipt.java
new file mode 100644
index 0000000..4204c66
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceipt.java
@@ -0,0 +1,185 @@
+package it.gov.pagopa.receipt.pdf.helpdesk;
+
+import com.azure.cosmos.models.FeedResponse;
+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.helpdesk.client.ReceiptCosmosClient;
+import it.gov.pagopa.receipt.pdf.helpdesk.client.impl.ReceiptCosmosClientImpl;
+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.NotNotifiedRecoveryRequest;
+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.Optional;
+
+/**
+ * Azure Functions with HTTP Trigger.
+ */
+public class RecoverNotNotifiedReceipt {
+
+ private final Logger logger = LoggerFactory.getLogger(RecoverNotNotifiedReceipt.class);
+
+ private final ReceiptCosmosClient receiptCosmosClient;
+
+ public RecoverNotNotifiedReceipt() {
+ this.receiptCosmosClient = ReceiptCosmosClientImpl.getInstance();
+ }
+
+ RecoverNotNotifiedReceipt(ReceiptCosmosClient receiptCosmosClient) {
+ this.receiptCosmosClient = receiptCosmosClient;
+ }
+
+ /**
+ * This function will be invoked when a Http Trigger occurs.
+ *
+ * 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}).
+ *
+ * If invoked with a specific eventId it restore the associated receipt, otherwise it restore all receipt with status
+ * {@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}.
+ *
+ * @return response with {@link HttpStatus#OK} if the notification succeeded
+ */
+ @FunctionName("RecoverNotNotifiedReceipt")
+ public HttpResponseMessage run(
+ @HttpTrigger(name = "RecoverNotNotifiedTrigger",
+ methods = {HttpMethod.PUT},
+ route = "recoverNotNotified",
+ authLevel = AuthorizationLevel.FUNCTION)
+ HttpRequestMessage> request,
+ @CosmosDBOutput(
+ name = "ReceiptDatastore",
+ databaseName = "db",
+ collectionName = "receipts",
+ connectionStringSetting = "COSMOS_RECEIPTS_CONN_STRING")
+ OutputBinding> documentReceipts,
+ final ExecutionContext context) {
+ logger.info("[{}] function called at {}", context.getFunctionName(), LocalDateTime.now());
+
+ Optional recoveryRequestOptional = request.getBody();
+ if (recoveryRequestOptional.isEmpty()) {
+ return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a valid body").build();
+ }
+ NotNotifiedRecoveryRequest recoveryRequest = recoveryRequestOptional.get();
+
+ List statusToRestore = getStatusToRestore(recoveryRequest);
+ if (statusToRestore.isEmpty()) {
+ return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please select at least one status to recover").build();
+ }
+
+ String eventId = recoveryRequest.getEventId();
+ if (eventId != null) {
+ Receipt receipt;
+ try {
+ receipt = 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(responseMsg).build();
+ }
+
+ if (!statusToRestore.contains(receipt.getStatus())) {
+ 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(responseMsg).build();
+ }
+
+ Receipt restoredReceipt = restoreReceipt(receipt);
+
+ documentReceipts.setValue(Collections.singletonList(restoredReceipt));
+ String responseMsg = String.format("Receipt with id %s and eventId %s restored int status %s with success",
+ receipt.getId(), receipt.getEventId(), ReceiptStatusType.GENERATED);
+ return request.createResponseBuilder(HttpStatus.OK).body(responseMsg).build();
+ }
+
+ List receiptList = receiptMassiveRestore(recoveryRequest);
+ 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();
+ }
+
+ private List getStatusToRestore(NotNotifiedRecoveryRequest recoveryRequest) {
+ List statusToRestore = new ArrayList<>();
+ if (recoveryRequest.isGeneratedStatus()) {
+ statusToRestore.add(ReceiptStatusType.GENERATED);
+ }
+ if (recoveryRequest.isIoErrorToNotifyStatus()) {
+ statusToRestore.add(ReceiptStatusType.IO_ERROR_TO_NOTIFY);
+ }
+ return statusToRestore;
+ }
+
+ private List receiptMassiveRestore(NotNotifiedRecoveryRequest recoveryRequest) {
+ List receiptList = new ArrayList<>();
+ String continuationToken = null;
+ do {
+ Iterable> feedResponseIterator =
+ receiptCosmosClient
+ .getNotNotifiedReceiptDocuments(
+ continuationToken,
+ 100,
+ recoveryRequest.isIoErrorToNotifyStatus(),
+ recoveryRequest.isGeneratedStatus()
+ );
+
+ 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 Receipt restoreReceipt(Receipt receipt) {
+ receipt.setStatus(ReceiptStatusType.GENERATED);
+ receipt.setNotificationNumRetry(0);
+ receipt.setNotifiedAt(0);
+
+ if (receipt.getReasonErr() != null) {
+ receipt.setReasonErr(null);
+ }
+ if (receipt.getReasonErrPayer() != null) {
+ receipt.setReasonErrPayer(null);
+ }
+ return receipt;
+ }
+
+ //Retrieve receipt from CosmosDB
+ private Receipt getReceipt(String eventId) throws ReceiptNotFoundException {
+ Receipt receipt;
+ try {
+ receipt = 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;
+ }
+}
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/ReceiptCosmosClient.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/ReceiptCosmosClient.java
index ff3a251..18067fc 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/ReceiptCosmosClient.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/ReceiptCosmosClient.java
@@ -4,11 +4,12 @@
import com.azure.cosmos.models.FeedResponse;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.ReceiptError;
+import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType;
import it.gov.pagopa.receipt.pdf.helpdesk.exception.ReceiptNotFoundException;
public interface ReceiptCosmosClient {
- Receipt getReceiptDocument(String receiptId) throws ReceiptNotFoundException;
+ Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundException;
Iterable> getFailedReceiptDocuments(String continuationToken, Integer pageSize);
@@ -17,4 +18,21 @@ public interface ReceiptCosmosClient {
ReceiptError getReceiptError(String bizEventId) throws ReceiptNotFoundException;
Iterable> getToReviewReceiptsError(String continuationToken, Integer pageSize);
+
+ /**
+ * Retrieve the receipt documents with status {@link ReceiptStatusType#IO_ERROR_TO_NOTIFY}
+ * or {@link ReceiptStatusType#GENERATED} from Cosmos database
+ *
+ * @param continuationToken Paged query continuation token
+ * @param pageSize the page size
+ * @param ioErrorToNotifyStatus true if the receipts must be in {@link ReceiptStatusType#IO_ERROR_TO_NOTIFY} status, false otherwise
+ * @param generatedStatus true if the receipts must be in {@link ReceiptStatusType#GENERATED} status, false otherwise
+ * @return receipt documents
+ */
+ Iterable> getNotNotifiedReceiptDocuments(
+ String continuationToken,
+ Integer pageSize,
+ boolean ioErrorToNotifyStatus,
+ boolean generatedStatus
+ );
}
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImpl.java
index 76871f9..e0c3f84 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImpl.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImpl.java
@@ -4,12 +4,15 @@
import com.azure.cosmos.CosmosClientBuilder;
import com.azure.cosmos.CosmosContainer;
import com.azure.cosmos.CosmosDatabase;
-import com.azure.cosmos.models.*;
+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.helpdesk.client.ReceiptCosmosClient;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.ReceiptError;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptErrorStatusType;
+import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType;
import it.gov.pagopa.receipt.pdf.helpdesk.exception.ReceiptNotFoundException;
import java.time.OffsetDateTime;
@@ -47,7 +50,6 @@ public static ReceiptCosmosClientImpl getInstance() {
if (instance == null) {
instance = new ReceiptCosmosClientImpl();
}
-
return instance;
}
@@ -60,7 +62,6 @@ public static ReceiptCosmosClientImpl getInstance() {
*/
public Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundException {
CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId);
-
CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId);
//Build query
@@ -75,7 +76,6 @@ public Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundExceptio
} else {
throw new ReceiptNotFoundException("Document not found in the defined container");
}
-
}
/**
@@ -87,7 +87,6 @@ public Receipt getReceiptDocument(String eventId) throws ReceiptNotFoundExceptio
@Override
public Iterable> getFailedReceiptDocuments(String continuationToken, Integer pageSize) {
CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId);
-
CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId);
//Build query
@@ -99,7 +98,6 @@ public Iterable> getFailedReceiptDocuments(String continua
return cosmosContainer
.queryItems(query, new CosmosQueryRequestOptions(), Receipt.class)
.iterableByPage(continuationToken,pageSize);
-
}
/**
@@ -111,7 +109,6 @@ public Iterable> getFailedReceiptDocuments(String continua
@Override
public CosmosItemResponse saveReceipts(Receipt receipt) {
CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId);
-
CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId);
return cosmosContainer.createItem(receipt);
@@ -165,4 +162,44 @@ public Iterable> getToReviewReceiptsError(String cont
.iterableByPage(continuationToken,pageSize);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterable> getNotNotifiedReceiptDocuments(
+ String continuationToken,
+ Integer pageSize,
+ boolean ioErrorToNotifyStatus,
+ boolean generatedStatus
+ ) {
+ CosmosDatabase cosmosDatabase = this.cosmosClient.getDatabase(databaseId);
+ CosmosContainer cosmosContainer = cosmosDatabase.getContainer(containerId);
+
+ if (!ioErrorToNotifyStatus && !generatedStatus) {
+ throw new IllegalArgumentException("at least one param must be true");
+ }
+
+ //Build query
+ String query = buildQuery(ioErrorToNotifyStatus, generatedStatus);
+
+ //Query the container
+ return cosmosContainer
+ .queryItems(query, new CosmosQueryRequestOptions(), Receipt.class)
+ .iterableByPage(continuationToken,pageSize);
+ }
+
+ private String buildQuery(boolean ioErrorToNotifyStatus, boolean generatedStatus) {
+ String query = "SELECT *CosmosPagedIterable FROM c WHERE ";
+ String ioErrorNotifyParam = String.format("c.status = '%s'", ReceiptStatusType.IO_ERROR_TO_NOTIFY);
+ String generatedParam = String.format("(c.status= = '%s' AND ( %s - c.inserted_at) >= %s)",
+ ReceiptStatusType.GENERATED, OffsetDateTime.now().toInstant().toEpochMilli(), millisDiff);
+
+ if (ioErrorToNotifyStatus && generatedStatus) {
+ return query.concat(ioErrorNotifyParam).concat(" AND ").concat(generatedParam);
+ }
+ if (ioErrorToNotifyStatus) {
+ return query.concat(ioErrorNotifyParam);
+ }
+ return query.concat(generatedParam);
+ }
}
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/CartItem.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/CartItem.java
index a9387b8..2eb2431 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/CartItem.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/CartItem.java
@@ -1,5 +1,7 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -7,6 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
+@AllArgsConstructor
+@Builder
public class CartItem {
private String subject;
private String payeeName;
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/EventData.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/EventData.java
index 0fd5512..44b9e3b 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/EventData.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/EventData.java
@@ -1,5 +1,7 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -9,6 +11,8 @@
@Getter
@Setter
@NoArgsConstructor
+@AllArgsConstructor
+@Builder
public class EventData {
private String payerFiscalCode;
private String debtorFiscalCode;
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/IOMessageData.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/IOMessageData.java
index 2e47de3..fb4f494 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/IOMessageData.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/IOMessageData.java
@@ -1,5 +1,7 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -7,6 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
+@AllArgsConstructor
+@Builder
public class IOMessageData {
private String idMessageDebtor;
private String idMessagePayer;
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReasonError.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReasonError.java
index a81e9bd..e243420 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReasonError.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReasonError.java
@@ -1,6 +1,7 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -9,6 +10,7 @@
@Setter
@NoArgsConstructor
@AllArgsConstructor
+@Builder
public class ReasonError {
private int code;
private String message;
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/Receipt.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/Receipt.java
index c38cf4d..fbf934e 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/Receipt.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/Receipt.java
@@ -1,6 +1,9 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
+import com.fasterxml.jackson.annotation.JsonProperty;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.enumeration.ReceiptStatusType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -8,6 +11,8 @@
@Getter
@Setter
@NoArgsConstructor
+@AllArgsConstructor
+@Builder
public class Receipt {
private String eventId;
@@ -21,8 +26,12 @@ public class Receipt {
private int numRetry;
private ReasonError reasonErr;
private ReasonError reasonErrPayer;
- private long inserted_at;
- private long generated_at;
- private long notified_at;
+ private int notificationNumRetry;
+ @JsonProperty("inserted_at")
+ private long insertedAt;
+ @JsonProperty("generated_at")
+ private long generatedAt;
+ @JsonProperty("notified_at")
+ private long notifiedAt;
}
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReceiptMetadata.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReceiptMetadata.java
index 2554e1c..cd2eda0 100644
--- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReceiptMetadata.java
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/entity/receipt/ReceiptMetadata.java
@@ -1,5 +1,7 @@
package it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -7,6 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
+@AllArgsConstructor
+@Builder
public class ReceiptMetadata {
private String name;
diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/model/NotNotifiedRecoveryRequest.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/model/NotNotifiedRecoveryRequest.java
new file mode 100644
index 0000000..93058a3
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/model/NotNotifiedRecoveryRequest.java
@@ -0,0 +1,16 @@
+package it.gov.pagopa.receipt.pdf.helpdesk.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class NotNotifiedRecoveryRequest {
+
+ private String eventId;
+ private boolean generatedStatus;
+ private boolean ioErrorToNotifyStatus;
+
+}
\ No newline at end of file
diff --git a/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceiptTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceiptTest.java
new file mode 100644
index 0000000..9ea871c
--- /dev/null
+++ b/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/RecoverNotNotifiedReceiptTest.java
@@ -0,0 +1,422 @@
+package it.gov.pagopa.receipt.pdf.helpdesk;
+
+import com.azure.cosmos.models.FeedResponse;
+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.helpdesk.client.ReceiptCosmosClient;
+import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.ReasonError;
+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.NotNotifiedRecoveryRequest;
+import it.gov.pagopa.receipt.pdf.helpdesk.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.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+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.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RecoverNotNotifiedReceiptTest {
+
+ private static final String EVENT_ID = "eventId";
+
+ private final ExecutionContext executionContextMock = mock(ExecutionContext.class);
+
+ @Mock
+ private ReceiptCosmosClient receiptCosmosClientMock;
+
+ @Spy
+ OutputBinding> documentReceipts;
+
+ @Captor
+ private ArgumentCaptor> receiptCaptor;
+
+ private RecoverNotNotifiedReceipt sut;
+
+ private AutoCloseable closeable;
+
+ @BeforeEach
+ public void openMocks() {
+ closeable = MockitoAnnotations.openMocks(this);
+ sut = spy(new RecoverNotNotifiedReceipt(receiptCosmosClientMock));
+ }
+
+ @AfterEach
+ public void releaseMocks() throws Exception {
+ closeable.close();
+ }
+
+ @Test
+ void recoverNotNotifiedReceiptWithEventIdSuccess() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ Receipt receipt = buildReceipt();
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(receipt);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, 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 recoverNotNotifiedReceiptWithoutEventIdSuccess() {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(null, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ FeedResponse feedResponseMock = mock(FeedResponse.class);
+ List receiptList = getReceiptList();
+ when(feedResponseMock.getResults()).thenReturn(receiptList);
+ when(receiptCosmosClientMock.getNotNotifiedReceiptDocuments(any(), any(), anyBoolean(), anyBoolean()))
+ .thenReturn(Collections.singletonList(feedResponseMock));
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, 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 recoverNotNotifiedReceiptWithoutEventIdSuccessWithNoReceiptUpdated() {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(null, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ FeedResponse feedResponseMock = mock(FeedResponse.class);
+ when(feedResponseMock.getResults()).thenReturn(Collections.emptyList());
+ when(receiptCosmosClientMock.getNotNotifiedReceiptDocuments(any(), any(), anyBoolean(), anyBoolean()))
+ .thenReturn(Collections.singletonList(feedResponseMock));
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.OK, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailForEmptyBody() {
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.empty());
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailForInvalidInputParams() {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, false);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptNotFound() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenThrow(ReceiptNotFoundException.class);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptFoundIsNull() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(null);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptInGeneratedButOnlyIOErrorToNotify() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ Receipt receipt = new Receipt();
+ receipt.setStatus(ReceiptStatusType.GENERATED);
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(receipt);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptInIOErrorToNotifyButOnlyGenerated() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, true, false);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ Receipt receipt = new Receipt();
+ receipt.setStatus(ReceiptStatusType.IO_ERROR_TO_NOTIFY);
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(receipt);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptInInsertedButOnlyGenerated() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, true, false);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ Receipt receipt = new Receipt();
+ receipt.setStatus(ReceiptStatusType.INSERTED);
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(receipt);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus());
+ assertNotNull(response.getBody());
+
+ verify(documentReceipts, never()).setValue(receiptCaptor.capture());
+ }
+
+ @Test
+ void recoverReceiptFailReceiptInInsertedButOnlyIOErrorToNotify() throws ReceiptNotFoundException {
+ NotNotifiedRecoveryRequest recoveryRequest = new NotNotifiedRecoveryRequest(EVENT_ID, false, true);
+
+ @SuppressWarnings("unchecked")
+ HttpRequestMessage> request = mock(HttpRequestMessage.class);
+ when(request.getBody()).thenReturn(Optional.of(recoveryRequest));
+
+ Receipt receipt = new Receipt();
+ receipt.setStatus(ReceiptStatusType.INSERTED);
+ when(receiptCosmosClientMock.getReceiptDocument(EVENT_ID)).thenReturn(receipt);
+
+ doAnswer((Answer) invocation -> {
+ HttpStatus status = (HttpStatus) invocation.getArguments()[0];
+ return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
+ }).when(request).createResponseBuilder(any(HttpStatus.class));
+
+ // test execution
+ HttpResponseMessage response = sut.run(request, documentReceipts, executionContextMock);
+
+ // test assertion
+ assertNotNull(response);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatus());
+ assertNotNull(response.getBody());
+
+ 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)
+ .insertedAt(0)
+ .generatedAt(0)
+ .notifiedAt(0)
+ .build();
+ }
+
+ private List getReceiptList() {
+ List receiptList = new ArrayList<>();
+ Receipt receipt1 = buildReceipt();
+ Receipt receipt2 = buildReceipt();
+ 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/helpdesk/client/impl/ReceiptCosmosClientImplTest.java b/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImplTest.java
index ea640db..fd3b42d 100644
--- a/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImplTest.java
+++ b/src/test/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptCosmosClientImplTest.java
@@ -6,34 +6,47 @@
import com.azure.cosmos.util.CosmosPagedIterable;
import it.gov.pagopa.receipt.pdf.helpdesk.entity.receipt.Receipt;
import it.gov.pagopa.receipt.pdf.helpdesk.exception.ReceiptNotFoundException;
-import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
-import static org.mockito.ArgumentMatchers.*;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.mock;
import static org.mockito.Mockito.when;
import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables;
class ReceiptCosmosClientImplTest {
+ private static final String RECEIPT_ID = "a valid receipt id";
+
+ private CosmosClient mockClient;
+
+ private ReceiptCosmosClientImpl client;
+
+ @BeforeEach
+ void setUp() {
+ mockClient = mock(CosmosClient.class);
+
+ client = new ReceiptCosmosClientImpl(mockClient);
+ }
+
@Test
void testSingletonConnectionError() throws Exception {
String mockKey = "mockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeymockKeyMK==";
withEnvironmentVariables(
"COSMOS_RECEIPT_KEY", mockKey,
"COSMOS_RECEIPT_SERVICE_ENDPOINT", ""
- ).execute(() -> Assertions.assertThrows(IllegalArgumentException.class, ReceiptCosmosClientImpl::getInstance)
- );
+ ).execute(() -> assertThrows(IllegalArgumentException.class, ReceiptCosmosClientImpl::getInstance));
}
@Test
- void runOk() throws ReceiptNotFoundException {
- String RECEIPT_ID = "a valid receipt id";
-
- CosmosClient mockClient = mock(CosmosClient.class);
-
+ void runOk() {
CosmosDatabase mockDatabase = mock(CosmosDatabase.class);
CosmosContainer mockContainer = mock(CosmosContainer.class);
@@ -54,18 +67,13 @@ void runOk() throws ReceiptNotFoundException {
when(mockDatabase.getContainer(any())).thenReturn(mockContainer);
when(mockClient.getDatabase(any())).thenReturn(mockDatabase);
- ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient);
-
- Assertions.assertDoesNotThrow(() -> client.getReceiptDocument(RECEIPT_ID));
+ Receipt receiptResponse = assertDoesNotThrow(() -> client.getReceiptDocument(RECEIPT_ID));
- Receipt receiptResponse = client.getReceiptDocument(RECEIPT_ID);
- Assertions.assertEquals(RECEIPT_ID, receiptResponse.getId());
+ assertEquals(RECEIPT_ID, receiptResponse.getId());
}
@Test
void runKo() {
- CosmosClient mockClient = mock(CosmosClient.class);
-
CosmosDatabase mockDatabase = mock(CosmosDatabase.class);
CosmosContainer mockContainer = mock(CosmosContainer.class);
@@ -83,17 +91,11 @@ void runKo() {
when(mockDatabase.getContainer(any())).thenReturn(mockContainer);
when(mockClient.getDatabase(any())).thenReturn(mockDatabase);
- ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient);
-
- Assertions.assertThrows(ReceiptNotFoundException.class, () -> client.getReceiptDocument("an invalid receipt id"));
+ assertThrows(ReceiptNotFoundException.class, () -> client.getReceiptDocument("an invalid receipt id"));
}
@Test
-void runOk_FailedQueryClient() throws ReceiptNotFoundException {
- String RECEIPT_ID = "a valid receipt id";
-
- CosmosClient mockClient = mock(CosmosClient.class);
-
+ void runOk_FailedQueryClient() {
CosmosDatabase mockDatabase = mock(CosmosDatabase.class);
CosmosContainer mockContainer = mock(CosmosContainer.class);
@@ -114,10 +116,73 @@ void runOk_FailedQueryClient() throws ReceiptNotFoundException {
when(mockDatabase.getContainer(any())).thenReturn(mockContainer);
when(mockClient.getDatabase(any())).thenReturn(mockDatabase);
- ReceiptCosmosClientImpl client = new ReceiptCosmosClientImpl(mockClient);
+ assertDoesNotThrow(() -> client.getFailedReceiptDocuments(null, 100));
+ }
+
+ @Test
+ void getNotNotifiedReceiptDocumentsSuccess() {
+ CosmosDatabase mockDatabase = mock(CosmosDatabase.class);
+ CosmosContainer mockContainer = mock(CosmosContainer.class);
+
+ when(mockClient.getDatabase(any())).thenReturn(mockDatabase);
+ when(mockDatabase.getContainer(any())).thenReturn(mockContainer);
+
+ CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class);
+ when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable);
+
+ Iterator mockIterator = mock(Iterator.class);
+ when(mockIterable.iterator()).thenReturn(mockIterator);
+ when(mockIterator.hasNext()).thenReturn(true);
+
+ Receipt receipt = new Receipt();
+ receipt.setId(RECEIPT_ID);
- Assertions.assertDoesNotThrow(() -> client.getFailedReceiptDocuments(null, 100));
+ when(mockIterator.next()).thenReturn(receipt);
+ assertDoesNotThrow(() -> client.getNotNotifiedReceiptDocuments(
+ null,
+ 100,
+ true,
+ true));
+ assertDoesNotThrow(() -> client.getNotNotifiedReceiptDocuments(
+ null,
+ 100,
+ false,
+ true));
+ assertDoesNotThrow(() -> client.getNotNotifiedReceiptDocuments(
+ null,
+ 100,
+ true,
+ false));
}
+
+ @Test
+ void getNotNotifiedReceiptDocumentsFailThrowsIllegalArgumentException() {
+ CosmosDatabase mockDatabase = mock(CosmosDatabase.class);
+ CosmosContainer mockContainer = mock(CosmosContainer.class);
+
+ when(mockClient.getDatabase(any())).thenReturn(mockDatabase);
+ when(mockDatabase.getContainer(any())).thenReturn(mockContainer);
+
+ CosmosPagedIterable mockIterable = mock(CosmosPagedIterable.class);
+ when(mockContainer.queryItems(anyString(), any(), eq(Receipt.class))).thenReturn(mockIterable);
+
+ Iterator mockIterator = mock(Iterator.class);
+ when(mockIterable.iterator()).thenReturn(mockIterator);
+ when(mockIterator.hasNext()).thenReturn(true);
+
+ Receipt receipt = new Receipt();
+ receipt.setId(RECEIPT_ID);
+
+ when(mockIterator.next()).thenReturn(receipt);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> client.getNotNotifiedReceiptDocuments(
+ null,
+ 100,
+ false,
+ false));
+ }
}
\ No newline at end of file
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..7587362
--- /dev/null
+++ b/src/test/resources/logback-test.xml
@@ -0,0 +1,10 @@
+
+
+
+ %msg%n
+
+
+
+
+
+
\ No newline at end of file