diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/createcase/SubmitCaseTransaction.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/createcase/SubmitCaseTransaction.java index a6641a3628..882679ee2d 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/createcase/SubmitCaseTransaction.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/createcase/SubmitCaseTransaction.java @@ -180,11 +180,14 @@ public CaseDetails submitCase(Event event, ); caseDataAccessControl.grantAccess(savedCaseDetails, idamUser.getId()); + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, savedCaseDetails.getData()); + caseDocumentService.attachCaseDocuments( caseDetails.getReferenceAsString(), caseDetails.getCaseTypeId(), caseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); } @@ -200,11 +203,14 @@ private CaseDetails submitDecentralisedCase(Event event, CaseTypeDefinition case caseDataAccessControl.grantAccess(newCaseDetails, idamUser.getId()); + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, newCaseDetails.getData()); + caseDocumentService.attachCaseDocuments( newCaseDetails.getReferenceAsString(), newCaseDetails.getCaseTypeId(), newCaseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); try { diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventService.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventService.java index 60877aa99f..5f5566eb3f 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventService.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventService.java @@ -275,12 +275,16 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C if (isDecentralisedCase) { // Documents must be attached before the event is committed. // When decentralised we must do the attach before the event is submitted to the decentralised service. + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, caseDetailsAfterCallbackWithoutHashes.getData()); + caseDocumentService.attachCaseDocuments( caseDetails.getReferenceAsString(), caseDetails.getCaseTypeId(), caseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); + var decentralisedCaseDetails = decentralisedCreateCaseEventService.submitDecentralisedEvent( content.getEvent(), caseEventDefinition, caseTypeDefinition, caseDetailsAfterCallbackWithoutHashes, Optional.of(caseDetailsInDatabase), onBehalfOfUser); @@ -297,6 +301,10 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C } else { finalCaseDetails = saveCaseDetails(caseDetailsInDatabase, caseDetailsAfterCallbackWithoutHashes, caseEventDefinition, newState, timeNow); + + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, finalCaseDetails.getData()); + saveAuditEventForCaseDetails( aboutToSubmitCallbackResponse, content.getEvent(), @@ -306,8 +314,7 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C timeNow, oldState, onBehalfOfUser, - securityClassificationService.getClassificationForEvent(caseTypeDefinition, - caseEventDefinition) + securityClassificationService.getClassificationForEvent(caseTypeDefinition, caseEventDefinition) ); caseLinkService.updateCaseLinks(finalCaseDetails, caseTypeDefinition.getCaseFieldDefinitions()); @@ -318,9 +325,8 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C caseDetails.getReferenceAsString(), caseDetails.getCaseTypeId(), caseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); - } return CreateCaseEventResult.caseEventWith() @@ -393,11 +399,14 @@ public CreateCaseEventResult createCaseSystemEvent(final String caseReference, if (resolver.isDecentralised(caseDetailsInDatabase)) { // Documents must be attached before event is committed. // When decentralised we must do the attach before the event is submitted. + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, caseDetailsAfterCallbackWithoutHashes.getData()); + caseDocumentService.attachCaseDocuments( caseDetails.getReferenceAsString(), caseDetails.getCaseTypeId(), caseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); finalCaseDetails = decentralisedCreateCaseEventService.submitDecentralisedEvent(event, caseEventDefinition, @@ -407,6 +416,10 @@ public CreateCaseEventResult createCaseSystemEvent(final String caseReference, } else { finalCaseDetails = saveCaseDetails(caseDetailsInDatabase, caseDetailsAfterCallbackWithoutHashes, caseEventDefinition, newState, timeNow); + + final List verifiedDocumentHashes = caseDocumentService + .filterDocumentHashesAgainstSavedData(documentHashes, finalCaseDetails.getData()); + saveAuditEventForCaseDetails( aboutToSubmitCallbackResponse, event, @@ -425,7 +438,7 @@ public CreateCaseEventResult createCaseSystemEvent(final String caseReference, caseDetails.getReferenceAsString(), caseDetails.getCaseTypeId(), caseDetails.getJurisdiction(), - documentHashes + verifiedDocumentHashes ); } diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentService.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentService.java index 4ebdac6b7e..7dfd1de41d 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentService.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentService.java @@ -13,9 +13,12 @@ import jakarta.inject.Inject; import jakarta.inject.Named; + +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static java.util.Collections.emptyMap; import static uk.gov.hmcts.ccd.domain.service.getcasedocument.CaseDocumentUtils.DOCUMENT_HASH; @@ -138,4 +141,22 @@ private void verifyNoTamper(final List> preCallbackHashes } } + public List filterDocumentHashesAgainstSavedData( + final List documentHashes, + final Map savedData) { + + if (documentHashes.isEmpty()) { + return documentHashes; + } + + final Set savedDocumentIds = caseDocumentUtils.findDocumentIds(savedData); + + if (savedDocumentIds.isEmpty()) { + return Collections.emptyList(); + } + + return documentHashes.stream() + .filter(hash -> savedDocumentIds.contains(hash.getId())) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtils.java b/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtils.java index 294d3fdc75..81c912d0e7 100644 --- a/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtils.java +++ b/src/main/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtils.java @@ -127,4 +127,9 @@ public List getViolatingDocuments(@NonNull final List findDocumentIds(final Map data) { + return findDocumentNodes(data).stream() + .map(this::getDocumentId) + .collect(Collectors.toSet()); + } } diff --git a/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventServiceTest.java b/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventServiceTest.java index 58642fbeb9..18517566a2 100644 --- a/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventServiceTest.java +++ b/src/test/java/uk/gov/hmcts/ccd/domain/service/createevent/CreateCaseEventServiceTest.java @@ -63,6 +63,7 @@ import uk.gov.hmcts.ccd.decentralised.service.DecentralisedCreateCaseEventService; import uk.gov.hmcts.ccd.decentralised.service.SynchronisedCaseProcessor; import uk.gov.hmcts.ccd.infrastructure.user.UserAuthorisation; +import uk.gov.hmcts.ccd.v2.external.domain.DocumentHashToken; import java.time.Clock; import java.time.Instant; @@ -114,7 +115,6 @@ class CreateCaseEventServiceTest extends TestFixtures { private static final String EVENT_ID = "UpdateCase"; private static final String PRE_STATE_ID = "Created"; private static final LocalDateTime LAST_MODIFIED = LocalDateTime.of(2015, 12, 21, 15, 30); - private static final int CASE_VERSION = 0; private static final String ATTRIBUTE_PATH = "DocumentField"; private static final String CATEGORY_ID = "categoryId"; private static final JsonNodeFactory JSON_NODE_FACTORY = new JsonNodeFactory(false); @@ -236,6 +236,7 @@ void setUp() throws Exception { caseDetails = new CaseDetails(); caseDetails.setReference(Long.parseLong(CASE_REFERENCE)); caseDetails.setCaseTypeId(CASE_TYPE_ID); + caseDetails.setJurisdiction(JURISDICTION_ID); caseDetails.setState(PRE_STATE_ID); caseDetails.setLastModified(LAST_MODIFIED); caseDetails.setLastStateModifiedDate(LAST_MODIFIED); @@ -635,12 +636,10 @@ void shouldThrowResourceNotFoundExceptionWhenNoCaseReferenceFound() throws Excep CaseDetailsRepository defaultCaseDetailsRepository = mock(DefaultCaseDetailsRepository.class); doReturn(Optional.empty()).when(defaultCaseDetailsRepository) .findByReference(NON_EXISTENT_CASE_REFERENCE, false); - assertThrows(ResourceNotFoundException.class, () -> { - underTest.createCaseSystemEvent(NON_EXISTENT_CASE_REFERENCE, - ATTRIBUTE_PATH, - CATEGORY_ID, - new Event()); - }); + assertThrows(ResourceNotFoundException.class, () -> underTest.createCaseSystemEvent(NON_EXISTENT_CASE_REFERENCE, + ATTRIBUTE_PATH, + CATEGORY_ID, + new Event())); assertAll( () -> verify(caseTypeService, times(0)).findState(any(), any()), @@ -680,6 +679,181 @@ void shouldAddUploadTimestampToDocument() throws Exception { assertEquals(1, countChanges.get()); } + @Test + @DisplayName("should only attach documents that are present in saved case data") + void shouldOnlyAttachDocumentsPresentInSavedCaseData() { + // GIVEN + final List allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + final List verifiedDocumentHashes = List.of(HASH_TOKEN_A1); + + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(verifiedDocumentHashes).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(allDocumentHashes, caseDetails.getData()); + doReturn(true).when(applicationParams).isAttachDocumentEnabled(); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN + verify(caseDocumentService).filterDocumentHashesAgainstSavedData( + allDocumentHashes, caseDetails.getData()); + verify(caseDocumentService).attachCaseDocuments( + CASE_REFERENCE, + CASE_TYPE_ID, + JURISDICTION_ID, + verifiedDocumentHashes + ); + } + + @Test + @DisplayName("should not attach any documents when callback drops all document fields") + void shouldNotAttachDocumentsWhenCallbackDropsAllDocumentFields() { + // GIVEN - callback drops the document, so all document hashes are filtered out + final List allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(List.of()).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(allDocumentHashes, caseDetails.getData()); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN + verify(caseDocumentService).filterDocumentHashesAgainstSavedData( + allDocumentHashes, caseDetails.getData()); + verify(caseDocumentService).attachCaseDocuments( + CASE_REFERENCE, + CASE_TYPE_ID, + JURISDICTION_ID, + List.of() + ); + } + + @Test + @DisplayName("should attach all documents when all are present in saved case data") + void shouldAttachAllDocumentsWhenAllPresentInSavedCaseData() { + // GIVEN - all documents survive through callback and are saved + final List allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(allDocumentHashes).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(allDocumentHashes, caseDetails.getData()); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN + verify(caseDocumentService).filterDocumentHashesAgainstSavedData( + allDocumentHashes, caseDetails.getData()); + verify(caseDocumentService).attachCaseDocuments( + CASE_REFERENCE, + CASE_TYPE_ID, + JURISDICTION_ID, + allDocumentHashes + ); + } + + @Test + @DisplayName("should filter document hashes using saved case data not pre-save data") + void shouldFilterDocumentHashesUsingFinalSavedCaseDataNotPreSaveData() { + // GIVEN + final List allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + final List verifiedDocumentHashes = List.of(HASH_TOKEN_A1); + + // savedCaseDetails returned from repository after save + final CaseDetails savedCaseDetails = new CaseDetails(); + savedCaseDetails.setReference(Long.parseLong(CASE_REFERENCE)); + savedCaseDetails.setCaseTypeId(CASE_TYPE_ID); + savedCaseDetails.setState(POST_STATE); + final Map savedData = Map.of("savedKey", JSON_NODE_FACTORY.textNode("savedValue")); + savedCaseDetails.setData(savedData); + + doReturn(savedCaseDetails).when(caseDetailsRepository).set(any(CaseDetails.class)); + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(verifiedDocumentHashes).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(allDocumentHashes, savedData); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN - verify filter was called with the data from the repository response + // not from caseDetailsAfterCallbackWithoutHashes + verify(caseDocumentService).filterDocumentHashesAgainstSavedData(allDocumentHashes, savedData); + verify(caseDocumentService).attachCaseDocuments( + CASE_REFERENCE, + CASE_TYPE_ID, + JURISDICTION_ID, + verifiedDocumentHashes + ); + } + + @Test + @DisplayName("should call filterDocumentHashesAgainstSavedData before attachCaseDocuments") + void shouldCallFilterBeforeAttachCaseDocuments() { + // GIVEN + final List allDocumentHashes = List.of(HASH_TOKEN_A1); + final List verifiedDocumentHashes = List.of(HASH_TOKEN_A1); + + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(verifiedDocumentHashes).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(any(), any()); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN - verify ordering: filter must be called before attach + final var inOrder = org.mockito.Mockito.inOrder(caseDocumentService); + inOrder.verify(caseDocumentService).filterDocumentHashesAgainstSavedData(any(), any()); + inOrder.verify(caseDocumentService).attachCaseDocuments(any(), any(), any(), any()); + } + + @Test + @DisplayName("should call filterDocumentHashesAgainstSavedData after saveCaseDetails") + void shouldCallFilterAfterSaveCaseDetails() { + // GIVEN + final List allDocumentHashes = List.of(HASH_TOKEN_A1); + + doReturn(allDocumentHashes).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(allDocumentHashes).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(any(), any()); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN - verify ordering: save must happen before filter + final var inOrder = org.mockito.Mockito.inOrder(caseDetailsRepository, caseDocumentService); + inOrder.verify(caseDetailsRepository).set(any(CaseDetails.class)); + inOrder.verify(caseDocumentService).filterDocumentHashesAgainstSavedData(any(), any()); + } + + @Test + @DisplayName("should not call filterDocumentHashesAgainstSavedData when no document hashes extracted") + void shouldNotCallFilterWhenNoDocumentHashesExtracted() { + // GIVEN + doReturn(List.of()).when(caseDocumentService).extractDocumentHashToken( + anyMap(), anyMap(), anyMap()); + doReturn(List.of()).when(caseDocumentService) + .filterDocumentHashesAgainstSavedData(List.of(), caseDetails.getData()); + + // WHEN + underTest.createCaseEvent(CASE_REFERENCE, caseDataContent); + + // THEN + verify(caseDocumentService).filterDocumentHashesAgainstSavedData(List.of(), caseDetails.getData()); + verify(caseDocumentService).attachCaseDocuments( + CASE_REFERENCE, + CASE_TYPE_ID, + JURISDICTION_ID, + List.of() + ); + } + private CaseFieldDefinition buildDocumentCaseField(String id) { FieldTypeDefinition fieldTypeDefinition = new FieldTypeDefinition(); fieldTypeDefinition.setType(FieldTypeDefinition.DOCUMENT); diff --git a/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentServiceTest.java b/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentServiceTest.java index 92f9c15e05..08b94f2ca0 100644 --- a/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentServiceTest.java @@ -1,7 +1,6 @@ package uk.gov.hmcts.ccd.domain.service.getcasedocument; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -58,14 +57,6 @@ class CaseDocumentServiceTest extends TestFixtures { @InjectMocks private CaseDocumentService underTest; - private static final JsonNodeFactory JSON_NODE_FACTORY = new JsonNodeFactory(false); - private static final String CASE_DETAIL_FIELD = "dataTestField1"; - - private final String urlGoogle = "https://www.google.com"; - private final String urlYahoo = "https://www.yahoo.com"; - private final String urlMicrosoft = "https://www.microsoft.com"; - private final String urlElastic = "https://www.elastic.com"; - private final String urlApple = "https://www.apple.com"; @Test void testShouldReturnClonedCaseDetailsWithoutHashes() throws Exception { @@ -293,4 +284,204 @@ void testShouldNotCheckDocumentsForHashToken() { verifyNoMoreInteractions(documentUtils); } + @Test + void testShouldReturnEmptyListWhenDocumentHashesIsEmpty() { + // Given + final Map savedData = emptyMap(); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(emptyList(), savedData); + + // Then + assertThat(result).isEmpty(); + verifyNoMoreInteractions(documentUtils); + } + + @Test + void testShouldReturnEmptyListWhenNoDocumentsInSavedData() { + // Given + final List documentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + + doReturn(emptySet()).when(documentUtils).findDocumentIds(anyMap()); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(documentHashes, + emptyMap()); + + // Then + assertThat(result).isEmpty(); + verify(documentUtils).findDocumentIds(anyMap()); + } + + @Test + void testShouldReturnOnlyDocumentHashesPresentInSavedData() { + // Given + final List documentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + final Set savedDocumentIds = Set.of(HASH_TOKEN_A1.getId()); + + doReturn(savedDocumentIds).when(documentUtils).findDocumentIds(anyMap()); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(documentHashes, + emptyMap()); + + // Then + assertThat(result) + .hasSize(1) + .containsExactly(HASH_TOKEN_A1); + verify(documentUtils).findDocumentIds(anyMap()); + } + + @Test + void testShouldReturnAllDocumentHashesWhenAllPresentInSavedData() { + // Given + final List documentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + final Set savedDocumentIds = Set.of(HASH_TOKEN_A1.getId(), HASH_TOKEN_A2.getId()); + + doReturn(savedDocumentIds).when(documentUtils).findDocumentIds(anyMap()); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(documentHashes, + emptyMap()); + + // Then + assertThat(result) + .hasSize(2) + .hasSameElementsAs(List.of(HASH_TOKEN_A1, HASH_TOKEN_A2)); + verify(documentUtils).findDocumentIds(anyMap()); + } + + @Test + void testShouldReturnEmptyListWhenNoDocumentHashesMatchSavedData() { + // Given + final List documentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2); + final Set savedDocumentIds = Set.of(HASH_TOKEN_B1.getId(), HASH_TOKEN_B2.getId()); + + doReturn(savedDocumentIds).when(documentUtils).findDocumentIds(anyMap()); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(documentHashes, + emptyMap()); + + // Then + assertThat(result).isEmpty(); + verify(documentUtils).findDocumentIds(anyMap()); + } + + @Test + void testShouldNotCallFindDocumentIdsWhenDocumentHashesDroppedByCallback() { + // Given - document was in event payload but callback dropped it + final List documentHashes = List.of(HASH_TOKEN_A1); + final Set savedDocumentIds = emptySet(); + + doReturn(savedDocumentIds).when(documentUtils).findDocumentIds(anyMap()); + + // When + final List result = underTest.filterDocumentHashesAgainstSavedData(documentHashes, + emptyMap()); + + // Then + assertThat(result).isEmpty(); + verify(documentUtils).findDocumentIds(anyMap()); + verify(documentUtils, never()).findDocumentNodes(anyMap()); + } + + @Test + void testShouldFilterDocumentHashesUsingRealDataTraversal() throws Exception { + // Given + final CaseDocumentUtils realDocumentUtils = new CaseDocumentUtils(); + final CaseDocumentService serviceWithRealUtils = new CaseDocumentService( + caseService, + realDocumentUtils, + applicationParams, + caseDocumentAmApiClient + ); + + final Map savedData = fromFileAsMap("case-data.json"); + + // All document IDs present in the JSON across all nested structures, + // excluding hearingDoc as it contains hearing-recordings in the URL + final String draftOrderDocId = "f1f9a2c2-c309-4060-8f77-1800be0c85a8"; // A_Simple Document + final String extraDoc1Id = "7654b1e0-5df6-47c4-a5a0-3ec79fc78cfb"; // C_1_Collection + final String extraDoc2Id = "f6d623f2-db67-4a01-ae6e-3b6ee14a8b20"; // C_2_Collection + final String timelineDoc1Id = "f8e7f506-e8bd-4886-bd08-d4f70e9c84f6"; // D_1_Collection of Complex + final String timelineDoc2Id = "cdab9cf7-1b38-4245-9c91-e098d28f6404"; // D_2_Collection of Complex + final String evidenceDocId = "84f04693-56ae-4aad-97e8-d1fc7592acea"; // B_Document Inside Complex + final String stateDoc1AId = "5a16b8ed-c62f-41b3-b3c9-1df20b6a9979"; // E_1_AA + final String stateDoc1BId = "19de0db3-37c6-4191-a81d-c31a1379a9ca"; // E_1_BB + final String stateDoc2AId = "f51456a2-7b25-4855-844a-81c9763bc02c"; // E_2_AA + final String stateDoc2BId = "2b01ebbc-d6e5-4ee5-9a80-58b28cb623ec"; // E_2_BB + final String stateDoc3AId = "c6924316-4146-441d-a66d-6e181c48cb09"; // E_3_AA + final String stateDoc3BId = "77ad6295-59ce-4167-8f1e-4aa711ba2c00"; // E_3_BB + final String stateDoc3CId = "80e9471e-0f67-42ef-8739-170aa1942363"; // E_3_CC + final String missingDocId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; // not in saved data + + // Build document hashes - all saved docs plus one that was dropped by callback + final List documentHashes = List.of( + DocumentHashToken.builder().id(draftOrderDocId).hashToken("hash1").build(), + DocumentHashToken.builder().id(extraDoc1Id).hashToken("hash2").build(), + DocumentHashToken.builder().id(extraDoc2Id).hashToken("hash3").build(), + DocumentHashToken.builder().id(timelineDoc1Id).hashToken("hash4").build(), + DocumentHashToken.builder().id(timelineDoc2Id).hashToken("hash5").build(), + DocumentHashToken.builder().id(evidenceDocId).hashToken("hash6").build(), + DocumentHashToken.builder().id(stateDoc1AId).hashToken("hash7").build(), + DocumentHashToken.builder().id(stateDoc1BId).hashToken("hash8").build(), + DocumentHashToken.builder().id(stateDoc2AId).hashToken("hash9").build(), + DocumentHashToken.builder().id(stateDoc2BId).hashToken("hash10").build(), + DocumentHashToken.builder().id(stateDoc3AId).hashToken("hash11").build(), + DocumentHashToken.builder().id(stateDoc3BId).hashToken("hash12").build(), + DocumentHashToken.builder().id(stateDoc3CId).hashToken("hash13").build(), + DocumentHashToken.builder().id(missingDocId).hashToken("hash14").build() + ); + + // When + final List result = serviceWithRealUtils + .filterDocumentHashesAgainstSavedData(documentHashes, savedData); + + assertThat(result) + .hasSize(13) + .extracting(DocumentHashToken::getId) + .containsExactlyInAnyOrder( + draftOrderDocId, + extraDoc1Id, + extraDoc2Id, + timelineDoc1Id, + timelineDoc2Id, + evidenceDocId, + stateDoc1AId, + stateDoc1BId, + stateDoc2AId, + stateDoc2BId, + stateDoc3AId, + stateDoc3BId, + stateDoc3CId + ) + .doesNotContain(missingDocId); + } + + @Test + void testShouldNotAttachHearingRecordingDocumentFromSavedData() throws Exception { + // Given - hearingDoc in saved data should be excluded from traversal entirely + final CaseDocumentUtils realDocumentUtils = new CaseDocumentUtils(); + final CaseDocumentService serviceWithRealUtils = new CaseDocumentService( + caseService, + realDocumentUtils, + applicationParams, + caseDocumentAmApiClient + ); + + final Map savedData = fromFileAsMap("case-data.json"); + + final String hearingDocId = "39a196bf-f105-4e47-840a-90f661b54ed0"; + + final List documentHashes = List.of( + DocumentHashToken.builder().id(hearingDocId).hashToken("hash1").build() + ); + + // When + final List result = serviceWithRealUtils + .filterDocumentHashesAgainstSavedData(documentHashes, savedData); + + assertThat(result).isEmpty(); + } } diff --git a/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtilsTest.java b/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtilsTest.java index 858cdd3e33..dd07b12831 100644 --- a/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtilsTest.java +++ b/src/test/java/uk/gov/hmcts/ccd/domain/service/getcasedocument/CaseDocumentUtilsTest.java @@ -206,4 +206,58 @@ private static Stream provideHashTokenValidationParameters() { Arguments.of(List.of(HASH_TOKEN_B2), List.of(HASH_TOKEN_B2)) ); } + + @Test + void testShouldExtractDocumentIds() throws Exception { + // Given + final Map caseData = fromFileAsMap("case-data.json"); + final Set expectedDocumentIds = Set.of( + "19de0db3-37c6-4191-a81d-c31a1379a9ca", + "77ad6295-59ce-4167-8f1e-4aa711ba2c00", + "80e9471e-0f67-42ef-8739-170aa1942363", + "c6924316-4146-441d-a66d-6e181c48cb09", + "cdab9cf7-1b38-4245-9c91-e098d28f6404", + "f1f9a2c2-c309-4060-8f77-1800be0c85a8", + "2b01ebbc-d6e5-4ee5-9a80-58b28cb623ec", + "7654b1e0-5df6-47c4-a5a0-3ec79fc78cfb", + "f8e7f506-e8bd-4886-bd08-d4f70e9c84f6", + "84f04693-56ae-4aad-97e8-d1fc7592acea", + "f6d623f2-db67-4a01-ae6e-3b6ee14a8b20", + "5a16b8ed-c62f-41b3-b3c9-1df20b6a9979", + "f51456a2-7b25-4855-844a-81c9763bc02c" + ); + + // When + final Set actualDocumentIds = underTest.findDocumentIds(caseData); + + // Then + assertThat(actualDocumentIds) + .isNotNull() + .hasSameElementsAs(expectedDocumentIds); + } + + @Test + @SuppressWarnings({"ConstantConditions"}) + void testShouldRaiseExceptionWhenCaseDataIsNullForDocumentIds() { + // When + final Throwable thrown = catchThrowable(() -> underTest.findDocumentIds(null)); + + // Then + assertThat(thrown) + .isInstanceOf(NullPointerException.class); + } + + @Test + void testShouldReturnEmptySetWhenCaseDataIsEmptyForDocumentIds() { + // Given + final Map emptyCaseData = Map.of(); + + // When + final Set actualDocumentIds = underTest.findDocumentIds(emptyCaseData); + + // Then + assertThat(actualDocumentIds) + .isNotNull() + .isEmpty(); + } } diff --git a/src/test/java/uk/gov/hmcts/ccd/endpoint/std/CaseDetailsEndpointIT.java b/src/test/java/uk/gov/hmcts/ccd/endpoint/std/CaseDetailsEndpointIT.java index da2eb8e71b..dba784d89b 100644 --- a/src/test/java/uk/gov/hmcts/ccd/endpoint/std/CaseDetailsEndpointIT.java +++ b/src/test/java/uk/gov/hmcts/ccd/endpoint/std/CaseDetailsEndpointIT.java @@ -64,7 +64,10 @@ import java.util.Set; import java.util.stream.Collectors; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.google.common.collect.Lists.newArrayList; @@ -74,6 +77,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.in; @@ -91,6 +95,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static uk.gov.hmcts.ccd.data.casedetails.SecurityClassification.PRIVATE; import static uk.gov.hmcts.ccd.data.caselinking.CaseLinkEntity.NON_STANDARD_LINK; @@ -164,6 +169,9 @@ public class CaseDetailsEndpointIT extends WireMockBaseTest { private static final String CASE_LINKS_CASE_998_TYPE = "TestAddressBookCase1"; private static final String CASE_LINKS_CASE_999_TYPE = "TestAddressBookCase2"; + private static final String DOCUMENT_COLLECTION_EVENT_ID = "DOCUMENT_COLLECTION_EVENT"; + private static final String CREATE_DOCUMENT_EVENT_ID = "CREATE_DOCUMENT_EVENT"; + @Inject private WebApplicationContext wac; private MockMvc mockMvc; @@ -447,7 +455,7 @@ public void shouldReturn201WhenPostCreateCaseWithEmptyDataClassificationForCasew Map actualData = mapper.readValue(mapper.readTree(mvcResult.getResponse().getContentAsString()) .get("case_data").toString(), Map.class); - assertTrue("Incorrect Response Content", expectedSanitizedData.entrySet().containsAll(actualData.entrySet())); + assertTrue("Incorrect Response Content", expectedSanitizedData.entrySet().containsAll(actualData.entrySet())); final List caseDetailsList = template.query("SELECT * FROM case_data", this::mapCaseData); assertEquals("Incorrect number of cases", 1, caseDetailsList.size()); @@ -949,7 +957,7 @@ public void shouldReturn201WhenPostCreateCaseWithEmptyDataClassificationForCitiz Map expectedSanitizedData = mapper.readValue(sanitizedData.toString(), Map.class); Map actualData = mapper.readValue(mapper.readTree(mvcResult.getResponse().getContentAsString()) .get("case_data").toString(), Map.class); - assertTrue("Incorrect Response Content", expectedSanitizedData.entrySet().containsAll(actualData.entrySet())); + assertTrue("Incorrect Response Content", expectedSanitizedData.entrySet().containsAll(actualData.entrySet())); final List caseDetailsList = template.query("SELECT * FROM case_data", this::mapCaseData); assertEquals("Incorrect number of cases", 1, caseDetailsList.size()); @@ -3622,10 +3630,10 @@ private void shouldReturn201WithFieldRemovedWhenPostCreateCaseWithNoFieldReadAcc JsonNode dataClassification = mapper.readTree(mvcResult.getResponse().getContentAsString()) .get("data_classification"); Map actualData = mapper.readValue(caseData.toString(), Map.class); - assertAll(() -> + assertAll(() -> assertTrue("Incorrect Response Content", expectedSanitizedData.entrySet() .containsAll(actualData.entrySet())), - () -> assertThat("Response contains filtered out data", + () -> assertThat("Response contains filtered out data", caseData.has("PersonFirstName"), is(false)), () -> assertThat(dataClassification.has("PersonFirstName"), CoreMatchers.is(false)), () -> assertThat(dataClassification.has("PersonLastName"), CoreMatchers.is(true)), @@ -6096,6 +6104,786 @@ public void shouldAddMissingCollectionFieldsBackToDataWhenReadFalseCreateTrueMat ); } + @Test + @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + scripts = {"classpath:sql/insert_cases_document_filter.sql"}) + public void shouldAttachDocumentToCdamWhenCallbackIgnoreDocumentctionField() throws Exception { + final String caseReference = "1504259907353545"; + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases/" + caseReference + "/events"; + + final String newDocumentId = "05e7cd7e-7041-4d8a-826a-7bb49dfd83d0"; + final String newDocumentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + // Event payload: existing item + new item with documentFile and hash token + final String eventData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-002\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + // Callback ignore D8Documents field with both documentIds + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback.*aboutToSubmit.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent() + .withEvent(createEvent(PRE_STATES_EVENT_ID, SUMMARY, DESCRIPTION)) + .withData(JacksonUtils.convertValue(mapper.readTree(eventData))) + .withToken(generateEventToken(template, UID, JURISDICTION, CASE_TYPE, caseReference, PRE_STATES_EVENT_ID)) + .build(); + + // WHEN + mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)); + + // THEN - both collection items including the new document are saved + final List caseDetailsList = template.query( + "SELECT * FROM case_data WHERE reference = " + caseReference, this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(2)); + + boolean newItemSaved = false; + + for (JsonNode item : savedDocuments) { + if ("new-item-id-002".equals(item.get("id").asText()) + && item.path("value") + .path("documentFile") + .path("document_url") + .asText() + .contains(newDocumentId)) { + newItemSaved = true; + break; + } + } + assertThat(newItemSaved, is(true)); + + // CDAM was called with the new document hash + com.github.tomakehurst.wiremock.client.WireMock.verify(1, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newDocumentId + "')]"))); + } + + @Test + @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + scripts = {"classpath:sql/insert_cases_document_filter.sql"}) + public void shouldNotAttachDocumentToCdamWhenCallbackDeleteCollectionItemRemovingDocumentSubfield() + throws Exception { + final String caseReference = "1504259907353545"; + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases/" + caseReference + "/events"; + + final String newDocumentId = "e68c85df-df44-40ae-8c85-dfdf4400ae5a"; + final String newDocumentHash = "430058e3436956981d5a29cde2cbf38faf0d1263fbf382b33a55e8477079464e"; + + final String eventData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-002\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback/aboutToSubmit")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent() + .withEvent(createEvent(DOCUMENT_COLLECTION_EVENT_ID, SUMMARY, DESCRIPTION)) + .withData(JacksonUtils.convertValue(mapper.readTree(eventData))) + .withToken(generateEventToken(template, UID, JURISDICTION, CASE_TYPE, + caseReference, DOCUMENT_COLLECTION_EVENT_ID)) + .build(); + + // WHEN + mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)); + + // THEN - new item is saved but without documentFile because callback stripped it + final List caseDetailsList = template.query( + "SELECT * FROM case_data WHERE reference = " + caseReference, this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(1)); + + boolean newItemFound = false; + for (JsonNode item : savedDocuments) { + if ("new-item-id-002".equals(item.path("id").asText())) { + newItemFound = true; + break; + } + } + + assertThat("Item should not exist in savedDocuments", newItemFound, is(false)); + + com.github.tomakehurst.wiremock.client.WireMock.verify(0, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newDocumentId + "')]"))); + } + + @Test + @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + scripts = {"classpath:sql/insert_cases_document_filter.sql"}) + public void shouldNotAttachDocumentToCdamWhenCallbackReplacesCollectionItemRemovingDocumentSubfield() + throws Exception { + final String caseReference = "1504259907353545"; + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases/" + caseReference + "/events"; + + final String newEventDocumentId = "05e7cd7e-7041-4d8a-826a-7bb49dfd83d0"; + final String newEventDocumentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + final String eventData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-002\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newEventDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newEventDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newEventDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + final String newCallbackDocumentId = "e68c85df-df44-40ae-8c85-dfdf4400ae5a"; + final String newCallbackDocumentHash = "430058e3436956981d5a29cde2cbf38faf0d1263fbf382b33a55e8477079464e"; + + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-003\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + "/documents/" + newCallbackDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + "/documents/" + newCallbackDocumentId + "/binary\"," + + " \"document_hash\": \"" + newCallbackDocumentHash + "\"," + + " \"document_filename\": \"NewDocument.pdf\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback/aboutToSubmit")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent() + .withEvent(createEvent(DOCUMENT_COLLECTION_EVENT_ID, SUMMARY, DESCRIPTION)) + .withData(JacksonUtils.convertValue(mapper.readTree(eventData))) + .withToken(generateEventToken(template, UID, JURISDICTION, CASE_TYPE, + caseReference, DOCUMENT_COLLECTION_EVENT_ID)) + .build(); + + // WHEN + mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)); + + // THEN - new item is saved but without documentFile because callback stripped it + final List caseDetailsList = template.query( + "SELECT * FROM case_data WHERE reference = " + caseReference, this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(2)); + + boolean newItemFound = false; + for (JsonNode item : savedDocuments) { + if ("new-item-id-002".equals(item.path("id").asText())) { + newItemFound = true; + break; + } + } + assertThat("Item should not exist in savedDocuments", newItemFound, is(false)); + + com.github.tomakehurst.wiremock.client.WireMock.verify(0, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newEventDocumentId + "')]"))); + + com.github.tomakehurst.wiremock.client.WireMock.verify(1, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newCallbackDocumentId + "')]"))); + } + + @Test + @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + scripts = {"classpath:sql/insert_cases_document_filter.sql"}) + public void shouldUpdateDocumentCategoryIdAndNotCallCdamForSystemEvent() throws Exception { + final String caseReference = "1504259907353545"; + final String URL = "/documentData/caseref/" + caseReference; + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final String requestBody = "{" + + "\"attribute_path\": \"D8Document\"," + + "\"case_version\": 1," + + "\"category_id\": \"caseDocuments\"" + + "}"; + + // WHEN + mockMvc.perform(put(URL) + .contentType(JSON_CONTENT_TYPE) + .content(requestBody) + ).andExpect(status().is(200)); + + // THEN - categoryId updated on D8Document in saved case data + final List caseDetailsList = template.query( + "SELECT * FROM case_data WHERE reference = " + caseReference, this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + assertThat(savedCase.getData(), hasKey("D8Document")); + final JsonNode savedDocument = mapper.convertValue( + savedCase.getData().get("D8Document"), JsonNode.class); + assertThat(savedDocument.path("category_id").asText(), is("caseDocuments")); + + com.github.tomakehurst.wiremock.client.WireMock.verify(0, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*"))); + } + + @Test + public void shouldAttachDocumentToCdamWhenCallbackRetainsDocumentOnCaseCreation() throws Exception { + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases"; + + final String newDocumentId = "05e7cd7e-7041-4d8a-826a-7bb49dfd83d0"; + final String newDocumentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + final String caseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"new-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + // Callback retains documentFile — nothing dropped + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"new-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback/aboutToSubmit")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent().build(); + caseDataContent.setEvent(createEvent(CREATE_DOCUMENT_EVENT_ID, SUMMARY, DESCRIPTION)); + caseDataContent.setData(JacksonUtils.convertValue(mapper.readTree(caseData))); + caseDataContent.setToken(generateEventTokenNewCase(UID, JURISDICTION, CASE_TYPE, CREATE_DOCUMENT_EVENT_ID)); + + // WHEN + final MvcResult mvcResult = mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)) + .andReturn(); + + final List caseDetailsList = template.query("SELECT * FROM case_data", this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(1)); + + boolean documentSaved = false; + + for (JsonNode item : savedDocuments) { + if ("new-item-id-001".equals(item.path("id").asText()) + && item.path("value") + .path("documentFile") + .path("document_url") + .asText() + .contains(newDocumentId)) { + documentSaved = true; + break; + } + } + assertThat("Item should exist in savedDocuments", documentSaved, is(true)); + + // CDAM was called with the new document hash + com.github.tomakehurst.wiremock.client.WireMock.verify(1, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newDocumentId + "')]"))); + } + + @Test + public void shouldNotAttachDocumentToCdamWhenCallbackStripsDocumentSubfieldOnCaseCreation() throws Exception { + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases"; + + final String newDocumentId = "05e7cd7e-7041-4d8a-826a-7bb49dfd83d0"; + final String newDocumentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + final String caseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"new-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + final String newCallbackDocumentId = "e68c85df-df44-40ae-8c85-dfdf4400ae5a"; + final String newCallbackDocumentHash = "430058e3436956981d5a29cde2cbf38faf0d1263fbf382b33a55e8477079464e"; + // Callback strips documentFile from the collection item + // This is the bug scenario on case creation — callback rebuilds the collection + // item without carrying forward documentFile + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"new-item-id-002\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newCallbackDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newCallbackDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newCallbackDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback/aboutToSubmit")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent().build(); + caseDataContent.setEvent(createEvent(CREATE_DOCUMENT_EVENT_ID, SUMMARY, DESCRIPTION)); + caseDataContent.setData(JacksonUtils.convertValue(mapper.readTree(caseData))); + caseDataContent.setToken(generateEventTokenNewCase(UID, JURISDICTION, CASE_TYPE, CREATE_DOCUMENT_EVENT_ID)); + + // WHEN + mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)); + + final List caseDetailsList = template.query("SELECT * FROM case_data", this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(1)); + + boolean documentSaved = false; + + for (JsonNode item : savedDocuments) { + if ("new-item-id-001".equals(item.path("id").asText()) + && item.path("value") + .path("documentFile") + .path("document_url") + .asText() + .contains(newDocumentId)) { + documentSaved = true; + break; + } + } + assertThat("Item should not exist in savedDocuments", documentSaved, is(false)); + + boolean newItemFound = false; + for (JsonNode item : savedDocuments) { + if ("new-item-id-002".equals(item.path("id").asText())) { + newItemFound = true; + break; + } + } + + assertThat("Item should exist in savedDocuments", newItemFound, is(true)); + + com.github.tomakehurst.wiremock.client.WireMock.verify(0, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newDocumentId + "')]"))); + + com.github.tomakehurst.wiremock.client.WireMock.verify(1, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newCallbackDocumentId + "')]"))); + } + + @Test + public void shouldNotAttachDocumentToCdamWhenCallbackReplaceDocumentSubfieldOnCaseCreation() throws Exception { + final String URL = "/caseworkers/" + UID + "/jurisdictions/" + JURISDICTION + + "/case-types/" + CASE_TYPE + "/cases"; + + final String newCaseDocumentId = "05e7cd7e-7041-4d8a-826a-7bb49dfd83d0"; + final String newEventDocumentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + final String caseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-002\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newCaseDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + + "/documents/" + newCaseDocumentId + "/binary\"," + + " \"document_filename\": \"NewDocument.pdf\"," + + " \"document_hash\": \"" + newEventDocumentHash + "\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + final String newCallbackDocumentId = "e68c85df-df44-40ae-8c85-dfdf4400ae5a"; + final String newCallbackDocumentHash = "430058e3436956981d5a29cde2cbf38faf0d1263fbf382b33a55e8477079464e"; + + final String callbackResponseData = "{" + + "\"PersonFirstName\": \"George\"," + + "\"PersonLastName\": \"Roof\"," + + "\"D8Documents\": [" + + " {" + + " \"id\": \"existing-item-id-001\"," + + " \"value\": {" + + " \"documentType\": \"EXISTING_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + "/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary\"," + + " \"document_filename\": \"ExistingDocument.pdf\"" + + " }" + + " }" + + " }," + + " {" + + " \"id\": \"new-item-id-003\"," + + " \"value\": {" + + " \"documentType\": \"NEW_TYPE\"," + + " \"documentFile\": {" + + " \"document_url\": \"http://localhost:" + wiremockPort + "/documents/" + newCallbackDocumentId + "\"," + + " \"document_binary_url\": \"http://localhost:" + wiremockPort + "/documents/" + newCallbackDocumentId + "/binary\"," + + " \"document_hash\": \"" + newCallbackDocumentHash + "\"," + + " \"document_filename\": \"NewDocument.pdf\"" + + " }" + + " }" + + " }" + + "]" + + "}"; + + stubFor(WireMock.post(urlMatching("/callback/aboutToSubmit")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"data\": " + callbackResponseData + "}"))); + + stubFor(WireMock.patch(urlMatching("/cases/documents/attachToCase.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{}"))); + + final CaseDataContent caseDataContent = newCaseDataContent().build(); + caseDataContent.setEvent(createEvent(CREATE_DOCUMENT_EVENT_ID, SUMMARY, DESCRIPTION)); + caseDataContent.setData(JacksonUtils.convertValue(mapper.readTree(caseData))); + caseDataContent.setToken(generateEventTokenNewCase(UID, JURISDICTION, CASE_TYPE, CREATE_DOCUMENT_EVENT_ID)); + + // WHEN + mockMvc.perform(post(URL) + .contentType(JSON_CONTENT_TYPE) + .content(mapper.writeValueAsBytes(caseDataContent)) + ).andExpect(status().is(201)); + + final List caseDetailsList = template.query("SELECT * FROM case_data", this::mapCaseData); + assertThat(caseDetailsList, hasSize(1)); + final CaseDetails savedCase = caseDetailsList.get(0); + + final JsonNode savedDocuments = mapper.convertValue(savedCase.getData().get("D8Documents"), JsonNode.class); + assertThat(savedDocuments.isArray(), is(true)); + assertThat(savedDocuments.size(), is(2)); + + boolean documentSaved = false; + + for (JsonNode item : savedDocuments) { + if ("existing-item-id-001".equals(item.path("id").asText()) + && item.path("value") + .path("documentFile") + .path("document_url") + .asText() + .contains("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")) { + documentSaved = true; + break; + } + } + assertThat("Item should exist in savedDocuments", documentSaved, is(true)); + + for (JsonNode item : savedDocuments) { + if ("new-item-id-003".equals(item.path("id").asText()) + && item.path("value") + .path("documentFile") + .path("document_url") + .asText() + .contains(newCallbackDocumentId)) { + documentSaved = true; + break; + } + } + assertThat("Item should exist in savedDocuments", documentSaved, is(true)); + + boolean newItemFound = false; + for (JsonNode item : savedDocuments) { + if ("new-item-id-002".equals(item.path("id").asText())) { + newItemFound = true; + break; + } + } + + assertThat("Item should exist in savedDocuments", newItemFound, is(false)); + + com.github.tomakehurst.wiremock.client.WireMock.verify(0, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newCaseDocumentId + "')]"))); + + com.github.tomakehurst.wiremock.client.WireMock.verify(1, + patchRequestedFor(urlMatching("/cases/documents/attachToCase.*")) + .withRequestBody(matchingJsonPath( + "$.documentHashTokens[?(@.id == '" + newCallbackDocumentId + "')]"))); + } + private String createExampleEventDataWithMissingItems() { return "{" + "\"PersonAddress\":{" diff --git a/src/test/resources/mappings/bookcase-definition.json b/src/test/resources/mappings/bookcase-definition.json index 486085ecae..3dd1328359 100644 --- a/src/test/resources/mappings/bookcase-definition.json +++ b/src/test/resources/mappings/bookcase-definition.json @@ -22,6 +22,13 @@ "description": "Test Jurisdiction" }, "security_classification": "PUBLIC", + "categories": [ + { + "category_id": "caseDocuments", + "category_label": "Case Documents", + "parent_category_id": null + } + ], "acls": [ { "role": "caseworker-probate-public", @@ -179,9 +186,9 @@ "CaseCreated" ], "post_states": [{ - "enabling_condition" : null, - "priority" : 99, - "post_state_reference" : "state4" + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state4" }], "security_classification": "PRIVATE", "acls": [ @@ -195,6 +202,46 @@ "show_event_notes": false, "can_save_draft": false }, + { + "id": "DOCUMENT_COLLECTION_EVENT", + "name": "DOCUMENT COLLECTION EVENT", + "description": "Test event for document collection callback filter", + "order": 2, + "case_fields": [ + { + "case_field_id": "PersonFirstName", + "display_context": "OPTIONAL" + }, + { + "case_field_id": "PersonLastName", + "display_context": "OPTIONAL" + }, + { + "case_field_id": "D8Documents", + "display_context": "OPTIONAL" + } + ], + "pre_states": [ + "CaseCreated" + ], + "post_states": [{ + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state4" + }], + "security_classification": "PUBLIC", + "acls": [ + { + "role": "caseworker-probate-public", + "create": true, + "read": true, + "update": true, + "delete": false + }], + "callback_url_about_to_submit_event": "http://localhost:{{request.requestLine.port}}/callback/aboutToSubmit", + "show_event_notes": false, + "can_save_draft": false + }, { "id": "NO_PRE_STATES_EVENT", "name": "NO PRE STATES EVENT", @@ -210,12 +257,11 @@ "display_context": "OPTIONAL" } ], - "pre_states": [ - ], + "pre_states": [], "post_states": [{ - "enabling_condition" : null, - "priority" : 99, - "post_state_reference" : "state4" + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state4" }], "security_classification": "PRIVATE", "acls": [ @@ -233,14 +279,12 @@ "id": "TEST_EVENT", "name": "TEST EVENT NAME", "description": "Just a test", - "case_fields": [ - ], - "pre_states": [ - ], + "case_fields": [], + "pre_states": [], "post_states": [{ - "enabling_condition" : null, - "priority" : 99, - "post_state_reference" : "state3" + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state3" }], "security_classification": "PRIVATE", "acls": [ @@ -265,9 +309,9 @@ "CaseCreated" ], "post_states": [{ - "enabling_condition" : null, - "priority" : 99, - "post_state_reference" : "state4" + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state4" }], "security_classification": "PRIVATE", "acls": [ @@ -289,9 +333,9 @@ "case_fields": [], "pre_states": [], "post_states": [{ - "enabling_condition" : null, - "priority" : 99, - "post_state_reference" : "state4" + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state4" }], "security_classification": "PRIVATE", "acls": [ @@ -304,6 +348,44 @@ }], "show_event_notes": true, "can_save_draft": true + }, + { + "id": "CREATE_DOCUMENT_EVENT", + "name": "CREATE DOCUMENT EVENT", + "description": "Test event for document collection on case creation", + "order": 5, + "case_fields": [ + { + "case_field_id": "PersonFirstName", + "display_context": "OPTIONAL" + }, + { + "case_field_id": "PersonLastName", + "display_context": "OPTIONAL" + }, + { + "case_field_id": "D8Documents", + "display_context": "OPTIONAL" + } + ], + "pre_states": [], + "post_states": [{ + "enabling_condition": null, + "priority": 99, + "post_state_reference": "state3" + }], + "security_classification": "PUBLIC", + "acls": [ + { + "role": "caseworker-probate-public", + "create": true, + "read": true, + "update": true, + "delete": false + }], + "callback_url_about_to_submit_event": "http://localhost:{{request.requestLine.port}}/callback/aboutToSubmit", + "show_event_notes": false, + "can_save_draft": false } ], "states": [ @@ -605,8 +687,59 @@ "type": "Document", "id": "Document" } + }, + { + "id": "D8Documents", + "case_type_id": "TestAddressBookCase", + "label": "Documents Collection", + "security_classification": "PUBLIC", + "acls": [ + { + "role": "caseworker-probate-public", + "create": true, + "read": true, + "update": true, + "delete": false + }, + { + "role": "caseworker-probate-private", + "create": true, + "read": true, + "update": true, + "delete": false + } + ], + "field_type": { + "type": "Collection", + "id": "D8DocumentsCollection", + "collection_field_type": { + "type": "Complex", + "id": "D8DocumentItem", + "complex_fields": [ + { + "id": "documentType", + "label": "Document Type", + "security_classification": "PUBLIC", + "field_type": { + "id": "Text", + "type": "Text" + } + }, + { + "id": "documentFile", + "label": "Document File", + "security_classification": "PUBLIC", + "field_type": { + "id": "Document", + "type": "Document" + } + } + ] + } + } } ] - } + }, + "transformers": ["response-template"] } } diff --git a/src/test/resources/sql/insert_cases_document_filter.sql b/src/test/resources/sql/insert_cases_document_filter.sql new file mode 100644 index 0000000000..8d5ffdce97 --- /dev/null +++ b/src/test/resources/sql/insert_cases_document_filter.sql @@ -0,0 +1,100 @@ +DELETE FROM case_event; +DELETE FROM case_data; + +INSERT INTO case_data (id, case_type_id, jurisdiction, state, security_classification, data, data_classification, reference, created_date, last_modified, last_state_modified_date) +VALUES (2, 'TestAddressBookCase', 'PROBATE', 'CaseCreated', 'PUBLIC', + '{ + "PersonFirstName": "George", + "PersonLastName": "Roof", + "PersonAddress": { + "AddressLine1": "Flat 9", + "AddressLine2": "2 Hubble Avenue", + "AddressLine3": "ButtonVillie", + "Country": "Wales", + "Postcode": "W11 5DF" + }, + "D8Document": { + "document_url": "http://localhost:[port]/documents/fa99ac30-d6e4-4cd8-99ac-30d6e4dcd849", + "document_binary_url": "http://localhost:[port]/documents/fa99ac30-d6e4-4cd8-99ac-30d6e4dcd849/binary", + "document_filename": "ExistingDocument.pdf" + }, + "D8Documents": [ + { + "id": "existing-item-id-001", + "value": { + "documentType": "EXISTING_TYPE", + "documentFile": { + "document_url": "http://localhost:[port]/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "document_binary_url": "http://localhost:[port]/documents/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/binary", + "document_filename": "ExistingDocument.pdf" + } + } + } + ] + }', + '{ + "PersonFirstName": "PUBLIC", + "PersonLastName": "PUBLIC", + "PersonAddress": { + "classification": "PUBLIC", + "value": { + "AddressLine1": "PUBLIC", + "AddressLine2": "PUBLIC", + "AddressLine3": "PUBLIC", + "Country": "PUBLIC", + "Postcode": "PUBLIC" + } + }, + "D8Document": "PUBLIC", + "D8Documents": { + "classification": "PUBLIC", + "value": { + "existing-item-id-001": { + "classification": "PUBLIC", + "value": { + "documentType": "PUBLIC", + "documentFile": "PUBLIC" + } + } + } + } + }', + '1504259907353545', + '2016-08-22 20:44:52.824', + '2016-08-24 20:44:52.824', + '2016-08-24 20:44:52.824' + ); + +INSERT INTO case_event ( + case_data_id, + case_type_id, + case_type_version, + description, + summary, + event_id, + event_name, + user_id, + user_first_name, + user_last_name, + state_id, + state_name, + security_classification, + created_date, + data +) VALUES ( + 2, + 'TestAddressBookCase', + 1, + 'Initial event', + 'Initial summary', + 'TEST_EVENT', + 'TEST EVENT NAME', + 0, + 'Cloud', + 'Strife', + 'CaseCreated', + 'Case Created', + 'PUBLIC', + '2017-05-09 14:31:43.000000', + '{}' + );