diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/entity/OnboardingAttachment.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/entity/OnboardingAttachment.java index 39b8260de..b78695602 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/entity/OnboardingAttachment.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/entity/OnboardingAttachment.java @@ -6,6 +6,8 @@ public class OnboardingAttachment { private Onboarding onboarding; private AttachmentTemplate attachment; + public OnboardingAttachment() {} + private OnboardingAttachment(OnboardingAttachment.Builder builder) { this.onboarding = builder.onboarding; this.attachment = builder.attachment; diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractService.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractService.java index 8032f4a08..9878b39ed 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractService.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractService.java @@ -1,6 +1,7 @@ package it.pagopa.selfcare.onboarding.service; import it.pagopa.selfcare.onboarding.entity.Onboarding; +import it.pagopa.selfcare.onboarding.entity.OnboardingAttachment; import it.pagopa.selfcare.onboarding.entity.OnboardingWorkflow; import org.openapi.quarkus.user_registry_json.model.UserResource; @@ -24,6 +25,8 @@ File createAttachmentPDF( File retrieveContractNotSigned(OnboardingWorkflow onboardingWorkflow, String productName); + File retrieveAttachment(OnboardingAttachment onboardingAttachment, String productName); + Optional getLogoFile(); void uploadAggregatesCsv(OnboardingWorkflow onboardingWorkflow); diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java index ad0565a70..224575948 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/ContractServiceDefault.java @@ -9,10 +9,7 @@ import it.pagopa.selfcare.onboarding.config.PagoPaSignatureConfig; import it.pagopa.selfcare.onboarding.crypto.PadesSignService; import it.pagopa.selfcare.onboarding.crypto.entity.SignatureInformation; -import it.pagopa.selfcare.onboarding.entity.AggregateInstitution; -import it.pagopa.selfcare.onboarding.entity.Institution; -import it.pagopa.selfcare.onboarding.entity.Onboarding; -import it.pagopa.selfcare.onboarding.entity.OnboardingWorkflow; +import it.pagopa.selfcare.onboarding.entity.*; import it.pagopa.selfcare.onboarding.exception.GenericOnboardingException; import it.pagopa.selfcare.onboarding.utils.ClassPathStream; import jakarta.enterprise.context.ApplicationScoped; @@ -387,6 +384,16 @@ public File retrieveContractNotSigned(OnboardingWorkflow onboardingWorkflow, Str return azureBlobClient.getFileAsPdf(path); } + @Override + public File retrieveAttachment(OnboardingAttachment onboardingAttachment, String productName) { + final String onboardingId = onboardingAttachment.getOnboarding().getId(); + final String filename = + CONTRACT_FILENAME_FUNC.apply(onboardingAttachment.getAttachment().getName(), productName); + final String path = + String.format("%s%s/%s", azureStorageConfig.contractPath(), onboardingId, filename); + return azureBlobClient.getFileAsPdf(path); + } + @Override public Optional getLogoFile() { if (Objects.nonNull(isLogoEnable) && isLogoEnable) { diff --git a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java index 6f492332c..d470f5552 100644 --- a/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java +++ b/apps/onboarding-functions/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java @@ -150,7 +150,11 @@ public void saveTokenWithAttachment(OnboardingAttachment onboardingAttachment) { Product product = productService.getProductIsValid(onboarding.getProductId()); - saveTokenAttachment(onboardingAttachment, product); + File contract = contractService.retrieveAttachment(onboardingAttachment, product.getTitle()); + DSSDocument document = new FileDocument(contract); + String digest = document.getDigest(DigestAlgorithm.SHA256); + + saveTokenAttachment(onboardingAttachment, product, digest); } private boolean checkTokenExist(Onboarding onboarding) { @@ -168,25 +172,25 @@ private void saveToken(OnboardingWorkflow onboardingWorkflow, Product product, S Onboarding onboarding = onboardingWorkflow.getOnboarding(); // Persist token entity - Token token = buildBaseToken(onboarding); + Token token = buildBaseToken(onboarding, digest); token.setContractTemplate(onboardingWorkflow.getContractTemplatePath(product)); token.setContractVersion(onboardingWorkflow.getContractTemplateVersion(product)); token.setContractFilename( CONTRACT_FILENAME_FUNC.apply( onboardingWorkflow.getPdfFormatFilename(), product.getTitle())); - token.setChecksum(digest); token.setType(onboardingWorkflow.getTokenType()); tokenRepository.persist(token); } - private void saveTokenAttachment(OnboardingAttachment onboardingAttachment, Product product) { + private void saveTokenAttachment( + OnboardingAttachment onboardingAttachment, Product product, String digest) { Onboarding onboarding = onboardingAttachment.getOnboarding(); AttachmentTemplate attachmentTemplate = onboardingAttachment.getAttachment(); // Persist token entity - Token token = buildBaseToken(onboarding); + Token token = buildBaseToken(onboarding, digest); token.setContractTemplate(attachmentTemplate.getTemplatePath()); token.setContractVersion(attachmentTemplate.getTemplateVersion()); token.setContractFilename( @@ -196,13 +200,14 @@ private void saveTokenAttachment(OnboardingAttachment onboardingAttachment, Prod tokenRepository.persist(token); } - private Token buildBaseToken(Onboarding onboarding) { + private Token buildBaseToken(Onboarding onboarding, String digest) { log.debug("creating Token for onboarding {} ...", onboarding.getId()); Token token = new Token(); token.setId(UUID.randomUUID().toString()); token.setOnboardingId(onboarding.getId()); token.setCreatedAt(LocalDateTime.now()); token.setUpdatedAt(LocalDateTime.now()); + token.setChecksum(digest); token.setProductId(onboarding.getProductId()); return token; } diff --git a/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/functions/OnboardingFunctionsTest.java b/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/functions/OnboardingFunctionsTest.java index d97cccd45..62d45cc74 100644 --- a/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/functions/OnboardingFunctionsTest.java +++ b/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/functions/OnboardingFunctionsTest.java @@ -1,25 +1,6 @@ package it.pagopa.selfcare.onboarding.functions; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.BUILD_CONTRACT_ACTIVITY_NAME; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_AGGREGATES_CSV_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_AGGREGATE_ONBOARDING_REQUEST_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_DELEGATION_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_INSTITUTION_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_ONBOARDING_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.CREATE_USERS_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.DELETE_MANAGERS_BY_IC_AND_ADE; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.EXISTS_DELEGATION_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.ONBOARDINGS_AGGREGATE_ORCHESTRATOR; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.RETRIEVE_AGGREGATES_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SAVE_TOKEN_WITH_CONTRACT_ACTIVITY_NAME; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_COMPLETION_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_ONBOARDING_APPROVE_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_REGISTRATION_APPROVE_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_REGISTRATION_FOR_CONTRACT; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_REGISTRATION_FOR_CONTRACT_WHEN_APPROVE_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_REGISTRATION_REQUEST_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.SEND_MAIL_REJECTION_ACTIVITY; -import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.STORE_ONBOARDING_ACTIVATEDAT; +import static it.pagopa.selfcare.onboarding.functions.utils.ActivityName.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -86,8 +67,7 @@ class OnboardingFunctionsTest { @InjectMock CompletionService completionService; - @InjectMock - ProductService productService; + @InjectMock ProductService productService; @Inject ObjectMapper objectMapper; @@ -96,6 +76,13 @@ class OnboardingFunctionsTest { final String onboardingWorkflowString = "{\"type\":\"INSTITUTION\",\"onboarding\":{\"id\":\"id\",\"productId\":\"prod-test\",\"testEnvProductIds\":null,\"workflowType\":\"FOR_APPROVE\",\"institution\":null,\"users\":null,\"aggregates\":null,\"pricingPlan\":null,\"billing\":null,\"signContract\":null,\"expiringDate\":null,\"status\":\"REQUEST\",\"userRequestUid\":null,\"workflowInstanceId\":null,\"createdAt\":null,\"updatedAt\":null,\"activatedAt\":null,\"deletedAt\":null,\"reasonForReject\":null,\"isAggregator\":null}}"; + final String onboardingWorkflowString2 = + "{\"type\":\"INSTITUTION\",\"onboarding\":{\"id\":\"id\",\"productId\":\"prod-test\",\"testEnvProductIds\":null,\"workflowType\":\"CONTRACT_REGISTRATION\",\"institution\":null,\"users\":null,\"aggregates\":null,\"pricingPlan\":null,\"billing\":null,\"signContract\":null,\"expiringDate\":null,\"status\":\"REQUEST\",\"userRequestUid\":null,\"workflowInstanceId\":null,\"createdAt\":null,\"updatedAt\":null,\"activatedAt\":null,\"deletedAt\":null,\"reasonForReject\":null,\"isAggregator\":null}}"; + + final String onboardingAttachmentString = + "{\"onboarding\":{\"id\":\"id\",\"productId\":\"prod-test\",\"testEnvProductIds\":null,\"workflowType\":\"FOR_APPROVE\",\"institution\":null,\"users\":null,\"aggregates\":null,\"pricingPlan\":null,\"billing\":null,\"signContract\":null,\"expiringDate\":null,\"status\":\"REQUEST\",\"userRequestUid\":null,\"workflowInstanceId\":null,\"createdAt\":null,\"updatedAt\":null,\"activatedAt\":null,\"deletedAt\":null,\"reasonForReject\":null,\"isAggregator\":null},\"attachmentTemplate\":{" + + "\"templatePath\": null, \"templateVersion\": null, \"name\": null, \"mandatory\": null, \"generated\": null, \"workflowType\": null, \"workflowState\": null, \"order\": null}}"; + static ExecutionContext executionContext; static { @@ -671,7 +658,78 @@ void buildContract() { } @Test - void buildAttachmentsAndSaveTokensOrchestrator_invokeActivity() throws JsonProcessingException { + void buildAttachmentsAndSaveTokens_validBody_returnsAccepted() { + // Mock HttpRequestMessage with valid body + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + doReturn(Optional.of(onboardingWorkflowString)).when(req).getBody(); + + doAnswer( + (Answer) + invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock() + .status(status); + }) + .when(req) + .createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + final DurableClientContext durableContext = mock(DurableClientContext.class); + final DurableTaskClient client = mock(DurableTaskClient.class); + final String instanceId = "instanceId123"; + + doReturn(client).when(durableContext).getClient(); + doReturn(instanceId) + .when(client) + .scheduleNewOrchestrationInstance("BuildAttachmentAndSaveToken", onboardingWorkflowString); + when(durableContext.createCheckStatusResponse(req, instanceId)) + .thenReturn( + new HttpResponseMessageMock.HttpResponseMessageBuilderMock() + .status(HttpStatus.ACCEPTED) + .build()); + + // Invoke + HttpResponseMessage responseMessage = + function.buildAttachmentsAndSaveTokens(req, durableContext, context); + + // Verify + assertEquals(HttpStatus.ACCEPTED.value(), responseMessage.getStatusCode()); + } + + @Test + void buildAttachmentsAndSaveTokens_emptyBody_returnsBadRequest() { + // Mock HttpRequestMessage with empty body + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + doReturn(Optional.empty()).when(req).getBody(); + + doAnswer( + (Answer) + invocation -> { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock() + .status(status); + }) + .when(req) + .createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + final DurableClientContext durableContext = mock(DurableClientContext.class); + + // Invoke + HttpResponseMessage responseMessage = + function.buildAttachmentsAndSaveTokens(req, durableContext, context); + + // Verify + assertEquals(HttpStatus.BAD_REQUEST.value(), responseMessage.getStatusCode()); + assertEquals("Body can not be empty", responseMessage.getBody()); + } + + @Test + void buildAttachmentAndSaveToken_invokeActivity() throws JsonProcessingException { // given Product product = createDummyProduct(); when(productService.getProductIsValid(anyString())).thenReturn(product); @@ -689,20 +747,51 @@ void buildAttachmentsAndSaveTokensOrchestrator_invokeActivity() throws JsonProce // then verify(productService, times(1)).getProductIsValid(anyString()); - Mockito.verify(orchestrationContext, times(2)) - .callActivity(any(), any(), any(), any()); + Mockito.verify(orchestrationContext, times(2)).callActivity(any(), any(), any(), any()); } - // @Test + @Test + void buildAttachmentAndSaveToken_noAttachments_invokeActivity() throws JsonProcessingException { + // given + Product product = createDummyProduct(); + + when(productService.getProductIsValid(anyString())).thenReturn(product); + + TaskOrchestrationContext orchestrationContext = mock(TaskOrchestrationContext.class); + when(orchestrationContext.getInput(String.class)).thenReturn(onboardingWorkflowString2); + + Task task = mock(Task.class); + when(orchestrationContext.callActivity(any(), any(), any(), any())).thenReturn(task); + when(task.await()).thenReturn("false"); + when(orchestrationContext.allOf(anyList())).thenReturn(task); + + // when + function.buildAttachmentAndSaveToken(orchestrationContext, executionContext); + + // then + verify(productService, times(1)).getProductIsValid(anyString()); + } + + @Test void buildAttachment() { doNothing().when(service).createAttachment(any()); - function.buildAttachment(onboardingWorkflowString, executionContext); + function.buildAttachment(onboardingAttachmentString, executionContext); verify(service, times(1)).createAttachment(any()); } + @Test + void saveTokenAttachment() { + + doNothing().when(service).saveTokenWithAttachment(any()); + + function.saveTokenAttachment(onboardingAttachmentString, executionContext); + + verify(service, times(1)).saveTokenWithAttachment(any()); + } + @Test void saveToken() { diff --git a/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceTest.java b/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceTest.java index c770290f9..844b06f45 100644 --- a/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceTest.java +++ b/apps/onboarding-functions/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceTest.java @@ -55,7 +55,7 @@ class OnboardingServiceTest { private Onboarding createOnboarding() { Onboarding onboarding = new Onboarding(); - onboarding.setId(onboarding.getId()); + onboarding.setId("id"); onboarding.setProductId(productId); onboarding.setUsers(List.of()); Institution institution = new Institution(); @@ -189,35 +189,36 @@ void createContract() { .getContractTemplatePath()); } - /* - @Test - void createAttachments() { + @Test + void createAttachments() { - // Arrange - Onboarding onboarding = createOnboarding(); - Product product = createDummyProduct(); + // Arrange + Onboarding onboarding = createOnboarding(); + AttachmentTemplate attachmentTemplate = createDummyAttachmentTemplate(); + Product product = createDummyProduct(); + OnboardingAttachment onboardingAttachment = new OnboardingAttachment(); + onboardingAttachment.setAttachment(attachmentTemplate); + onboardingAttachment.setOnboarding(onboarding); - when(productService.getProductIsValid(onboarding.getProductId())).thenReturn(product); + when(productService.getProductIsValid(onboarding.getProductId())).thenReturn(product); - OnboardingWorkflow onboardingWorkflow = getOnboardingWorkflowInstitution(onboarding); + // Act + onboardingService.createAttachment(onboardingAttachment); - // Act - onboardingService.createAttachment(onboardingWorkflow); + // Assert + Mockito.verify(productService, Mockito.times(1)).getProductIsValid(onboarding.getProductId()); - // Assert - Mockito.verify(productService, Mockito.times(1)).getProductIsValid(onboarding.getProductId()); + // Capture the path of the template used for the PDF + ArgumentCaptor captorTemplatePath = ArgumentCaptor.forClass(String.class); + Mockito.verify(contractService, Mockito.times(1)) + .createAttachmentPDF(captorTemplatePath.capture(), any(), any(), any()); - // Capture the path of the template used for the PDF - ArgumentCaptor captorTemplatePath = ArgumentCaptor.forClass(String.class); - Mockito.verify(contractService, Mockito.times(1)) - .createAttachmentPDF(captorTemplatePath.capture(), any(), any(), any()); + // Check that the correct template was used + assertEquals( + "path", // This is the template matching the onboarding filter + captorTemplatePath.getValue()); + } - // Check that the correct template was used - assertEquals( - "path", // This is the template matching the onboarding filter - captorTemplatePath.getValue()); - } - */ private Product createDummyProduct() { Product product = new Product(); product.setTitle("Title"); @@ -231,10 +232,7 @@ private Product createDummyProduct() { private static Map createDummyContractTemplateInstitution() { Map institutionTemplate = new HashMap<>(); List attachments = new ArrayList<>(); - AttachmentTemplate attachmentTemplate = new AttachmentTemplate(); - attachmentTemplate.setTemplatePath("path"); - attachmentTemplate.setWorkflowState(OnboardingStatus.REQUEST); - attachmentTemplate.setWorkflowType(List.of(WorkflowType.FOR_APPROVE)); + AttachmentTemplate attachmentTemplate = createDummyAttachmentTemplate(); attachments.add(attachmentTemplate); ContractTemplate conctractTemplate = new ContractTemplate(); conctractTemplate.setAttachments(attachments); @@ -244,6 +242,15 @@ private static Map createDummyContractTemplateInstitut return institutionTemplate; } + private static AttachmentTemplate createDummyAttachmentTemplate() { + AttachmentTemplate attachmentTemplate = new AttachmentTemplate(); + attachmentTemplate.setTemplatePath("path"); + attachmentTemplate.setName("name"); + attachmentTemplate.setWorkflowState(OnboardingStatus.REQUEST); + attachmentTemplate.setWorkflowType(List.of(WorkflowType.FOR_APPROVE)); + return attachmentTemplate; + } + private static Map createDummyContractTemplateUser() { Map institutionTemplate = new HashMap<>(); ContractTemplate conctractTemplate = new ContractTemplate(); @@ -306,6 +313,53 @@ void saveToken() { tokenArgumentCaptor.getValue().getContractVersion()); } + @Test + void saveTokenAttachment() { + Onboarding onboarding = createOnboarding(); + AttachmentTemplate attachmentTemplate = createDummyAttachmentTemplate(); + OnboardingAttachment onboardingAttachment = new OnboardingAttachment(); + onboardingAttachment.setOnboarding(onboarding); + onboardingAttachment.setAttachment(attachmentTemplate); + File contract = + new File( + Objects.requireNonNull( + getClass().getClassLoader().getResource("application.properties")) + .getFile()); + DSSDocument document = new FileDocument(contract); + String digestExpected = document.getDigest(DigestAlgorithm.SHA256); + + Product productExpected = createDummyProduct(); + when(contractService.retrieveAttachment(onboardingAttachment, productExpected.getTitle())) + .thenReturn(contract); + when(productService.getProductIsValid(onboarding.getProductId())).thenReturn(productExpected); + + Mockito.doNothing().when(tokenRepository).persist(any(Token.class)); + + onboardingService.saveTokenWithAttachment(onboardingAttachment); + + ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); + Mockito.verify(tokenRepository, Mockito.times(1)).persist(tokenArgumentCaptor.capture()); + assertEquals(onboarding.getProductId(), tokenArgumentCaptor.getValue().getProductId()); + assertEquals(digestExpected, tokenArgumentCaptor.getValue().getChecksum()); + } + + @Test + void saveTokenAttachment_shouldSkipIfTokenExists() { + Onboarding onboarding = createOnboarding(); + AttachmentTemplate attachmentTemplate = createDummyAttachmentTemplate(); + OnboardingAttachment onboardingAttachment = new OnboardingAttachment(); + onboardingAttachment.setOnboarding(onboarding); + onboardingAttachment.setAttachment(attachmentTemplate); + Token token = createDummyToken(); + + when(tokenRepository.findByOnboardingId(onboarding.getId())).thenReturn(Optional.of(token)); + + onboardingService.saveTokenWithAttachment(onboardingAttachment); + + Mockito.verify(tokenRepository, Mockito.times(1)).findByOnboardingId(onboarding.getId()); + Mockito.verifyNoMoreInteractions(tokenRepository); + } + @Test void loadContract() {