Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,14 @@ public CaseDetails submitCase(Event event,
);
caseDataAccessControl.grantAccess(savedCaseDetails, idamUser.getId());

final List<DocumentHashToken> verifiedDocumentHashes = caseDocumentService
.filterDocumentHashesAgainstSavedData(documentHashes, savedCaseDetails.getData());

caseDocumentService.attachCaseDocuments(
caseDetails.getReferenceAsString(),
caseDetails.getCaseTypeId(),
caseDetails.getJurisdiction(),
documentHashes
verifiedDocumentHashes
);
}

Expand All @@ -200,11 +203,14 @@ private CaseDetails submitDecentralisedCase(Event event, CaseTypeDefinition case

caseDataAccessControl.grantAccess(newCaseDetails, idamUser.getId());

final List<DocumentHashToken> verifiedDocumentHashes = caseDocumentService
.filterDocumentHashesAgainstSavedData(documentHashes, newCaseDetails.getData());

caseDocumentService.attachCaseDocuments(
newCaseDetails.getReferenceAsString(),
newCaseDetails.getCaseTypeId(),
newCaseDetails.getJurisdiction(),
documentHashes
verifiedDocumentHashes
);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DocumentHashToken> 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);
Expand All @@ -297,6 +301,10 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C
} else {
finalCaseDetails = saveCaseDetails(caseDetailsInDatabase, caseDetailsAfterCallbackWithoutHashes,
caseEventDefinition, newState, timeNow);

final List<DocumentHashToken> verifiedDocumentHashes = caseDocumentService
.filterDocumentHashesAgainstSavedData(documentHashes, finalCaseDetails.getData());

saveAuditEventForCaseDetails(
aboutToSubmitCallbackResponse,
content.getEvent(),
Expand All @@ -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());
Expand All @@ -318,9 +325,8 @@ public CreateCaseEventResult createCaseEvent(final String caseReference, final C
caseDetails.getReferenceAsString(),
caseDetails.getCaseTypeId(),
caseDetails.getJurisdiction(),
documentHashes
verifiedDocumentHashes
);

}

return CreateCaseEventResult.caseEventWith()
Expand Down Expand Up @@ -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<DocumentHashToken> verifiedDocumentHashes = caseDocumentService
.filterDocumentHashesAgainstSavedData(documentHashes, caseDetailsAfterCallbackWithoutHashes.getData());

caseDocumentService.attachCaseDocuments(
caseDetails.getReferenceAsString(),
caseDetails.getCaseTypeId(),
caseDetails.getJurisdiction(),
documentHashes
verifiedDocumentHashes
);

finalCaseDetails = decentralisedCreateCaseEventService.submitDecentralisedEvent(event, caseEventDefinition,
Expand All @@ -407,6 +416,10 @@ public CreateCaseEventResult createCaseSystemEvent(final String caseReference,
} else {
finalCaseDetails = saveCaseDetails(caseDetailsInDatabase,
caseDetailsAfterCallbackWithoutHashes, caseEventDefinition, newState, timeNow);

final List<DocumentHashToken> verifiedDocumentHashes = caseDocumentService
.filterDocumentHashesAgainstSavedData(documentHashes, finalCaseDetails.getData());

saveAuditEventForCaseDetails(
aboutToSubmitCallbackResponse,
event,
Expand All @@ -425,7 +438,7 @@ public CreateCaseEventResult createCaseSystemEvent(final String caseReference,
caseDetails.getReferenceAsString(),
caseDetails.getCaseTypeId(),
caseDetails.getJurisdiction(),
documentHashes
verifiedDocumentHashes
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,4 +141,22 @@ private void verifyNoTamper(final List<Tuple2<String, String>> preCallbackHashes
}
}

public List<DocumentHashToken> filterDocumentHashesAgainstSavedData(
final List<DocumentHashToken> documentHashes,
final Map<String, JsonNode> savedData) {

if (documentHashes.isEmpty()) {
return documentHashes;
}

final Set<String> savedDocumentIds = caseDocumentUtils.findDocumentIds(savedData);

if (savedDocumentIds.isEmpty()) {
return Collections.emptyList();
}

return documentHashes.stream()
.filter(hash -> savedDocumentIds.contains(hash.getId()))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,9 @@ public List<DocumentHashToken> getViolatingDocuments(@NonNull final List<Documen
.collect(Collectors.toUnmodifiableList());
}

public Set<String> findDocumentIds(final Map<String, JsonNode> data) {
return findDocumentNodes(data).stream()
.map(this::getDocumentId)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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<DocumentHashToken> allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2);
final List<DocumentHashToken> 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<DocumentHashToken> 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<DocumentHashToken> 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<DocumentHashToken> allDocumentHashes = List.of(HASH_TOKEN_A1, HASH_TOKEN_A2);
final List<DocumentHashToken> 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<String, JsonNode> 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<DocumentHashToken> allDocumentHashes = List.of(HASH_TOKEN_A1);
final List<DocumentHashToken> 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<DocumentHashToken> 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);
Expand Down
Loading