diff --git a/src/integrationTest/java/uk/gov/hmcts/darts/task/runner/impl/CaseExpiryDeletionAutomatedTaskITest.java b/src/integrationTest/java/uk/gov/hmcts/darts/task/runner/impl/CaseExpiryDeletionAutomatedTaskITest.java index ba45bf1726c..31c240794f8 100644 --- a/src/integrationTest/java/uk/gov/hmcts/darts/task/runner/impl/CaseExpiryDeletionAutomatedTaskITest.java +++ b/src/integrationTest/java/uk/gov/hmcts/darts/task/runner/impl/CaseExpiryDeletionAutomatedTaskITest.java @@ -12,6 +12,7 @@ import uk.gov.hmcts.darts.common.entity.DefenceEntity; import uk.gov.hmcts.darts.common.entity.DefendantEntity; import uk.gov.hmcts.darts.common.entity.EventEntity; +import uk.gov.hmcts.darts.common.entity.EventLinkedCaseEntity; import uk.gov.hmcts.darts.common.entity.HearingEntity; import uk.gov.hmcts.darts.common.entity.ProsecutorEntity; import uk.gov.hmcts.darts.common.entity.TranscriptionCommentEntity; @@ -37,7 +38,11 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; @@ -66,6 +71,38 @@ void positiveRetentionDatePassed() { assertCase(caseId1, true); } + @Test + @DisplayName("Two cases linked to the same event, one case has passed retention date, the other has not. Event should not be anonymised") + void retentionDatePassedForOneCaseButNotAnotherEventNotAnoymised() { + CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE); + CourtCaseEntity courtCase2 = createCase(-1, CaseRetentionStatus.PENDING); + + EventEntity event = dartsDatabase.getEventLinkedCaseRepository().findAllByCourtCase(courtCase1).getFirst().getEvent(); + eventLinkedCaseStub.createCaseLinkedEvent(event, courtCase2); + final int caseId1 = courtCase1.getId(); + + caseExpiryDeletionAutomatedTask.preRunTask(); + caseExpiryDeletionAutomatedTask.runTask(); + + assertCase(caseId1, true, event.getId()); + } + + @Test + @DisplayName("Two cases linked to the same event, both cases have passed retention date. Event should be anonymised") + void retentionDatePassedForBothCaseLinkedEventsAnoymised() { + CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE); + CourtCaseEntity courtCase2 = createCase(-1, CaseRetentionStatus.COMPLETE); + + EventEntity event = dartsDatabase.getEventLinkedCaseRepository().findAllByCourtCase(courtCase1).getFirst().getEvent(); + eventLinkedCaseStub.createCaseLinkedEvent(event, courtCase2); + final int caseId1 = courtCase1.getId(); + + caseExpiryDeletionAutomatedTask.preRunTask(); + caseExpiryDeletionAutomatedTask.runTask(); + + assertCase(caseId1, true); + } + @Test void positiveRetentionDateNotPassed() { @@ -111,7 +148,7 @@ void positiveMultipleToAnonymiseAndSomeNotTo() { } - private void assertCase(int caseId, boolean isAnonymised) { + private void assertCase(int caseId, boolean isAnonymised, int... excludeEventIds) { transactionalUtil.executeInTransaction(() -> { CourtCaseEntity courtCase = dartsDatabase.getCourtCaseStub().getCourtCase(caseId); assertThat(courtCase.isDataAnonymised()) @@ -140,7 +177,15 @@ private void assertCase(int caseId, boolean isAnonymised) { courtCase.getHearings().forEach(hearingEntity -> assertHearing(hearingEntity, isAnonymised)); - dartsDatabase.getEventRepository().findAllByCaseId(caseId).forEach(eventEntity -> assertEvent(eventEntity, isAnonymised)); + Set eventIdsToExclude = new HashSet<>(); + Arrays.stream(excludeEventIds).forEach(eventIdsToExclude::add); + List eventLinkedCaseEntities = new ArrayList<>(); + eventLinkedCaseEntities.addAll(dartsDatabase.getEventLinkedCaseRepository().findAllByCourtCase(courtCase)); + eventLinkedCaseEntities.addAll(dartsDatabase.getEventLinkedCaseRepository().findAllByCaseNumberAndCourthouseName( + courtCase.getCaseNumber(), + courtCase.getCourthouse().getCourthouseName())); + eventLinkedCaseEntities.stream().map(EventLinkedCaseEntity::getEvent) + .forEach(eventEntity -> assertEvent(eventEntity, !eventIdsToExclude.contains(eventEntity.getId()) && isAnonymised)); assertAuditEntries(courtCase, isAnonymised); }); diff --git a/src/integrationTest/java/uk/gov/hmcts/darts/testutils/PostgresIntegrationBase.java b/src/integrationTest/java/uk/gov/hmcts/darts/testutils/PostgresIntegrationBase.java index 7989b436d01..f536160aa53 100644 --- a/src/integrationTest/java/uk/gov/hmcts/darts/testutils/PostgresIntegrationBase.java +++ b/src/integrationTest/java/uk/gov/hmcts/darts/testutils/PostgresIntegrationBase.java @@ -42,11 +42,15 @@ public class PostgresIntegrationBase { */ private static final int SERVER_MAX_CONNECTIONS = 50; - private static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer<>( - "postgres:16-alpine" - ).withDatabaseName("darts") - .withUsername("darts") - .withPassword("darts"); + private static final PostgreSQLContainer POSTGRES; + + static { + POSTGRES = new PostgreSQLContainer<>( + "postgres:16-alpine" + ).withDatabaseName("darts") + .withUsername("darts") + .withPassword("darts"); + } @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { @@ -73,4 +77,4 @@ void clearDb() { void clearTestData() { logAppender.reset(); } -} \ No newline at end of file +} diff --git a/src/main/java/uk/gov/hmcts/darts/common/repository/EventLinkedCaseRepository.java b/src/main/java/uk/gov/hmcts/darts/common/repository/EventLinkedCaseRepository.java index a49940c43c2..9e3f72a870b 100644 --- a/src/main/java/uk/gov/hmcts/darts/common/repository/EventLinkedCaseRepository.java +++ b/src/main/java/uk/gov/hmcts/darts/common/repository/EventLinkedCaseRepository.java @@ -1,8 +1,10 @@ package uk.gov.hmcts.darts.common.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import uk.gov.hmcts.darts.common.entity.CourtCaseEntity; +import uk.gov.hmcts.darts.common.entity.EventEntity; import uk.gov.hmcts.darts.common.entity.EventLinkedCaseEntity; import java.util.List; @@ -13,4 +15,16 @@ public interface EventLinkedCaseRepository extends JpaRepository findAllByCourtCase(CourtCaseEntity courtCase); List findAllByCaseNumberAndCourthouseName(String caseNumber, String courthouseName); + + @Query(""" + SELECT COUNT(DISTINCT cc) = (COUNT(cc.isDataAnonymised) filter (where cc.isDataAnonymised = true)) + FROM EventLinkedCaseEntity elc + LEFT JOIN CourtCaseEntity cc + ON (elc.courtCase = cc + or (cc.courthouse.courthouseName = elc.courthouseName and cc.caseNumber = elc.caseNumber)) + WHERE elc.event = :eventEntity + group by elc.event + """ + ) + boolean areAllAssociatedCasesAnonymised(EventEntity eventEntity); } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImpl.java b/src/main/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImpl.java index 309627cde8c..5805b575a57 100644 --- a/src/main/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImpl.java @@ -65,14 +65,13 @@ void anonymiseCourtCaseEntity(UserAccountEntity userAccount, CourtCaseEntity cou courtCase.getDefenceList().forEach(defenceEntity -> anonymiseDefenceEntity(userAccount, defenceEntity)); courtCase.getProsecutorList().forEach(prosecutorEntity -> anonymiseProsecutorEntity(userAccount, prosecutorEntity)); courtCase.getHearings().forEach(hearingEntity -> anonymiseHearingEntity(userAccount, hearingEntity)); - anonymiseAllEventsFromCase(userAccount, courtCase); - courtCase.markAsExpired(userAccount); //This also saves defendant, defence and prosecutor entities tidyUpTransformedMediaEntities(userAccount, courtCase); auditApi.record(AuditActivity.CASE_EXPIRED, userAccount, courtCase); anonymiseCreatedModifiedBaseEntity(userAccount, courtCase); caseService.saveCase(courtCase); + anonymiseAllEventsFromCase(userAccount, courtCase); //Required for Dynatrace dashboards logApi.caseDeletedDueToExpiry(courtCase.getId(), courtCase.getCaseNumber()); @@ -108,16 +107,24 @@ public void anonymiseEventByIds(UserAccountEntity userAccount, List eve @Override public void anonymiseEvent(UserAccountEntity userAccount, EventEntity eventEntity) { - anonymiseEventEntity(userAccount, eventEntity); - eventService.saveEvent(eventEntity); + anonymiseEventEntity(userAccount, eventEntity, false); auditApi.record(AuditActivity.MANUAL_OBFUSCATION, userAccount, eventEntity.getId().toString()); logApi.manualObfuscation(eventEntity); } - void anonymiseEventEntity(UserAccountEntity userAccount, EventEntity eventEntity) { + void anonymiseEventEntity(UserAccountEntity userAccount, EventEntity eventEntity, boolean onlyAnonymiseIfAllCasesExpired) { + if (eventEntity.isDataAnonymised()) { + log.debug("Event {} already anonymised skipping", eventEntity.getId()); + return; + } + if (onlyAnonymiseIfAllCasesExpired && !eventService.allAssociatedCasesAnonymised(eventEntity)) { + log.debug("Event {} not anonymised as not all cases are expired", eventEntity.getId()); + return; + } eventEntity.setEventText(UUID.randomUUID().toString()); eventEntity.setDataAnonymised(true); anonymiseCreatedModifiedBaseEntity(userAccount, eventEntity); + eventService.saveEvent(eventEntity); } void anonymiseTranscriptionEntity(UserAccountEntity userAccount, TranscriptionEntity transcriptionEntity) { @@ -127,6 +134,10 @@ void anonymiseTranscriptionEntity(UserAccountEntity userAccount, TranscriptionEn } void anonymiseTranscriptionCommentEntity(UserAccountEntity userAccount, TranscriptionCommentEntity transcriptionCommentEntity) { + if (transcriptionCommentEntity.isDataAnonymised()) { + log.debug("Transcription comment {} already anonymised skipping", transcriptionCommentEntity.getId()); + return; + } transcriptionCommentEntity.setComment(UUID.randomUUID().toString()); transcriptionCommentEntity.setDataAnonymised(true); anonymiseCreatedModifiedBaseEntity(userAccount, transcriptionCommentEntity); @@ -134,7 +145,6 @@ void anonymiseTranscriptionCommentEntity(UserAccountEntity userAccount, Transcri void anonymiseTranscriptionWorkflowEntity(TranscriptionWorkflowEntity transcriptionWorkflowEntity) { transcriptionWorkflowEntity.close(); - } void tidyUpTransformedMediaEntities(UserAccountEntity userAccount, CourtCaseEntity courtCase) { @@ -190,6 +200,6 @@ private void anonymiseCreatedModifiedBaseEntity(UserAccountEntity userAccount, C } private void anonymiseAllEventsFromCase(UserAccountEntity userAccount, CourtCaseEntity courtCase) { - eventService.getAllCourtCaseEventVersions(courtCase).forEach(eventEntity -> anonymiseEventEntity(userAccount, eventEntity)); + eventService.getAllCourtCaseEventVersions(courtCase).forEach(eventEntity -> anonymiseEventEntity(userAccount, eventEntity, true)); } } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/darts/event/service/EventService.java b/src/main/java/uk/gov/hmcts/darts/event/service/EventService.java index 2d963cebcae..5d9138e58c7 100644 --- a/src/main/java/uk/gov/hmcts/darts/event/service/EventService.java +++ b/src/main/java/uk/gov/hmcts/darts/event/service/EventService.java @@ -14,4 +14,6 @@ public interface EventService { EventEntity saveEvent(EventEntity eventEntity); Set getAllCourtCaseEventVersions(CourtCaseEntity courtCase); + + boolean allAssociatedCasesAnonymised(EventEntity eventEntity); } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImpl.java b/src/main/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImpl.java index 663e8333284..17813d354fa 100644 --- a/src/main/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImpl.java +++ b/src/main/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImpl.java @@ -64,4 +64,8 @@ public Set getAllCourtCaseEventVersions(CourtCaseEntity courtCase) return allEvents; } + @Override + public boolean allAssociatedCasesAnonymised(EventEntity eventEntity) { + return eventLinkedCaseRepository.areAllAssociatedCasesAnonymised(eventEntity); + } } \ No newline at end of file diff --git a/src/test/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImplTest.java b/src/test/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImplTest.java index cfd56040c1d..76c7e5a8dc8 100644 --- a/src/test/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/darts/common/service/impl/DataAnonymisationServiceImplTest.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.darts.common.service.impl; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -87,16 +88,64 @@ private void assertLastModifiedByAndAt(CreatedModifiedBaseEntity entity, UserAcc @Test - void positiveEventEntityAnonymise() { + @DisplayName("Event should not be anonymised if one or more assocaited cases are not anonymised") + void eventEntityAnonymiseNotUpdatedAsNotAllCasesExpiredAndOnlyAnonymiseIfAllCasesExpiredIsTrue() { + EventEntity eventEntity = new EventEntity(); + eventEntity.setEventText("event text"); + + UserAccountEntity userAccount = new UserAccountEntity(); + when(eventService.allAssociatedCasesAnonymised(eventEntity)).thenReturn(false); + dataAnonymisationService.anonymiseEventEntity(userAccount, eventEntity, true); + assertThat(eventEntity.getEventText()).isEqualTo("event text"); + assertThat(eventEntity.isDataAnonymised()).isFalse(); + verify(eventService).allAssociatedCasesAnonymised(eventEntity); + verify(eventService, never()).saveEvent(eventEntity); + } + + @Test + void positiveEventEntityAnonymiseUpdatedAsAllCasesExpiredAndOnlyAnonymiseIfAllCasesExpiredIsTrue() { setupOffsetDateTime(); EventEntity eventEntity = new EventEntity(); eventEntity.setEventText("event text"); UserAccountEntity userAccount = new UserAccountEntity(); - dataAnonymisationService.anonymiseEventEntity(userAccount, eventEntity); + when(eventService.allAssociatedCasesAnonymised(eventEntity)).thenReturn(true); + dataAnonymisationService.anonymiseEventEntity(userAccount, eventEntity, true); assertThat(eventEntity.getEventText()).matches(TestUtils.UUID_REGEX); assertThat(eventEntity.isDataAnonymised()).isTrue(); assertLastModifiedByAndAt(eventEntity, userAccount); + verify(eventService).allAssociatedCasesAnonymised(eventEntity); + verify(eventService).saveEvent(eventEntity); + } + + @Test + @DisplayName("Event should be anonymised if one or more assocaited cases are not anonymised and the onlyAnonymiseIfAllCasesExpired flag is false") + void eventEntityAnonymiseUpdatedAsNotAllCasesExpiredAndOnlyAnonymiseIfAllCasesExpiredIsFalse() { + setupOffsetDateTime(); + EventEntity eventEntity = new EventEntity(); + eventEntity.setEventText("event text"); + + UserAccountEntity userAccount = new UserAccountEntity(); + dataAnonymisationService.anonymiseEventEntity(userAccount, eventEntity, false); + assertThat(eventEntity.getEventText()).matches(TestUtils.UUID_REGEX); + assertThat(eventEntity.isDataAnonymised()).isTrue(); + assertLastModifiedByAndAt(eventEntity, userAccount); + verify(eventService).saveEvent(eventEntity); + } + + @Test + @DisplayName("Event should not be anonymised again if the event is already anonymised") + void eventEntityNotUpdatedAsAlreadyAnonymised() { + EventEntity eventEntity = new EventEntity(); + eventEntity.setEventText("event text"); + eventEntity.setDataAnonymised(true); + + UserAccountEntity userAccount = new UserAccountEntity(); + dataAnonymisationService.anonymiseEventEntity(userAccount, eventEntity, false); + assertThat(eventEntity.getEventText()).isEqualTo("event text"); + assertThat(eventEntity.isDataAnonymised()).isTrue(); + verifyNoMoreInteractions(eventService); + verify(eventService, never()).saveEvent(eventEntity); } @Test @@ -201,8 +250,8 @@ void assertPositiveAnonymiseCourtCaseEntity() { verify(dataAnonymisationService, times(1)).anonymiseHearingEntity(userAccount, hearingEntity1); verify(dataAnonymisationService, times(1)).anonymiseHearingEntity(userAccount, hearingEntity2); - verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, entityEntity1); - verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, entityEntity2); + verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, entityEntity1, true); + verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, entityEntity2, true); verify(dataAnonymisationService, times(1)).tidyUpTransformedMediaEntities(userAccount, courtCase); verify(caseService, times(1)).saveCase(courtCase); @@ -409,9 +458,9 @@ void positiveAnonymiseEventByIds() { dataAnonymisationService.anonymiseEventByIds(userAccount, List.of(1, 2, 3, 4)); - verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event1); - verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event2); - verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event3); + verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event1, false); + verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event2, false); + verify(dataAnonymisationService, times(1)).anonymiseEventEntity(userAccount, event3, false); verify(eventService, times(1)).getEventByEveId(1); verify(eventService, times(1)).getEventByEveId(2); diff --git a/src/test/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImplTest.java b/src/test/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImplTest.java index 4bbf9172d88..d990103208f 100644 --- a/src/test/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImplTest.java +++ b/src/test/java/uk/gov/hmcts/darts/event/service/impl/EventServiceImplTest.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.darts.event.service.impl; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -98,4 +99,20 @@ void positiveGetAllCourtCaseEventVersions() { } + @Test + @DisplayName("areAllAssociatedCasesAnonymised(...) method, should return true if all associated cases are anonymised") + void allAssociatedCasesAnonymisedTrue() { + EventEntity event = mock(EventEntity.class); + when(eventLinkedCaseRepository.areAllAssociatedCasesAnonymised(event)).thenReturn(true); + assertThat(eventService.allAssociatedCasesAnonymised(event)).isTrue(); + verify(eventLinkedCaseRepository).areAllAssociatedCasesAnonymised(event); + } + + @Test + void positiveAllAssociatedCasesAnonymisedFalse() { + EventEntity event = mock(EventEntity.class); + when(eventLinkedCaseRepository.areAllAssociatedCasesAnonymised(event)).thenReturn(false); + assertThat(eventService.allAssociatedCasesAnonymised(event)).isFalse(); + verify(eventLinkedCaseRepository).areAllAssociatedCasesAnonymised(event); + } } \ No newline at end of file