diff --git a/docs/openapi/internal/externalchannel/gestore-repository-v1.yaml b/docs/openapi/internal/externalchannel/gestore-repository-v1.yaml index 8a3b2ad62..f972659d7 100644 --- a/docs/openapi/internal/externalchannel/gestore-repository-v1.yaml +++ b/docs/openapi/internal/externalchannel/gestore-repository-v1.yaml @@ -13,8 +13,29 @@ tags: description: Operazioni per la configurazione dei client - name: GestoreRequest description: Operazione per la gestione delle richieste + - name: DiscardedEvents + description: Operazioni per la gestione delle richieste scartate paths: + /external-channel/gestoreRepository/discarded-events: + post: + tags: + - DiscardedEvents + operationId: insertDiscardedEvents + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + items: + $ref : '#/components/schemas/DiscardedEventDto' + required: true + responses: + 200: + $ref: '#/components/responses/discardedEventResponseOk' + + /external-channel/gestoreRepository/clients/{xPagopaExtchCxId}: parameters: - name: xPagopaExtchCxId @@ -208,6 +229,15 @@ components: responses: + discardedEventResponseOk: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DiscardedEventDto' + clientResponseOk: description: OK content: @@ -365,6 +395,31 @@ components: come da specifiche di configurazione delle retry policy per la sola parte di interesse del canale example: '"SMS":[5,10,20,40],"EMAIL":[5,10,20,40],"PEC":[5,10,20,40],"PAPER":[5,10,20,40]' + DiscardedEventDto: + type: object + required: + - requestId + - timestampRicezione + - dataRicezione + - codiceScarto + - jsonRicevuto + - payloadHash + properties: + requestId: + type: string + timestampRicezione: + type: string + dataRicezione: + type: string + codiceScarto: + type: string + jsonRicevuto: + type: string + payloadHash: + type: string + details: + type: string + #Non usati internamente ma ref necessaria per la loro generazione. RequestConversionDto: $ref: './schemas/internal-schemas-paper-v1.yaml#/components/schemas/RequestConversionDto' diff --git a/docs/openapi/internal/externalchannel/schemas/internal-schemas-paper-v1.yaml b/docs/openapi/internal/externalchannel/schemas/internal-schemas-paper-v1.yaml index eec8d48f0..1c108eea6 100644 --- a/docs/openapi/internal/externalchannel/schemas/internal-schemas-paper-v1.yaml +++ b/docs/openapi/internal/externalchannel/schemas/internal-schemas-paper-v1.yaml @@ -197,7 +197,11 @@ components: additionalProperties: type: string description: >- - Servizi a valore aggiunto + Servizi a valore aggiunto + duplicateCheckPassthrough: + type: boolean + description: >- + Indica se il controllo sui duplicati deve essere bypassato per quella specifica richiesta PaperProgressStatusDto: title: Avanzamento di stato di una richiesta cartacea diff --git a/scripts/aws/cfn/microservice-dev-cfg.json b/scripts/aws/cfn/microservice-dev-cfg.json index a033d9d8d..3031f6d7a 100644 --- a/scripts/aws/cfn/microservice-dev-cfg.json +++ b/scripts/aws/cfn/microservice-dev-cfg.json @@ -43,6 +43,7 @@ "PnEcConsAllowedFutureOffsetDuration": "1m", "CpuValue": "1024", "MemoryAmount": "4GB", + "PnEcDuplicatesCheck": "|TEST | AR|890", "PnECPaperDocumentTypeToRaster": "PN_NOTIFICATION_ATTACHMENTS", "PnECPaperDocumentTypeForRasterized": "PN_PAPER_ATTACHMENT", "PnECPaperPAIdToRaster": "NOTHING", diff --git a/scripts/aws/cfn/microservice.yml b/scripts/aws/cfn/microservice.yml index 714c15081..ab1a5bf7a 100644 --- a/scripts/aws/cfn/microservice.yml +++ b/scripts/aws/cfn/microservice.yml @@ -199,6 +199,14 @@ Parameters: Type: String Description: 'ARN della Tabella DynamoDB di upporto per la tabella RichiesteConversione' + PnEcTableNameScartiConsolidatore: + Type: String + Description: 'Nome della Tabella DynamoDB relativa agli scarti consolidatore' + + PnEcTableArnScartiConsolidatore: + Type: String + Description: 'ARN della Tabella DynamoDB relativa agli scarti consolidatore' + ### QUEUES ### ### TRACKER : SMS ### @@ -694,7 +702,7 @@ Parameters: PnEcAlarmArnDLQueueTrackerErroriSERCQ: Type: String Description: "[Alarm DLQ - Tracker] ARN dell'allarme per presenza di messaggi nella DLQ PnEcQueueTrackerErroriSERCQ" - + ###### pn-core Event Bus ####### # PnEcQueueNameEventsDLQueuePnCoreTargetEventBus: @@ -909,6 +917,9 @@ Parameters: PnEcNamirialServerCacheEndpoint: Type: String + PnEcDuplicatesCheck: + Type: String + CpuValue: Type: Number Default: 1024 @@ -1029,18 +1040,21 @@ Resources: ContainerEnvEntry67: !Sub 'PnEcNamirialServerCache=${PnEcNamirialServerCache}' ContainerEnvEntry68: !Sub 'PnEcNamirialServerCacheEndpoint=${PnEcNamirialServerCacheEndpoint}' ContainerEnvEntry69: !Sub 'SpringCodecMaxInMemorySize=${SpringCodecMaxInMemorySize}' - ContainerEnvEntry70: !Sub 'PnEcConsAllowedFutureOffsetDuration=${PnEcConsAllowedFutureOffsetDuration}' - ContainerEnvEntry71: !Sub 'PnECPaperPAIdToRaster=${PnECPaperPAIdToRaster}' - ContainerEnvEntry72: !Sub 'PnECPaperDocumentTypeToRaster=${PnECPaperDocumentTypeToRaster}' - ContainerEnvEntry73: !Sub 'PnECPaperDocumentTypeForRasterized=${PnECPaperDocumentTypeForRasterized}' - ContainerEnvEntry74: !Sub 'PnECPaperPAIdOverride=${PnECPaperPAIdOverride}' - ContainerEnvEntry75: !Sub 'PnEcQueueNameAvailabilityManager=${PnEcQueueNameAvailabilityManager}' - ContainerEnvEntry76: !Sub 'PnEcDLQueueNameErroriCARTACEO=${PnEcDLQueueNameErroriCARTACEO}' - ContainerEnvEntry77: !Sub 'PnEcTableNameRichiesteConversione=${PnEcTableNameRichiesteConversione}' - ContainerEnvEntry78: !Sub 'PnEcTableNameConversionePdf=${PnEcTableNameConversionePdf}' - ContainerEnvEntry79: !Sub 'PnEcQueueNameTrackerStatoSERCQ=${PnEcQueueNameTrackerStatoSERCQ}' - ContainerEnvEntry80: !Sub 'PnEcQueueNameTrackerErroriSERCQ=${PnEcQueueNameTrackerErroriSERCQ}' - ContainerEnvEntry81: !Sub 'SQSMaxBatchSubscribedMsgs=${SQSMaxBatchSubscribedMsgs}' + ContainerEnvEntry70: !Sub 'PnEcDuplicatesCheck=${PnEcDuplicatesCheck}' + ContainerEnvEntry71: !Sub 'PnEcTableNameScartiConsolidatore=${PnEcTableNameScartiConsolidatore}' + ContainerEnvEntry72: !Sub 'PnEcConsAllowedFutureOffsetDuration=${PnEcConsAllowedFutureOffsetDuration}' + ContainerEnvEntry73: !Sub 'PnEcConsAllowedFutureOffsetDuration=${PnEcConsAllowedFutureOffsetDuration}' + ContainerEnvEntry74: !Sub 'PnECPaperPAIdToRaster=${PnECPaperPAIdToRaster}' + ContainerEnvEntry75: !Sub 'PnECPaperDocumentTypeToRaster=${PnECPaperDocumentTypeToRaster}' + ContainerEnvEntry76: !Sub 'PnECPaperDocumentTypeForRasterized=${PnECPaperDocumentTypeForRasterized}' + ContainerEnvEntry77: !Sub 'PnECPaperPAIdOverride=${PnECPaperPAIdOverride}' + ContainerEnvEntry78: !Sub 'PnEcQueueNameAvailabilityManager=${PnEcQueueNameAvailabilityManager}' + ContainerEnvEntry79: !Sub 'PnEcDLQueueNameErroriCARTACEO=${PnEcDLQueueNameErroriCARTACEO}' + ContainerEnvEntry80: !Sub 'PnEcTableNameRichiesteConversione=${PnEcTableNameRichiesteConversione}' + ContainerEnvEntry81: !Sub 'PnEcTableNameConversionePdf=${PnEcTableNameConversionePdf}' + ContainerEnvEntry82: !Sub 'PnEcQueueNameTrackerStatoSERCQ=${PnEcQueueNameTrackerStatoSERCQ}' + ContainerEnvEntry83: !Sub 'PnEcQueueNameTrackerErroriSERCQ=${PnEcQueueNameTrackerErroriSERCQ}' + ContainerEnvEntry84: !Sub 'SQSMaxBatchSubscribedMsgs=${SQSMaxBatchSubscribedMsgs}' ContainerSecret1: !Sub 'ConsolidatoreClientApiKey=arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:pn-EC-Secrets:ConsolidatoreClientApiKey:AWSCURRENT:' MicroServiceSecretPrefix: pn-EC-Secrets MappedPaths: '/external-channels/*,/external-channel/*,/consolidatore-ingress/*' @@ -1084,6 +1098,7 @@ Resources: - !Ref PnEcTableArnAnagrafica - !Ref PnEcTableArnRichieste - !Ref PnEcTableArnRichiesteMetadati + - !Ref PnEcTableArnScartiConsolidatore - !Ref PnEcTableArnRichiesteConversione - !Ref PnEcTableArnConversionePdf - Effect: Allow @@ -1262,7 +1277,7 @@ Resources: Resource: !Ref PnEcQueueArnTrackerStatoCARTACEO Principal: AWS: !Ref AWS::AccountId - + PnEcQueuePolicyTrackerStatoSERCQ: Type: AWS::SQS::QueuePolicy Properties: @@ -1456,6 +1471,7 @@ Resources: - - !Ref PnEcTableNameAnagrafica - !Ref PnEcTableNameRichieste - !Ref PnEcTableNameRichiesteMetadati + - !Ref PnEcTableNameScartiConsolidatore QueueArns: !Join - ',' @@ -1502,13 +1518,14 @@ Resources: - !Ref PnEcAlarmArnDLQueueTrackerStatoSERCQ - !Ref PnEcAlarmArnQueueTrackerErroriSERCQ - !Ref PnEcAlarmArnDLQueueTrackerErroriSERCQ - - !Ref PnEcAlarmArnEventsDLQueuePnCoreTargetEventBus + - !Ref PnEcAlarmArnEventsDLQueuePnCoreTargetEventBus DynamoDBTableNames: !Join - ',' - - !Ref PnEcTableNameAnagrafica - !Ref PnEcTableNameRichieste - !Ref PnEcTableNameRichiesteMetadati + - !Ref PnEcTableNameScartiConsolidatore QueueArns: !Join - ',' diff --git a/scripts/aws/cfn/storage.yml b/scripts/aws/cfn/storage.yml index c114455ed..51c3a0287 100644 --- a/scripts/aws/cfn/storage.yml +++ b/scripts/aws/cfn/storage.yml @@ -133,7 +133,7 @@ Resources: PointInTimeRecoveryEnabled: true DeletionPolicy: Retain UpdateReplacePolicy: Retain - + PnEcTableRichiesteConversione: Type: AWS::DynamoDB::Table Properties: @@ -154,6 +154,31 @@ Resources: DeletionPolicy: Retain UpdateReplacePolicy: Retain + PnEcTableScartiConsolidatore: + Type: AWS::DynamoDB::Table + Properties: + TableName: 'pn-EcScartiConsolidatore' + AttributeDefinitions: + - AttributeName: 'requestId' + AttributeType: 'S' + - AttributeName: 'timestampRicezione' + AttributeType: 'S' + KeySchema: + - AttributeName: 'requestId' + KeyType: 'HASH' + - AttributeName: 'timestampRicezione' + KeyType: 'RANGE' + BillingMode: 'PAY_PER_REQUEST' + SSESpecification: + SSEEnabled: true + SSEType: 'KMS' + KMSMasterKeyId: !Ref PCKmsEncDecDynamoDataKey + TableClass: 'STANDARD' + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: true + DeletionPolicy: Retain + UpdateReplacePolicy: Retain + PnEcTableConversionePdf: Type: AWS::DynamoDB::Table Properties: @@ -173,7 +198,7 @@ Resources: PointInTimeRecoveryEnabled: true DeletionPolicy: Retain UpdateReplacePolicy: Retain - + ######### S3 BUCKET FOR OVERSIZE SQS MESSAGES ######### PnEcSqsMessagesStagingBucket: @@ -1063,6 +1088,14 @@ Outputs: Description: 'ARN della Tabella DynamoDB relativa alle richieste' Value: !GetAtt PnEcTableRichiesteMetadati.Arn + PnEcTableNameScartiConsolidatore: + Description: 'Nome della Tabella DynamoDB relativa agli scarti consolidatore' + Value: !Ref PnEcTableScartiConsolidatore + + PnEcTableArnScartiConsolidatore: + Description: 'ARN della Tabella DynamoDB relativa agli scarti consolidatore' + Value: !GetAtt PnEcTableScartiConsolidatore.Arn + PnEcTableNameRichiesteConversione: Description: 'Nome della Tabella DynamoDB relativa alle richieste di conversione dei PDF' Value: !Ref PnEcTableRichiesteConversione @@ -1299,7 +1332,7 @@ Outputs: PnEcAlarmArnDLQueueTrackerErroriSERCQ: Description: "[Alarm DLQ - Tracker] ARN dell'allarme per presenza di messaggi nella DLQ PnEcQueueTrackerErroriSERCQ" Value: !GetAtt PnEcQueueTrackerErroriSERCQStack.Outputs.SqsDLQAlarmArn - + ###### BATCH : SMS ####### PnEcQueueNameBatchSMS: diff --git a/src/main/java/it/pagopa/pn/ec/commons/configurationproperties/endpoint/internal/ec/GestoreRepositoryEndpointProperties.java b/src/main/java/it/pagopa/pn/ec/commons/configurationproperties/endpoint/internal/ec/GestoreRepositoryEndpointProperties.java index 49ec25323..2dd559779 100644 --- a/src/main/java/it/pagopa/pn/ec/commons/configurationproperties/endpoint/internal/ec/GestoreRepositoryEndpointProperties.java +++ b/src/main/java/it/pagopa/pn/ec/commons/configurationproperties/endpoint/internal/ec/GestoreRepositoryEndpointProperties.java @@ -9,6 +9,6 @@ public record GestoreRepositoryEndpointProperties( String getClientConfiguration, String postClientConfiguration, String putClientConfiguration, String deleteClientConfiguration, // <-- REQUEST --> - String getRequest, String postRequest, String patchRequest, String deleteRequest, String getRequestByMessageId, + String getRequest, String postRequest, String patchRequest, String deleteRequest, String getRequestByMessageId, String postDiscardedEvents, String setMessageIdInRequestMetadata) {} diff --git a/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCall.java b/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCall.java index 952f7c5da..d3518e9ea 100644 --- a/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCall.java +++ b/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCall.java @@ -2,6 +2,7 @@ import it.pagopa.pn.ec.commons.rest.call.RestCallException; import it.pagopa.pn.ec.rest.v1.dto.*; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @SuppressWarnings("unused") @@ -22,4 +23,9 @@ public interface GestoreRepositoryCall { Mono deleteRichiesta(String clientId, String requestIdx); Mono getRequestByMessageId(String messageId); Mono setMessageIdInRequestMetadata(String clientId, String requestIdx); + +// <-- DISCARDED EVENTS --> + Flux insertDiscardedEvents(Flux discardedEventsDto); + + } diff --git a/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCallImpl.java b/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCallImpl.java index f9638f821..46f4b71e5 100644 --- a/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCallImpl.java +++ b/src/main/java/it/pagopa/pn/ec/commons/rest/call/ec/gestorerepository/GestoreRepositoryCallImpl.java @@ -7,6 +7,7 @@ import lombok.CustomLog; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static it.pagopa.pn.ec.commons.utils.LogUtils.*; @@ -149,6 +150,18 @@ public Mono setMessageIdInRequestMetadata(String clientId, String re .bodyToMono(RequestDto.class); } + @Override + public Flux insertDiscardedEvents(Flux discardedEventsDto) { + return ecWebClient.post() + .uri(gestoreRepositoryEndpointProperties.postDiscardedEvents()) + .body(discardedEventsDto, DiscardedEventDto.class) + .retrieve() + .onStatus(BAD_REQUEST::equals, + clientResponse -> Mono.error(new RepositoryManagerException.RequestMalformedException())) + .bodyToFlux(DiscardedEventDto.class); + + } + private static class ISEForMessageIdCreationException extends RestCallException { public ISEForMessageIdCreationException() { diff --git a/src/main/java/it/pagopa/pn/ec/commons/utils/CompareUtils.java b/src/main/java/it/pagopa/pn/ec/commons/utils/CompareUtils.java index 5297c8d87..76ae711f8 100644 --- a/src/main/java/it/pagopa/pn/ec/commons/utils/CompareUtils.java +++ b/src/main/java/it/pagopa/pn/ec/commons/utils/CompareUtils.java @@ -1,10 +1,9 @@ package it.pagopa.pn.ec.commons.utils; -import it.pagopa.pn.ec.rest.v1.dto.DigitalProgressStatusDto; -import it.pagopa.pn.ec.rest.v1.dto.EventsDto; -import it.pagopa.pn.ec.rest.v1.dto.PaperProgressStatusDto; +import it.pagopa.pn.ec.rest.v1.dto.*; import java.util.List; +import java.util.Objects; import static java.time.temporal.ChronoUnit.SECONDS; @@ -25,4 +24,71 @@ public static boolean isSameEvent(PaperProgressStatusDto lastEvent, PaperProgres return lastEvent.getStatus().equals(nextStatus) && lastEvent.getStatusDateTime().equals(newEvent.getStatusDateTime().truncatedTo(SECONDS)); } + public static boolean isSameEvent(PaperProgressStatusDto paperProgressStatusEvent, ConsolidatoreIngressPaperProgressStatusEvent consolidatoreIngressPaperProgressStatusEvent) { + return Objects.equals(paperProgressStatusEvent.getRegisteredLetterCode(),consolidatoreIngressPaperProgressStatusEvent.getRegisteredLetterCode()) + && Objects.equals(paperProgressStatusEvent.getProductType(), consolidatoreIngressPaperProgressStatusEvent.getProductType()) + && Objects.equals(paperProgressStatusEvent.getIun(), consolidatoreIngressPaperProgressStatusEvent.getIun()) + && Objects.equals(paperProgressStatusEvent.getStatusCode(), consolidatoreIngressPaperProgressStatusEvent.getStatusCode()) + && Objects.equals(paperProgressStatusEvent.getStatusDescription(), consolidatoreIngressPaperProgressStatusEvent.getStatusDescription()) + && paperProgressStatusEvent.getStatusDateTime().truncatedTo(SECONDS).isEqual(consolidatoreIngressPaperProgressStatusEvent.getStatusDateTime().truncatedTo(SECONDS)) + && Objects.equals(paperProgressStatusEvent.getDeliveryFailureCause(), consolidatoreIngressPaperProgressStatusEvent.getDeliveryFailureCause()) + && isSameAttachments(paperProgressStatusEvent.getAttachments(), consolidatoreIngressPaperProgressStatusEvent.getAttachments()) + && isSameAddress(paperProgressStatusEvent.getDiscoveredAddress(), consolidatoreIngressPaperProgressStatusEvent.getDiscoveredAddress()); + + } + + private static boolean isSameAttachments(List paperProgressAttachmentList, List consolidatoreIngressPaperProgressStatusEventAttachmentsList) { + + if(paperProgressAttachmentList == null) { + paperProgressAttachmentList = List.of(); + } + + if(consolidatoreIngressPaperProgressStatusEventAttachmentsList == null) { + consolidatoreIngressPaperProgressStatusEventAttachmentsList = List.of(); + } + + if(consolidatoreIngressPaperProgressStatusEventAttachmentsList.isEmpty() && paperProgressAttachmentList.isEmpty()) { + return true; + } + + if (paperProgressAttachmentList.isEmpty() || consolidatoreIngressPaperProgressStatusEventAttachmentsList.isEmpty()) { + return false; + } + + if (paperProgressAttachmentList.size() != consolidatoreIngressPaperProgressStatusEventAttachmentsList.size()){ + return false; + } else { + + for (int i = 0; i errors) { @@ -75,9 +84,9 @@ public Mono> getFile(String fileKey, String return MDCUtils.addMDCToContextAndExecute(consolidatoreServiceImpl.getFile(fileKey, xPagopaExtchServiceId, xApiKey) .doOnSuccess(result -> log.logEndingProcess(GET_FILE)) .doOnError(throwable -> log.logEndingProcess(GET_FILE, false, throwable.getMessage())) - .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute("requestBody"))) - .doOnError(SemanticException.class, e -> log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute("requestBody")).errorList(e.getAuditLogErrorList()))) - .doOnError(SyntaxException.class, e -> log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute("requestBody")).errorList(e.getAuditLogErrorList()))) + .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute(REQUEST_BODY))) + .doOnError(SemanticException.class, e -> log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute(REQUEST_BODY)).errorList(e.getAuditLogErrorList()))) + .doOnError(SyntaxException.class, e -> log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute(REQUEST_BODY)).errorList(e.getAuditLogErrorList()))) .map(ResponseEntity::ok)); } @@ -89,9 +98,9 @@ public Mono> presignedUploadRequest(String x return consolidatoreServiceImpl.presignedUploadRequest(xPagopaExtchServiceId, xApiKey, preLoadRequestData) .doOnSuccess(result -> log.logEndingProcess(PRESIGNED_UPLOAD_REQUEST_PROCESS)) .doOnError(throwable -> log.logEndingProcess(PRESIGNED_UPLOAD_REQUEST_PROCESS, false, throwable.getMessage())) - .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute("requestBody"))) - .doOnError(SemanticException.class, e -> log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute("requestBody")).errorList(e.getAuditLogErrorList()))) - .doOnError(SyntaxException.class, e -> log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute("requestBody")).errorList(e.getAuditLogErrorList()))) + .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute(REQUEST_BODY))) + .doOnError(SemanticException.class, e -> log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute(REQUEST_BODY)).errorList(e.getAuditLogErrorList()))) + .doOnError(SyntaxException.class, e -> log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute(REQUEST_BODY)).errorList(e.getAuditLogErrorList()))) .map(ResponseEntity::ok); } @@ -102,6 +111,9 @@ public Mono> sendPaperProgressStatus final ServerWebExchange exchange) { MDC.clear(); log.logStartingProcess(SEND_PAPER_PROGRESS_STATUS_REQUEST); + OffsetDateTime now = OffsetDateTime.now(); + String timestampRicezione = now.format(TIMESTAMP_RICEZIONE_FORMATTER); + String dataRicezione = now.format(DATA_RICEZIONE_FORMATTER); return authService.clientAuth(xPagopaExtchServiceId) .flatMap(clientConfiguration -> { log.logChecking(X_API_KEY_VALIDATION); @@ -145,34 +157,54 @@ public Mono> sendPaperProgressStatus // errori var listErrors = new ArrayList(); var consAuditLogErrorList = new ArrayList(); + var discardedEventsDtoList = new ArrayList(); + String jsonRequestBody = exchange.getAttribute(REQUEST_BODY); + String jsonRequestBodyHash = generateSha256(jsonRequestBody.getBytes(StandardCharsets.UTF_8)); listErrorResponse.forEach(dto -> { if (dto.getConsAuditLogErrorList() != null) { consAuditLogErrorList.addAll(dto.getConsAuditLogErrorList()); } if (dto.getOperationResultCodeResponse() != null) { + String requestId= dto.getPaperProgressStatusEvent().getRequestId(); + dto.getOperationResultCodeResponse().getErrorList().forEach(error -> { + DiscardedEventDto discardedEventDto = new DiscardedEventDto(); + discardedEventDto.setDataRicezione(dataRicezione); + discardedEventDto.setJsonRicevuto(jsonRequestBody); + discardedEventDto.setPayloadHash(jsonRequestBodyHash); + discardedEventDto.setCodiceScarto(error); + discardedEventDto.setTimestampRicezione(timestampRicezione); + discardedEventDto.setRequestId(requestId); + discardedEventsDtoList.add(discardedEventDto); + }); listErrors.add(dto.getOperationResultCodeResponse()); } }); - log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute("requestBody")).errorList(consAuditLogErrorList)); + log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(exchange.getAttribute(REQUEST_BODY)).errorList(consAuditLogErrorList)); var errors = getAllErrors(listErrors); log.debug(SEND_PAPER_PROGRESS_STATUS_REQUEST + "syntax/semantic errors : result code = '{}' : result description = '{}' : specific errors identified = {}", listErrors.get(0).getResultCode(), listErrors.get(0).getResultDescription(), errors); - return Mono.just(ResponseEntity + + var response = ResponseEntity .badRequest() .body(getOperationResultCodeResponse(listErrors.get(0).getResultCode(), listErrors.get(0).getResultDescription(), - errors))); + errors)); + + if (!discardedEventsDtoList.isEmpty()) { + return ricezioneEsitiCartaceoService.insertDiscardedEvents(discardedEventsDtoList).then(Mono.just(response)); + } + return Mono.just(response); } }) .doOnSuccess(result -> log.logEndingProcess(SEND_PAPER_PROGRESS_STATUS_REQUEST)) .doOnError(throwable -> log.logEndingProcess(SEND_PAPER_PROGRESS_STATUS_REQUEST, false, throwable.getMessage())) - .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute("requestBody")))) + .doOnError(WebExchangeBindException.class, e -> fieldValidationAuditLog(e.getFieldErrors(), exchange.getAttribute(REQUEST_BODY)))) .onErrorResume(RuntimeException.class, throwable -> { String fatalMessage = throwable.getClass() == WebExchangeBindException.class ? "" : "* FATAL * "; log.error(SEND_PAPER_PROGRESS_STATUS_REQUEST + fatalMessage + "errore generico = {}, {}", throwable, throwable.getMessage()); @@ -190,7 +222,19 @@ private void fieldValidationAuditLog(List errors, Object request) { var consAuditLogError = new ConsAuditLogError().description(description).error(ERR_CONS_BAD_JSON_FORMAT.getValue()); consAuditLogErrorList.add(consAuditLogError); } - log.error("{} - {}", ERR_CONS, new ConsAuditLogEvent<>().request(request).errorList(consAuditLogErrorList)); + log.error(LOG_FORMAT, ERR_CONS, new ConsAuditLogEvent<>().request(request).errorList(consAuditLogErrorList)); + } + + private String generateSha256(byte[] fileBytes) { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA256"); + md.update(fileBytes); + byte[] digest = md.digest(); + return Base64.getEncoder().encodeToString(digest); + } catch (NoSuchAlgorithmException | NullPointerException e) { + throw new ShaGenerationException(e.getMessage()); + } } } diff --git a/src/main/java/it/pagopa/pn/ec/consolidatore/service/RicezioneEsitiCartaceoService.java b/src/main/java/it/pagopa/pn/ec/consolidatore/service/RicezioneEsitiCartaceoService.java index 7fe042fb8..92b8caee5 100644 --- a/src/main/java/it/pagopa/pn/ec/consolidatore/service/RicezioneEsitiCartaceoService.java +++ b/src/main/java/it/pagopa/pn/ec/consolidatore/service/RicezioneEsitiCartaceoService.java @@ -2,8 +2,10 @@ import it.pagopa.pn.ec.consolidatore.model.dto.RicezioneEsitiDto; import it.pagopa.pn.ec.rest.v1.dto.ConsolidatoreIngressPaperProgressStatusEvent; +import it.pagopa.pn.ec.rest.v1.dto.DiscardedEventDto; import it.pagopa.pn.ec.rest.v1.dto.OperationResultCodeResponse; import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; @@ -18,4 +20,6 @@ Mono pubblicaEsitoCodaNotificationTracker( Mono> publishOnQueue(List listEvents, String xPagopaExtchServiceId); -} + Flux insertDiscardedEvents(List discardedEvents); + + } diff --git a/src/main/java/it/pagopa/pn/ec/consolidatore/service/impl/RicezioneEsitiCartaceoServiceImpl.java b/src/main/java/it/pagopa/pn/ec/consolidatore/service/impl/RicezioneEsitiCartaceoServiceImpl.java index 705823b8f..c2262c402 100644 --- a/src/main/java/it/pagopa/pn/ec/consolidatore/service/impl/RicezioneEsitiCartaceoServiceImpl.java +++ b/src/main/java/it/pagopa/pn/ec/consolidatore/service/impl/RicezioneEsitiCartaceoServiceImpl.java @@ -36,6 +36,7 @@ import static it.pagopa.pn.ec.commons.constant.Status.BOOKED; import static it.pagopa.pn.ec.commons.constant.Status.SENT; +import static it.pagopa.pn.ec.commons.utils.CompareUtils.isSameEvent; import static it.pagopa.pn.ec.commons.utils.LogUtils.*; import static it.pagopa.pn.ec.consolidatore.constant.ConsAuditLogEventType.*; import static it.pagopa.pn.ec.consolidatore.utils.PaperConstant.*; @@ -54,6 +55,7 @@ public class RicezioneEsitiCartaceoServiceImpl implements RicezioneEsitiCartaceo private final StatusCodesToDeliveryFailureCauses statusCodesToDeliveryFailureCauses; private final StatusPullService statusPullService; private boolean considerEventsWithoutSentStatusAsBooked; + private final String duplicatesCheck; private final Duration offsetDuration; @@ -61,6 +63,7 @@ public RicezioneEsitiCartaceoServiceImpl(GestoreRepositoryCall gestoreRepository FileCall fileCall, ObjectMapper objectMapper, NotificationTrackerSqsName notificationTrackerSqsName, SqsService sqsService, StatusCodesToDeliveryFailureCauses statusCodesToDeliveryFailureCauses, StatusPullService statusPullService, @Value("${ricezione-esiti-cartaceo.consider-event-without-sent-status-as-booked}") boolean considerEventsWithoutStatusAsBooked, + @Value("${ricezione-esiti-cartaceo.duplicates-check}") String duplicatesCheck, @Value("${ricezione-esiti-cartaceo.allowed-future-offset-duration}") Duration offsetDuration) { super(); this.gestoreRepositoryCall = gestoreRepositoryCall; @@ -71,8 +74,9 @@ public RicezioneEsitiCartaceoServiceImpl(GestoreRepositoryCall gestoreRepository this.statusCodesToDeliveryFailureCauses = statusCodesToDeliveryFailureCauses; this.statusPullService = statusPullService; this.considerEventsWithoutSentStatusAsBooked = considerEventsWithoutStatusAsBooked; + this.duplicatesCheck = duplicatesCheck; if (offsetDuration== null){ - this.offsetDuration = Duration.ofSeconds(-1); + this.offsetDuration = Duration.ofSeconds(-1); } else { this.offsetDuration = offsetDuration; } @@ -280,6 +284,25 @@ private Mono verificaAttachments( .doOnSuccess(result -> log.info(SUCCESSFUL_OPERATION_ON_LABEL, requestId, VERIFICA_ATTACHMENTS, result)); } + public Mono verificaDuplicati(RequestDto requestDto, ConsolidatoreIngressPaperProgressStatusEvent progressStatusEvent) { + log.debug(INVOKING_OPERATION_LABEL_WITH_ARGS, VERIFICA_DUPLICATI, progressStatusEvent); + return Mono.defer(() -> { + Boolean passthrough = requestDto.getRequestMetadata().getPaperRequestMetadata().getDuplicateCheckPassthrough(); + if ((passthrough == null || !passthrough) && duplicatesCheck.contains(progressStatusEvent.getProductType())) { + log.debug(VERIFICA_DUPLICATI + ": checking {} for duplicates against events {}", progressStatusEvent,requestDto.getRequestMetadata().getEventsList()); + return Flux.fromIterable(requestDto.getRequestMetadata().getEventsList()).any(event -> isSameEvent(event.getPaperProgrStatus(), progressStatusEvent)); + } + return Mono.just(false); + }).handle((isDuplicated, sink)-> { + if (Boolean.FALSE.equals(isDuplicated)) { + sink.next(requestDto); + } else { + sink.error(new RicezioneEsitiCartaceoException("400.02", errorCodeDescriptionMap().get(SEMANTIC_ERROR_CODE), List.of(DUPLICATED_EVENT), + List.of(new ConsAuditLogError(ERR_CONS_DUPLICATED_EVENT.getValue(), "The request has duplicated events", requestDto.getRequestIdx())))); + } + }); + } + @Override public Mono verificaEsitoDaConsolidatore( String xPagopaExtchServiceId, ConsolidatoreIngressPaperProgressStatusEvent progressStatusEvent) @@ -288,6 +311,7 @@ public Mono verificaEsitoDaConsolidatore( var requestId = progressStatusEvent.getRequestId(); return Mono.just(progressStatusEvent) .flatMap(unused -> gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceId, progressStatusEvent.getRequestId())) + .flatMap(requestDto -> verificaDuplicati(requestDto, progressStatusEvent)) .flatMap(requestDto -> verificaErroriSemantici(progressStatusEvent, requestDto, xPagopaExtchServiceId)) .flatMap(unused -> verificaAttachments(xPagopaExtchServiceId, requestId, progressStatusEvent.getAttachments())) .flatMap(unused -> Mono.just(new RicezioneEsitiDto(progressStatusEvent, @@ -434,4 +458,8 @@ public static List getAllErrors(List respon return errors; } + public Flux insertDiscardedEvents(List discardedEvents) { + return gestoreRepositoryCall.insertDiscardedEvents(Flux.fromIterable(discardedEvents)); + } + } diff --git a/src/main/java/it/pagopa/pn/ec/consolidatore/utils/PaperConstant.java b/src/main/java/it/pagopa/pn/ec/consolidatore/utils/PaperConstant.java index a81e75f21..2d7fe9852 100644 --- a/src/main/java/it/pagopa/pn/ec/consolidatore/utils/PaperConstant.java +++ b/src/main/java/it/pagopa/pn/ec/consolidatore/utils/PaperConstant.java @@ -24,4 +24,5 @@ private PaperConstant(){ public static final String CLIENT_REQUEST_TIMESTAMP_LABEL = "clientRequestTimeStamp"; + public static final String DUPLICATED_EVENT = "DUPLICATED_EVENT"; } diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/configurationproperties/RepositoryManagerDynamoTableName.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/configurationproperties/RepositoryManagerDynamoTableName.java index ec4357d8d..8888a84ac 100644 --- a/src/main/java/it/pagopa/pn/ec/repositorymanager/configurationproperties/RepositoryManagerDynamoTableName.java +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/configurationproperties/RepositoryManagerDynamoTableName.java @@ -4,7 +4,7 @@ @ConfigurationProperties(prefix = "dynamo.table.repository-manager") public record RepositoryManagerDynamoTableName(String anagraficaClientName, + String richiesteMetadataName, String scartiConsolidatoreName, String richiestePersonalName, - String richiesteMetadataName, String richiesteConversioneRequestName, String richiesteConversionePdfName) {} diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/DiscardedEvent.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/DiscardedEvent.java new file mode 100644 index 000000000..e2ccd343c --- /dev/null +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/DiscardedEvent.java @@ -0,0 +1,25 @@ +package it.pagopa.pn.ec.repositorymanager.model.entity; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + + +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor +@Data +@DynamoDbBean +@EqualsAndHashCode(callSuper = false) +public class DiscardedEvent { + @Getter(onMethod = @__({@DynamoDbPartitionKey})) + String requestId; + @Getter(onMethod = @__({@DynamoDbSortKey})) + String timestampRicezione; + String dataRicezione; + String codiceScarto; + String jsonRicevuto; + String payloadHash; + String details; +} diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/PaperRequestMetadata.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/PaperRequestMetadata.java index aae55ffee..59631c281 100644 --- a/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/PaperRequestMetadata.java +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/model/entity/PaperRequestMetadata.java @@ -19,4 +19,5 @@ public class PaperRequestMetadata { String productType; String printType; Map vas; + Boolean duplicateCheckPassthrough; } diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsController.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsController.java new file mode 100644 index 000000000..0526f32d2 --- /dev/null +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsController.java @@ -0,0 +1,43 @@ +package it.pagopa.pn.ec.repositorymanager.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.pn.ec.repositorymanager.model.entity.DiscardedEvent; +import it.pagopa.pn.ec.repositorymanager.service.DiscardedEventsService; +import it.pagopa.pn.ec.rest.v1.api.DiscardedEventsApi; +import it.pagopa.pn.ec.rest.v1.dto.DiscardedEventDto; +import lombok.CustomLog; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +@RestController +@CustomLog +public class DiscardedEventsController implements DiscardedEventsApi { + + private final DiscardedEventsService discardedEventsService; + private final ObjectMapper objectMapper; + + + public DiscardedEventsController(DiscardedEventsService discardedEventsService, ObjectMapper objectMapper) { + this.discardedEventsService = discardedEventsService; + this.objectMapper = objectMapper; + } + + @Override + public Mono>> insertDiscardedEvents(Flux discardedEventsList, ServerWebExchange exchange) { + Flux discardedEventDtoFlux = discardedEventsList.map(discardedEventDto -> { + discardedEventDto.setTimestampRicezione(discardedEventDto.getTimestampRicezione() + "~" + UUID.randomUUID()); + return discardedEventDto; + }) + .map(discardedEventDto -> objectMapper.convertValue(discardedEventDto, DiscardedEvent.class)) + .flatMap(discardedEventsService::insertDiscardedEvent) + .map(discardedEvent -> objectMapper.convertValue(discardedEvent, DiscardedEventDto.class)); + + return Mono.just(ResponseEntity.ok().body(discardedEventDtoFlux)); + + } +} diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/service/DiscardedEventsService.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/service/DiscardedEventsService.java new file mode 100644 index 000000000..9a82ac955 --- /dev/null +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/service/DiscardedEventsService.java @@ -0,0 +1,10 @@ +package it.pagopa.pn.ec.repositorymanager.service; + +import it.pagopa.pn.ec.repositorymanager.model.entity.DiscardedEvent; +import reactor.core.publisher.Mono; + +public interface DiscardedEventsService { + + Mono insertDiscardedEvent(DiscardedEvent discardedEvent); + +} diff --git a/src/main/java/it/pagopa/pn/ec/repositorymanager/service/impl/DiscardedEventsServiceImpl.java b/src/main/java/it/pagopa/pn/ec/repositorymanager/service/impl/DiscardedEventsServiceImpl.java new file mode 100644 index 000000000..7d7ebff2b --- /dev/null +++ b/src/main/java/it/pagopa/pn/ec/repositorymanager/service/impl/DiscardedEventsServiceImpl.java @@ -0,0 +1,35 @@ +package it.pagopa.pn.ec.repositorymanager.service.impl; + +import it.pagopa.pn.commons.utils.dynamodb.async.DynamoDbAsyncTableDecorator; +import it.pagopa.pn.ec.repositorymanager.configurationproperties.RepositoryManagerDynamoTableName; +import it.pagopa.pn.ec.repositorymanager.model.entity.DiscardedEvent; +import it.pagopa.pn.ec.repositorymanager.service.DiscardedEventsService; +import lombok.CustomLog; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; + +import static it.pagopa.pn.ec.commons.utils.LogUtils.*; +import static it.pagopa.pn.ec.commons.utils.LogUtils.GET_REQUEST_OP; + +@Service +@CustomLog +public class DiscardedEventsServiceImpl implements DiscardedEventsService { + + + private final DynamoDbAsyncTableDecorator discardedEventDynamoDbAsyncTable; + + + public DiscardedEventsServiceImpl(DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient, RepositoryManagerDynamoTableName repositoryManagerDynamoTableName) { + this.discardedEventDynamoDbAsyncTable = new DynamoDbAsyncTableDecorator<>(dynamoDbEnhancedAsyncClient.table(repositoryManagerDynamoTableName.scartiConsolidatoreName(), TableSchema.fromBean(DiscardedEvent.class))); + } + + @Override + public Mono insertDiscardedEvent(DiscardedEvent discardedEvent) { + return Mono.fromCompletionStage(() -> discardedEventDynamoDbAsyncTable.putItem(builder -> builder.item(discardedEvent))) + .thenReturn(discardedEvent) + .doOnError(throwable -> log.debug(EXCEPTION_IN_PROCESS, GET_REQUEST_METADATA_OP, throwable, throwable.getMessage())) + .doOnSuccess(result -> log.info(SUCCESSFUL_OPERATION_ON_LABEL, discardedEvent.getRequestId(), GET_REQUEST_OP, result)); + } +} diff --git a/src/main/resources/cartaceo/lavorazione-cartaceo.properties b/src/main/resources/cartaceo/lavorazione-cartaceo.properties index 180997178..acfe7581f 100644 --- a/src/main/resources/cartaceo/lavorazione-cartaceo.properties +++ b/src/main/resources/cartaceo/lavorazione-cartaceo.properties @@ -1,6 +1,7 @@ lavorazione-cartaceo.max-thread-pool-size=${LavorazioneCartaceoMaxThreadPoolSize:50} pn.ec.esiti-cartaceo.parameter.name=pn-EC-esitiCartaceo ricezione-esiti-cartaceo.consider-event-without-sent-status-as-booked=${PnEcRicezioneEsitiCartaceoConsiderEventWithoutSentStatusAsBooked} +ricezione-esiti-cartaceo.duplicates-check=${PnEcDuplicatesCheck} ricezione-esiti-cartaceo.allowed-future-offset-duration=${PnEcConsAllowedFutureOffsetDuration} lavorazione-cartaceo.max-retry-attempts=3 lavorazione-cartaceo.min-retry-backoff=2 diff --git a/src/main/resources/commons/internal-endpoint.properties b/src/main/resources/commons/internal-endpoint.properties index 00058f9e2..847640714 100644 --- a/src/main/resources/commons/internal-endpoint.properties +++ b/src/main/resources/commons/internal-endpoint.properties @@ -25,7 +25,7 @@ internal-endpoint.ec.gestore-repository.patch-request=${internal-endpoint.ec.ges internal-endpoint.ec.gestore-repository.delete-request=${internal-endpoint.ec.gestore-repository.base-path}/requests/{requestIdx} internal-endpoint.ec.gestore-repository.get-request-by-messageId=${internal-endpoint.ec.gestore-repository.base-path}/requests/messageId/{messageId} internal-endpoint.ec.gestore-repository.set-messageId-in-request-metadata=${internal-endpoint.ec.gestore-repository.base-path}/requests/messageId/{requestIdx} - +internal-endpoint.ec.gestore-repository.post-discarded-events=${internal-endpoint.ec.gestore-repository.base-path}/discarded-events ########################## ### Safe Storage ### @@ -113,4 +113,6 @@ internal-endpoint.pdfraster.base-url=${PdfRasterBaseUrl:http://localhost:8083} internal-endpoint.pdfraster.convert-pdf=${internal-endpoint.consolidatore.base-path}/convert-pdf internal-endpoint.pdfraster.client-header-value=${PdfRasterClientId} -internal-endpoint.pdfraster.api-key-header-value=${PdfRasterClientApiKey} \ No newline at end of file +internal-endpoint.pdfraster.api-key-header-value=${PdfRasterClientApiKey} +### DISCARDED EVENTS +internal-endpoint.consolidatore.discarded-events.insert-event=${internal-endpoint.consolidatore.base-path}/discarded-events \ No newline at end of file diff --git a/src/main/resources/repositorymanager/repository-manager-dynamo-table.properties b/src/main/resources/repositorymanager/repository-manager-dynamo-table.properties index 481adb10c..10f9faaf4 100644 --- a/src/main/resources/repositorymanager/repository-manager-dynamo-table.properties +++ b/src/main/resources/repositorymanager/repository-manager-dynamo-table.properties @@ -4,5 +4,9 @@ dynamo.table.repository-manager.anagrafica-client-name=${PnEcTableNameAnagrafica ### RICHIESTE ### dynamo.table.repository-manager.richieste-personal-name=${PnEcTableNameRichieste:pn-EcRichieste} dynamo.table.repository-manager.richieste-metadata-name=${PnEcTableNameRichiesteMetadati:pn-EcRichiesteMetadati} + +### EVENTI SCARTATI ### + +dynamo.table.repository-manager.scarti-consolidatore-name=${PnEcTableNameScartiConsolidatore:pn-EcScartiConsolidatore} dynamo.table.repository-manager.richieste-conversione-request-name=${PnEcTableNameRichiesteConversione:pn-EcRichiesteConversione} dynamo.table.repository-manager.richieste-conversione-pdf-name=${PnEcTableNameConversionePdf:pn-EcConversionePDF} diff --git a/src/test/java/it/pagopa/pn/ec/commons/utils/CompareUtilsTest.java b/src/test/java/it/pagopa/pn/ec/commons/utils/CompareUtilsTest.java new file mode 100644 index 000000000..724c99bbb --- /dev/null +++ b/src/test/java/it/pagopa/pn/ec/commons/utils/CompareUtilsTest.java @@ -0,0 +1,130 @@ +package it.pagopa.pn.ec.commons.utils; + +import it.pagopa.pn.ec.rest.v1.dto.*; +import it.pagopa.pn.ec.testutils.annotation.SpringBootTestWebEnv; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import static it.pagopa.pn.ec.commons.constant.Status.SENT; + +@SpringBootTestWebEnv +class CompareUtilsTest { + + @Test + void isSameEventDigitalOk() { + OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS); + DigitalProgressStatusDto digitalProgressStatusDto = new DigitalProgressStatusDto() + .status(SENT.getStatusTransactionTableCompliant()) + .generatedMessage(new GeneratedMessageDto().id("id").system("system")) + .eventTimestamp(now); + EventsDto eventsDto = new EventsDto().digProgrStatus(digitalProgressStatusDto); + + boolean isSameEvent = CompareUtils.isSameEvent(List.of(eventsDto), digitalProgressStatusDto, SENT.getStatusTransactionTableCompliant()); + Assertions.assertTrue(isSameEvent); + } + + @Test + void isSameEventPaperOk() { + OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS); + PaperProgressStatusDto paperProgressStatusDto = new PaperProgressStatusDto() + .status(SENT.getStatusTransactionTableCompliant()) + .statusDateTime(now); + + boolean isSameEvent = CompareUtils.isSameEvent(paperProgressStatusDto, paperProgressStatusDto, SENT.getStatusTransactionTableCompliant()); + Assertions.assertTrue(isSameEvent); + } + + @Test + void isSameEventConsolidatoreOk() { + OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS); + + String id = "id"; + String uri = "uri"; + String documentType = "documentType"; + String sha256 = "sha256"; + + AttachmentsProgressEventDto attachments = new AttachmentsProgressEventDto() + .id(id) + .date(now) + .uri(uri) + .documentType(documentType) + .sha256(sha256); + + ConsolidatoreIngressPaperProgressStatusEventAttachments consAttachments = new ConsolidatoreIngressPaperProgressStatusEventAttachments() + .id(id) + .date(now) + .uri(uri) + .documentType(documentType) + .sha256(sha256); + + String name = "name"; + String nameRow2 = "nameRow2"; + String city = "city"; + String city2 = "city2"; + String country = "country"; + String address = "address"; + String addressRow2 = "addressRow2"; + String cap = "cap"; + String pr = "pr"; + + DiscoveredAddressDto discoveredAddress = new DiscoveredAddressDto() + .name(name) + .nameRow2(nameRow2) + .city(city) + .city2(city2) + .country(country) + .address(address) + .addressRow2(addressRow2) + .cap(cap) + .pr(pr); + + ConsolidatoreIngressPaperProgressStatusEventDiscoveredAddress consDiscoveredAddress = new ConsolidatoreIngressPaperProgressStatusEventDiscoveredAddress() + .name(name) + .nameRow2(nameRow2) + .city(city) + .city2(city2) + .country(country) + .address(address) + .addressRow2(addressRow2) + .cap(cap) + .pr(pr); + + String statusCode = "P000"; + String statusDescription = "desc"; + String registeredLetterCode = "code"; + String productType = "type"; + String iun = "iun"; + String deliveryFailureCause = "cause"; + + PaperProgressStatusDto paperProgressStatusDto = new PaperProgressStatusDto() + .status(SENT.getStatusTransactionTableCompliant()) + .statusDateTime(now) + .registeredLetterCode(registeredLetterCode) + .productType(productType) + .iun(iun) + .deliveryFailureCause(deliveryFailureCause) + .statusCode(statusCode) + .statusDescription(statusDescription) + .attachments(List.of(attachments)) + .discoveredAddress(discoveredAddress); + + ConsolidatoreIngressPaperProgressStatusEvent consEvent = new ConsolidatoreIngressPaperProgressStatusEvent() + .statusDateTime(now) + .registeredLetterCode(registeredLetterCode) + .productType(productType) + .iun(iun) + .deliveryFailureCause(deliveryFailureCause) + .statusCode(statusCode) + .statusDescription(statusDescription) + .attachments(List.of(consAttachments)) + .discoveredAddress(consDiscoveredAddress); + + boolean isSameEvent = CompareUtils.isSameEvent(paperProgressStatusDto, consEvent); + Assertions.assertTrue(isSameEvent); + } + +} diff --git a/src/test/java/it/pagopa/pn/ec/consolidatore/rest/RicezioneEsitiConsolidatoreControllerTest.java b/src/test/java/it/pagopa/pn/ec/consolidatore/rest/RicezioneEsitiConsolidatoreControllerTest.java index c7ce5f8ed..fe400c6a2 100644 --- a/src/test/java/it/pagopa/pn/ec/consolidatore/rest/RicezioneEsitiConsolidatoreControllerTest.java +++ b/src/test/java/it/pagopa/pn/ec/consolidatore/rest/RicezioneEsitiConsolidatoreControllerTest.java @@ -45,6 +45,7 @@ import it.pagopa.pn.ec.commons.service.impl.SqsServiceImpl; import it.pagopa.pn.ec.testutils.annotation.SpringBootTestWebEnv; import lombok.CustomLog; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.hamcrest.Matchers; @@ -206,7 +207,8 @@ private ConsolidatoreIngressPaperProgressStatusEvent getProgressStatusEventWithI private RequestDto getRequestDto(EventsDto... eventsDtos) { return new RequestDto().requestIdx(requestId) .xPagopaExtchCxId(xPagopaExtchServiceIdHeaderValue) - .requestMetadata(new RequestMetadataDto().eventsList(List.of(eventsDtos))); + .requestMetadata(new RequestMetadataDto().eventsList(List.of(eventsDtos)) + .paperRequestMetadata(new PaperRequestMetadataDto())); } private Stream provideArguments() { @@ -247,6 +249,7 @@ void ricezioneEsitiInvalidAttachmentUri() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); FileDownloadResponse fileDownloadResponse = new FileDownloadResponse(); fileDownloadResponse.setKey(documentKey); @@ -274,6 +277,7 @@ void ricezioneEsitiAttachmentNotAvailable() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); when(fileCall.getFile(documentKey, xPagopaExtchServiceIdHeaderValue, true)).thenReturn(Mono.error(new AttachmentNotAvailableException(documentKey))); @@ -298,6 +302,7 @@ void ricezioneEsitiAttachmentGeneric400() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); when(fileCall.getFile(documentKey, xPagopaExtchServiceIdHeaderValue, true)) .thenReturn(Mono.error(new Generic400ErrorException("Chiamata a safestorage non valida", "Resource is no longer available. It may have been removed or deleted."))); @@ -346,6 +351,7 @@ void ricezioneEsitierroreStatusDecode() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.error(new StatusNotFoundException("status"))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); FileDownloadResponse fileDownloadResponse = new FileDownloadResponse(); fileDownloadResponse.setKey(documentKey); @@ -374,6 +380,7 @@ void ricezioneEsitiErroreValidazioneIun() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun("DIFFERENT_IUN"))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); FileDownloadResponse fileDownloadResponse = new FileDownloadResponse(); fileDownloadResponse.setKey(documentKey); @@ -403,6 +410,7 @@ void ricezioneEsitiErroreValidazioneStatusDateTime() { EventsDto badSentEvent = new EventsDto().paperProgrStatus(new PaperProgressStatusDto().status(SENT.getStatusTransactionTableCompliant()).statusDateTime(now.plusDays(1))); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(badSentEvent))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); FileDownloadResponse fileDownloadResponse = new FileDownloadResponse(); fileDownloadResponse.setKey(documentKey); @@ -457,8 +465,9 @@ void ricezioneEsitiErroreValidazioneStatusDateTime() { void ricezioneEsitiErroreValidazioneIdRichiesta() { log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiErroreValidazioneIdRichiesta() : START"); when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); - - when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.error(new RestCallException.ResourceNotFoundException())); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); + + when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.error(new RestCallException.ResourceNotFoundException())); List events = new ArrayList<>(); events.add(getProgressStatusEventWithoutAttachments()); @@ -481,8 +490,9 @@ void ricezioneEsitiErroreValidazioneStatusCode() { log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiErroreValidazioneStatusCode() : START"); when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); - - ConsolidatoreIngressPaperProgressStatusEvent progressStatusEvent = getProgressStatusEventWithoutAttachments(); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); + + ConsolidatoreIngressPaperProgressStatusEvent progressStatusEvent = getProgressStatusEventWithoutAttachments(); progressStatusEvent.setStatusCode(STATUS_CODE_INESISTENTE); List events = new ArrayList<>(); @@ -505,8 +515,8 @@ void ricezioneEsitiErroreValidazioneStatusCode() { void ricezioneEsitiErroreValidazioneAttachments() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiErroreValidazioneAttachments() : START"); - - when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); + when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(fileCall.getFile(documentKey, xPagopaExtchServiceIdHeaderValue, true)) .thenReturn(Mono.error(new AttachmentNotAvailableException(documentKey))); @@ -567,6 +577,7 @@ void ricezioneEsitiWithRecCodeAndAttachments(){ when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); when(sqsService.send(eq(notificationTrackerSqsName.statoCartaceoName()), any(NotificationTrackerQueueDto.class))) .thenReturn(Mono.error(new SqsClientException(notificationTrackerSqsName.statoCartaceoName()))); @@ -625,6 +636,7 @@ void ricezioneEsitiErroreValidazioneDeliveryFailureCauseNotInStatusCodeShouldBeA when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); FileDownloadResponse fileDownloadResponse = new FileDownloadResponse(); fileDownloadResponse.setKey(documentKey); @@ -676,7 +688,7 @@ void ricezioneEsitiErroreValidazioneDeliveryWithIncorrectStatusCodeShouldBeAdded when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(SENT_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); - + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); ConsolidatoreIngressPaperProgressStatusEvent progressStatusEvent = getProgressStatusEventWithoutAttachments(); progressStatusEvent.setStatusCode(STATUS_CODE_INESISTENTE); @@ -703,6 +715,7 @@ void ricezioneEsitiWithRetryStatusShouldThrowException() { when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(RETRY_EVENT,RETRY_EVENT))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); List events = new ArrayList<>(); events.add(getProgressStatusEventWithoutAttachments()); @@ -776,6 +789,7 @@ void ricezioneEsitiWithStatusDateTimeBeforeSentEventDateTimeShouldThrowException .statusDateTime(OffsetDateTime.of(2024, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC))); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(bookedEvent,sentEvent))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); List events = new ArrayList<>(); events.add(getProgressStatusEventWithoutAttachments().statusDateTime(OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC))); @@ -804,6 +818,7 @@ void ricezioneEsitiWithStatusDateTimeBeforeBookedEventDateTimeShouldThrowExcepti .statusDateTime(OffsetDateTime.of(2024, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC))); when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(bookedEvent,retryEvent))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); List events = new ArrayList<>(); events.add(getProgressStatusEventWithoutAttachments().statusDateTime(OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC))); @@ -876,6 +891,110 @@ void ricezioneEsitiWithStatusDateTimeAfterBookedEventDateTimeShouldReturnOk(){ .isOk(); } + //con evento duplicato: + // configurazione globale non attiva -> verifichiamo che non ci sia chiamata al controllo di duplicazione + // configurazione globale attiva + passthrough a true -> verifichiamo che non ci sia chiamata al controllo di duplicazione + // configurazione globale attiva + passthrough a false -> verifichiamo che ci sia chiamata al controllo di duplicazione e che venga ritornata eccezione + + //CG NA: + @Test + void ricezioneEsitiDuplicatesCheckNoActiveConfigOk() { + log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiDuplicatesCheckNoActiveConfigOk() : START"); + ConsolidatoreIngressPaperProgressStatusEvent event = getProgressStatusEventWithoutAttachments(); + EventsDto con010 = new EventsDto().paperProgrStatus(new PaperProgressStatusDto().status(event.getStatusCode()) + .statusDateTime(event.getStatusDateTime()) + .statusDescription(event.getStatusDescription()) + .iun(event.getIun()) + .productType(event.getProductType()) + .clientRequestTimeStamp(event.getClientRequestTimeStamp()) + ); + ReflectionTestUtils.setField(ricezioneEsitiCartaceoServiceImpl, "duplicatesCheck", PRODUCT_TYPE_AR); + when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); + when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(BOOKED_EVENT,SENT_EVENT, con010))); + when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + + List events = new ArrayList<>(); + events.add(event); + + webClient.put() + .uri(RICEZIONE_ESITI_ENDPOINT) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .header(xPagopaExtchServiceIdHeaderName, xPagopaExtchServiceIdHeaderValue) + .header(xApiKeyHeaderaName, xApiKeyHeaderValue) + .body(BodyInserters.fromValue(events)) + .exchange() + .expectStatus() + .isOk(); + } + + @Test + void ricezioneEsitiDuplicatesCheckActiveConfigActivePassthroughOk() { + log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiDuplicatesCheckNoActiveConfigOk() : START"); + ConsolidatoreIngressPaperProgressStatusEvent event = getProgressStatusEventWithoutAttachments(); + EventsDto con010 = new EventsDto().paperProgrStatus(new PaperProgressStatusDto().status(event.getStatusCode()) + .statusDateTime(event.getStatusDateTime()) + .statusDescription(event.getStatusDescription()) + .iun(event.getIun()) + .productType(event.getProductType()) + .clientRequestTimeStamp(event.getClientRequestTimeStamp()) + ); + RequestDto requestDto = getRequestDto(BOOKED_EVENT, SENT_EVENT, con010); + requestDto.getRequestMetadata().getPaperRequestMetadata().setDuplicateCheckPassthrough(true); + ReflectionTestUtils.setField(ricezioneEsitiCartaceoServiceImpl, "duplicatesCheck", PRODUCT_TYPE_AR); + when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); + when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(requestDto)); + when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + + List events = new ArrayList<>(); + events.add(event); + + webClient.put() + .uri(RICEZIONE_ESITI_ENDPOINT) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .header(xPagopaExtchServiceIdHeaderName, xPagopaExtchServiceIdHeaderValue) + .header(xApiKeyHeaderaName, xApiKeyHeaderValue) + .body(BodyInserters.fromValue(events)) + .exchange() + .expectStatus() + .isOk(); + } + + @Test + void ricezioneEsitiDuplicatesCheckActiveConfigNoActivePassthroughKo() { + log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiDuplicatesCheckNoActiveConfigOk() : START"); + ConsolidatoreIngressPaperProgressStatusEvent event = getProgressStatusEventWithoutAttachments(); + EventsDto con010 = new EventsDto().paperProgrStatus(new PaperProgressStatusDto().status(event.getStatusCode()) + .statusCode(event.getStatusCode()) + .statusDateTime(event.getStatusDateTime()) + .statusDescription(event.getStatusDescription()) + .iun(event.getIun()) + .productType(event.getProductType()) + .clientRequestTimeStamp(event.getClientRequestTimeStamp()) + ); + ReflectionTestUtils.setField(ricezioneEsitiCartaceoServiceImpl, "duplicatesCheck", "ProductType|"+PRODUCT_TYPE_AR+"|OtherProductType"); + when(authService.clientAuth(anyString())).thenReturn(Mono.just(clientConfigurationInternalDto)); + when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(BOOKED_EVENT, SENT_EVENT, con010))); + when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); + + List events = new ArrayList<>(); + events.add(event); + + webClient.put() + .uri(RICEZIONE_ESITI_ENDPOINT) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .header(xPagopaExtchServiceIdHeaderName, xPagopaExtchServiceIdHeaderValue) + .header(xApiKeyHeaderaName, xApiKeyHeaderValue) + .body(BodyInserters.fromValue(events)) + .exchange() + .expectStatus() + .isBadRequest(); + } + + @Test void ricezioneEsitiWithStatusDateTimeAndClientRequestTimeStampInFutureShouldThrowException(){ log.info("RicezioneEsitiConsolidatoreControllerTest.ricezioneEsitiWithStatusDateTimeAndClientRequestTimeStampInFutureShouldThrowException() : START"); @@ -893,6 +1012,8 @@ void ricezioneEsitiWithStatusDateTimeAndClientRequestTimeStampInFutureShouldThro when(gestoreRepositoryCall.getRichiesta(xPagopaExtchServiceIdHeaderValue, requestId)).thenReturn(Mono.just(getRequestDto(bookedEvent,retryEvent,clientRequestTimeStamp))); when(statusPullService.paperPullService(anyString(), anyString())).thenReturn(Mono.just(new PaperProgressStatusEvent().productType(PRODUCT_TYPE_AR).iun(IUN))); + when(gestoreRepositoryCall.insertDiscardedEvents(any())).thenReturn(Flux.empty()); + List events = new ArrayList<>(); events.add(getProgressStatusEventWithoutAttachments().statusDateTime(now.plusDays(1))); events.add(getProgressStatusEventWithoutAttachments().clientRequestTimeStamp(now.plusDays(1))); diff --git a/src/test/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsControllerTest.java b/src/test/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsControllerTest.java new file mode 100644 index 000000000..b627a4ad8 --- /dev/null +++ b/src/test/java/it/pagopa/pn/ec/repositorymanager/rest/DiscardedEventsControllerTest.java @@ -0,0 +1,129 @@ +package it.pagopa.pn.ec.repositorymanager.rest; + + +import it.pagopa.pn.ec.repositorymanager.model.entity.DiscardedEvent; +import it.pagopa.pn.ec.repositorymanager.service.DiscardedEventsService; +import it.pagopa.pn.ec.rest.v1.dto.*; +import it.pagopa.pn.ec.testutils.annotation.SpringBootTestWebEnv; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; +import reactor.core.publisher.Flux; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.http.MediaType.APPLICATION_JSON; + +@AutoConfigureWebTestClient +@SpringBootTestWebEnv +@DirtiesContext +class DiscardedEventsControllerTest { + + @Autowired + DiscardedEventsController discardedEventsController; + + @Autowired + WebTestClient webTestClient; + + @SpyBean + DiscardedEventsService discardedEventsService; + + @Autowired + DynamoDbEnhancedAsyncClient dynamoDbEnhancedAsyncClient; + + + private static final String DISCARDED_EVENTS_ENDPOINT = "/external-channel/gestoreRepository/discarded-events"; + + private static final String SCARTI_CONSOLIDATORE_TABLE_NAME = "pn-EcScartiConsolidatore"; + + private DiscardedEventDto generateDiscardedEventDto(String requestId, String timestampRicezione, String codiceScarto, String dataRicezione, String details, String jsonRicevuto, String payloadHash) { + DiscardedEventDto discardedEvent = new DiscardedEventDto(); + discardedEvent.setRequestId(requestId); + discardedEvent.setTimestampRicezione(timestampRicezione); + discardedEvent.setCodiceScarto(codiceScarto); + discardedEvent.setDataRicezione(dataRicezione); + discardedEvent.setDetails(details); + discardedEvent.setJsonRicevuto(jsonRicevuto); + discardedEvent.setPayloadHash(payloadHash); + + + return discardedEvent; + } + + private Flux generateDiscardedEventDtoFlux(boolean isValid) { + return Flux.just( + generateDiscardedEventDto("requestId1", "timeStampRicezione1", "codiceScarto1", "dataRicezione1", "details1", "jsonRicevuto1", "payloadHash1"), + generateDiscardedEventDto(isValid ? "requestId2" : null, "timeStampRicezione2", "codiceScarto2", "dataRicezione2", "details2", "jsonRicevuto2", "payloadHash2"), + generateDiscardedEventDto("requestId3", "timeStampRicezione3", "codiceScarto3", "dataRicezione3", "details3", "jsonRicevuto3", "payloadHash3") + ); + } + + private DiscardedEvent getDiscardedEventFromDynamo(String requestId, String timestampRicezione) { + Key key = Key.builder().partitionValue(requestId).sortValue(timestampRicezione).build(); + GetItemEnhancedRequest request = GetItemEnhancedRequest.builder().key(key).build(); + return dynamoDbEnhancedAsyncClient.table(SCARTI_CONSOLIDATORE_TABLE_NAME, TableSchema.fromBean(DiscardedEvent.class)).getItem(request).join(); + } + + private boolean areDiscardedEventsEquals(DiscardedEvent discardedEvent, DiscardedEventDto discardedEventDto) { + return discardedEvent.getRequestId().equals(discardedEventDto.getRequestId()) && + discardedEvent.getTimestampRicezione().equals(discardedEventDto.getTimestampRicezione()) && + discardedEvent.getCodiceScarto().equals(discardedEventDto.getCodiceScarto()) && + discardedEvent.getDataRicezione().equals(discardedEventDto.getDataRicezione()) && + discardedEvent.getDetails().equals(discardedEventDto.getDetails()) && + discardedEvent.getJsonRicevuto().equals(discardedEventDto.getJsonRicevuto()) && + discardedEvent.getPayloadHash().equals(discardedEventDto.getPayloadHash()); + } + + @Test + void insertDiscardedEventsWebClientOkTest() { + Flux events = generateDiscardedEventDtoFlux(true); + + webTestClient.post() + .uri(DISCARDED_EVENTS_ENDPOINT) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .body(BodyInserters.fromPublisher(events, DiscardedEventDto.class)) + .exchange() + .expectStatus() + .is2xxSuccessful() + .returnResult(DiscardedEventDto.class).getResponseBody().map(discardedEventDto -> { + DiscardedEvent discardedEvent = getDiscardedEventFromDynamo(discardedEventDto.getRequestId(), discardedEventDto.getTimestampRicezione()); + Assertions.assertTrue(areDiscardedEventsEquals(discardedEvent, discardedEventDto)); + return discardedEventDto; + }).subscribe(); + + + verify(discardedEventsService, times(3)).insertDiscardedEvent(any()); + } + + @Test + void insertDiscardedEventMissingRequiredPropertyKoTest() { + Flux events = generateDiscardedEventDtoFlux(false); + + webTestClient.post() + .uri(DISCARDED_EVENTS_ENDPOINT) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .body(BodyInserters.fromPublisher(events, DiscardedEventDto.class)) + .exchange() + .expectStatus() + .isBadRequest() + .returnResult(DiscardedEventDto.class).getResponseBody().map(discardedEventDto -> { + DiscardedEvent discardedEvent = getDiscardedEventFromDynamo(discardedEventDto.getRequestId(), discardedEventDto.getTimestampRicezione()); + Assertions.assertNull(discardedEvent); + return discardedEventDto; + }).subscribe(); + + verify(discardedEventsService, times(1)).insertDiscardedEvent(any()); + } +} diff --git a/src/test/java/it/pagopa/pn/ec/testutils/localstack/LocalStackTestConfig.java b/src/test/java/it/pagopa/pn/ec/testutils/localstack/LocalStackTestConfig.java index 43df0f4b7..d19440ce1 100644 --- a/src/test/java/it/pagopa/pn/ec/testutils/localstack/LocalStackTestConfig.java +++ b/src/test/java/it/pagopa/pn/ec/testutils/localstack/LocalStackTestConfig.java @@ -10,6 +10,7 @@ import it.pagopa.pn.ec.pec.configurationproperties.PecSqsQueueName; import it.pagopa.pn.ec.repositorymanager.configurationproperties.RepositoryManagerDynamoTableName; import it.pagopa.pn.ec.repositorymanager.model.entity.ClientConfiguration; +import it.pagopa.pn.ec.repositorymanager.model.entity.DiscardedEvent; import it.pagopa.pn.ec.repositorymanager.model.entity.RequestMetadata; import it.pagopa.pn.ec.repositorymanager.model.entity.RequestPersonal; import it.pagopa.pn.ec.scaricamentoesitipec.configurationproperties.ScaricamentoEsitiPecProperties; @@ -240,7 +241,8 @@ private void initDynamo() { entry(repositoryManagerDynamoTableName.richiestePersonalName(), RequestPersonal.class), entry(repositoryManagerDynamoTableName.richiesteMetadataName(), RequestMetadata.class), entry(repositoryManagerDynamoTableName.richiesteConversioneRequestName(), RequestConversionEntity.class), - entry(repositoryManagerDynamoTableName.richiesteConversionePdfName(), PdfConversionEntity.class) + entry(repositoryManagerDynamoTableName.richiesteConversionePdfName(), PdfConversionEntity.class), + entry(repositoryManagerDynamoTableName.scartiConsolidatoreName(), DiscardedEvent.class) ); tableNameWithEntityClass.forEach((tableName, entityClass) -> { diff --git a/src/test/resources/cartaceo/lavorazione-cartaceo.properties b/src/test/resources/cartaceo/lavorazione-cartaceo.properties index da517aaf7..335765199 100644 --- a/src/test/resources/cartaceo/lavorazione-cartaceo.properties +++ b/src/test/resources/cartaceo/lavorazione-cartaceo.properties @@ -2,6 +2,7 @@ lavorazione-cartaceo.max-thread-pool-size=${LavorazioneCartaceoMaxThreadPoolSize pn.ec.esiti-cartaceo.parameter.name=pn-EC-esitiCartaceo ricezione-esiti-pec.consider-event-without-status-as-booked=true ricezione-esiti-cartaceo.consider-event-without-sent-status-as-booked=${PnEcRicezioneEsitiCartaceoConsiderEventWithoutSentStatusAsBooked:true} +ricezione-esiti-cartaceo.duplicates-check=productType1|productType2|productType3 ricezione-esiti-cartaceo.allowed-future-offset-duration=1m lavorazione-cartaceo.max-retry-attempts=1 lavorazione-cartaceo.min-retry-backoff=1