From d438a79597424f61fc8e635a7f2d8fe47226f7e3 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Mon, 20 Jul 2026 13:56:31 +0100 Subject: [PATCH 01/11] Soft-delete original VF recordings where a re-encoded version exists --- .../repositories/RecordingRepository.java | 2 + .../preapi/services/RecordingService.java | 5 + .../DeleteOriginalVodafoneRecordings.java | 45 +++++++ .../preapi/services/RecordingServiceTest.java | 16 +++ .../DeleteOriginalVodafoneRecordingsTest.java | 115 ++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java create mode 100644 src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java index e4b05c4f4..2ab66f154 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java @@ -113,6 +113,8 @@ Page searchAllBy( List findAllByParentRecordingIsNull(); + boolean existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(UUID id, boolean reencode); + boolean existsByCaptureSessionAndDeletedAtIsNull(CaptureSession captureSession); Optional findFirstByCaptureSessionAndDeletedAtIsNull(CaptureSession captureSession); diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java index c40cc6316..4407654bc 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java @@ -312,4 +312,9 @@ public List findAllVodafoneRecordings() { .collect(Collectors.toList()); } + @Transactional + public boolean reencodedVersionExists(UUID recordingId) { + return recordingRepository.existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true); + } + } diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java new file mode 100644 index 000000000..ae48785bc --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java @@ -0,0 +1,45 @@ +package uk.gov.hmcts.reform.preapi.tasks; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.reform.preapi.dto.RecordingDTO; +import uk.gov.hmcts.reform.preapi.security.service.UserAuthenticationService; +import uk.gov.hmcts.reform.preapi.services.RecordingService; +import uk.gov.hmcts.reform.preapi.services.UserService; + +import java.util.List; + +@Slf4j +@Component +public class DeleteOriginalVodafoneRecordings extends RobotUserTask { + + private final boolean hideReencodedRecordings; + private final RecordingService recordingService; + + public DeleteOriginalVodafoneRecordings(RecordingService recordingService, + UserService userService, + UserAuthenticationService userAuthenticationService, + @Value("${cron-user-email}") String cronUserEmail, + @Value("${HIDE_REENCODED_RECORDINGS:}") String hideReencodedRecordings) { + super(userService, userAuthenticationService, cronUserEmail); + this.recordingService = recordingService; + this.hideReencodedRecordings = Boolean.parseBoolean(hideReencodedRecordings); + } + + @Override + public void run() { + signInRobotUser(); + if (hideReencodedRecordings) { + log.info("Re-encoded recordings are hidden. Quitting..."); + return; + } + + List allVodafoneRecordings = recordingService.findAllVodafoneRecordings(); + + allVodafoneRecordings.stream() + .filter(r -> recordingService.reencodedVersionExists(r.getId())) + .forEach(r -> recordingService.deleteById(r.getId())); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java index ff45c5061..4d5b96867 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @SpringBootTest(classes = RecordingService.class) @@ -829,4 +830,19 @@ void findAllVodafoneRecordingsSuccess() { assertThat(results.getFirst().getId()).isEqualTo(recordingEntity.getId()); verify(recordingRepository, times(1)).findAllOriginVodafoneNoDuration(); } + + @Test + @DisplayName("Check if re-encoded recording exists") + void checkIfReEncodedRecordingExists() { + UUID recordingId = UUID.randomUUID(); + when(recordingRepository.existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true)) + .thenReturn(true); + + recordingService.reencodedVersionExists(recordingId); + + verify(recordingRepository, times(1)) + .existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true); + + verifyNoMoreInteractions(recordingRepository); + } } diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java new file mode 100644 index 000000000..dfb3c75ea --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java @@ -0,0 +1,115 @@ +package uk.gov.hmcts.reform.preapi.tasks; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import uk.gov.hmcts.reform.preapi.dto.AccessDTO; +import uk.gov.hmcts.reform.preapi.dto.RecordingDTO; +import uk.gov.hmcts.reform.preapi.dto.base.BaseAppAccessDTO; +import uk.gov.hmcts.reform.preapi.entities.User; +import uk.gov.hmcts.reform.preapi.security.authentication.UserAuthentication; +import uk.gov.hmcts.reform.preapi.security.service.UserAuthenticationService; +import uk.gov.hmcts.reform.preapi.services.RecordingService; +import uk.gov.hmcts.reform.preapi.services.UserService; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@SpringBootTest(classes = DeleteOriginalVodafoneRecordings.class) +class DeleteOriginalVodafoneRecordingsTest { + + private static final String CRON_USER_EMAIL = "cron@example.com"; + + @MockitoBean + private RecordingService recordingService; + + @MockitoBean + private UserService userService; + + @MockitoBean + private User user; + + @MockitoBean + private UserAuthenticationService userAuthenticationService; + + UUID preRecordingId = UUID.randomUUID(); + UUID recordingWithReencodedVersion = UUID.randomUUID(); + UUID recordingWithoutReencodedVersion = UUID.randomUUID(); + + @BeforeEach + void setUp() { + BaseAppAccessDTO appAccess = new BaseAppAccessDTO(); + appAccess.setId(UUID.randomUUID()); + + AccessDTO access = new AccessDTO(); + access.setAppAccess(Set.of(appAccess)); + + when(userService.findByEmail(CRON_USER_EMAIL)).thenReturn(access); + when(userAuthenticationService.validateUser(any())).thenReturn(Optional.of(mock(UserAuthentication.class))); + + when(recordingService.reencodedVersionExists(recordingWithReencodedVersion)).thenReturn(true); + when(recordingService.reencodedVersionExists(recordingWithoutReencodedVersion)).thenReturn(false); + } + + @Test + @DisplayName("Nothing happens if Hide Reencodes flag is set to true") + void nothingDeletedIfHideReencodesFlagIsSetToTrue() { + DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, + userService, + userAuthenticationService, + CRON_USER_EMAIL, + "true"); + underTest.run(); + + verifyNoInteractions(userService); + verifyNoInteractions(userAuthenticationService); + verifyNoInteractions(recordingService); + } + + @Test + @DisplayName("Delete original VF recordings where re-encoded version exists") + void deleteVFWhereReEncodedVersionExists() { + DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, + userService, + userAuthenticationService, + CRON_USER_EMAIL, + "false"); + + RecordingDTO preRecording = new RecordingDTO(); + preRecording.setId(preRecordingId); + + RecordingDTO vfRecording = new RecordingDTO(); + vfRecording.setId(recordingWithoutReencodedVersion); + + RecordingDTO reencodedRecording = new RecordingDTO(); + reencodedRecording.setId(recordingWithReencodedVersion); + + when(recordingService.findAllVodafoneRecordings()).thenReturn(List.of(vfRecording, reencodedRecording)); + + underTest.run(); + + verify(recordingService, times(1)).reencodedVersionExists(recordingWithReencodedVersion); + verify(recordingService, times(1)).reencodedVersionExists(recordingWithoutReencodedVersion); + verify(recordingService, times(0)).reencodedVersionExists(preRecordingId); + + verify(recordingService, times(1)).deleteById(recordingWithReencodedVersion); + verify(recordingService, times(0)).deleteById(recordingWithoutReencodedVersion); + verify(recordingService, times(0)).deleteById(preRecordingId); + + verify(userService, times(1)).findByEmail(CRON_USER_EMAIL); + + verify(userAuthenticationService, times(1)).validateUser(any()); + } + +} From 354cce086bf4ddfa19f30b453c6c0762d2ad475a Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Mon, 20 Jul 2026 14:07:58 +0100 Subject: [PATCH 02/11] Checkstyle --- .../tasks/DeleteOriginalVodafoneRecordingsTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java index dfb3c75ea..8719f0703 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java @@ -80,12 +80,6 @@ void nothingDeletedIfHideReencodesFlagIsSetToTrue() { @Test @DisplayName("Delete original VF recordings where re-encoded version exists") void deleteVFWhereReEncodedVersionExists() { - DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, - userService, - userAuthenticationService, - CRON_USER_EMAIL, - "false"); - RecordingDTO preRecording = new RecordingDTO(); preRecording.setId(preRecordingId); @@ -97,6 +91,12 @@ void deleteVFWhereReEncodedVersionExists() { when(recordingService.findAllVodafoneRecordings()).thenReturn(List.of(vfRecording, reencodedRecording)); + DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, + userService, + userAuthenticationService, + CRON_USER_EMAIL, + "false"); + underTest.run(); verify(recordingService, times(1)).reencodedVersionExists(recordingWithReencodedVersion); From e839570521c46048ffcfd9098fb104595e59ce88 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Mon, 20 Jul 2026 14:15:07 +0100 Subject: [PATCH 03/11] Don't sign in if flag is true --- .../reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java index ae48785bc..1f75a1172 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java @@ -29,11 +29,11 @@ public DeleteOriginalVodafoneRecordings(RecordingService recordingService, @Override public void run() { - signInRobotUser(); if (hideReencodedRecordings) { log.info("Re-encoded recordings are hidden. Quitting..."); return; } + signInRobotUser(); List allVodafoneRecordings = recordingService.findAllVodafoneRecordings(); From faabcad08b29ac25eb14d5532b955ef9bb986715 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Mon, 20 Jul 2026 16:40:32 +0100 Subject: [PATCH 04/11] Add flag to mark original recordings that were deleted --- .../reform/preapi/entities/Recording.java | 3 + .../repositories/RecordingRepository.java | 16 +++++ .../preapi/services/RecordingService.java | 35 +++++++-- .../DeleteOriginalVodafoneRecordings.java | 45 ------------ .../tasks/UpdateRecordingsVisibility.java | 49 +++++++++++++ ...AddVfOriginalReencodedFlagToRecordings.sql | 2 + .../preapi/services/RecordingServiceTest.java | 56 +++++++++++++-- ...va => UpdateRecordingsVisibilityTest.java} | 72 +++++++------------ 8 files changed, 172 insertions(+), 106 deletions(-) delete mode 100644 src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java create mode 100644 src/main/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibility.java create mode 100644 src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql rename src/test/java/uk/gov/hmcts/reform/preapi/tasks/{DeleteOriginalVodafoneRecordingsTest.java => UpdateRecordingsVisibilityTest.java} (51%) diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/entities/Recording.java b/src/main/java/uk/gov/hmcts/reform/preapi/entities/Recording.java index c00e35968..159e2763c 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/entities/Recording.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/entities/Recording.java @@ -67,6 +67,9 @@ public class Recording extends BaseEntity implements ISoftDeletable { @Column(name = "is_reencode", nullable = false) private boolean reencode; + @Column(name = "vf_original_reencoded", nullable = false) + private boolean vfOriginalNowReencoded; + @OneToMany(mappedBy = "sourceRecording") private Set editRequests; diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java index 2ab66f154..10d325e9a 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java @@ -210,4 +210,20 @@ List countRecordingsPerCase( """ ) List findAllOriginVodafoneNoDuration(); + + @Query(""" + SELECT r FROM Recording r + WHERE r.captureSession.origin = 'VODAFONE' + AND r.deletedAt IS NULL + AND r.id in + (SELECT reencoded.parentRecording.id FROM Recording reencoded + where r.captureSession.origin = 'VODAFONE' + AND r.deletedAt IS NULL + AND r.reencode = true + ) + """ + ) + List findVodafoneOriginalRecordingsWhereReencodedVersionExists(); + + List findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue(); } diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java index 4407654bc..6bb42cf63 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java @@ -231,6 +231,34 @@ public void deleteById(UUID recordingId) { recordingRepository.saveAndFlush(recording); } + @Transactional + @PreAuthorize("hasAnyRole('ROLE_SUPER_USER', 'ROLE_LEVEL_1')") + public void deleteOriginalWhereReencodedVersionExists() { + recordingRepository.findVodafoneOriginalRecordingsWhereReencodedVersionExists() + .forEach(recording -> { + recording.setVfOriginalNowReencoded(Boolean.TRUE); + recording.setDeletedAt(Timestamp.from(Instant.now())); + recording.setDeleted(Boolean.TRUE); + recordingRepository.save(recording); + }); + + recordingRepository.flush(); + } + + @Transactional + @PreAuthorize("hasAnyRole('ROLE_SUPER_USER', 'ROLE_LEVEL_1')") + public void undeleteOriginalWhereReencodedVersionExists() { + recordingRepository.findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue() + .forEach(recording -> { + // Leave originalVfRecording flag set - doesn't need to be reset + recording.setDeletedAt(null); + recording.setDeleted(Boolean.FALSE); + recordingRepository.save(recording); + }); + + recordingRepository.flush(); + } + @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void checkIfCaptureSessionHasAssociatedRecordings(CaptureSession captureSession) { Optional recording = recordingRepository.findFirstByCaptureSessionAndDeletedAtIsNull(captureSession); @@ -303,7 +331,6 @@ public List findAllDurationNull() { .toList(); } - @Transactional public List findAllVodafoneRecordings() { // return recordingRepository.findAllOriginVodafone().stream() @@ -311,10 +338,4 @@ public List findAllVodafoneRecordings() { .map(RecordingDTO::new) .collect(Collectors.toList()); } - - @Transactional - public boolean reencodedVersionExists(UUID recordingId) { - return recordingRepository.existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true); - } - } diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java deleted file mode 100644 index 1f75a1172..000000000 --- a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordings.java +++ /dev/null @@ -1,45 +0,0 @@ -package uk.gov.hmcts.reform.preapi.tasks; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import uk.gov.hmcts.reform.preapi.dto.RecordingDTO; -import uk.gov.hmcts.reform.preapi.security.service.UserAuthenticationService; -import uk.gov.hmcts.reform.preapi.services.RecordingService; -import uk.gov.hmcts.reform.preapi.services.UserService; - -import java.util.List; - -@Slf4j -@Component -public class DeleteOriginalVodafoneRecordings extends RobotUserTask { - - private final boolean hideReencodedRecordings; - private final RecordingService recordingService; - - public DeleteOriginalVodafoneRecordings(RecordingService recordingService, - UserService userService, - UserAuthenticationService userAuthenticationService, - @Value("${cron-user-email}") String cronUserEmail, - @Value("${HIDE_REENCODED_RECORDINGS:}") String hideReencodedRecordings) { - super(userService, userAuthenticationService, cronUserEmail); - this.recordingService = recordingService; - this.hideReencodedRecordings = Boolean.parseBoolean(hideReencodedRecordings); - } - - @Override - public void run() { - if (hideReencodedRecordings) { - log.info("Re-encoded recordings are hidden. Quitting..."); - return; - } - signInRobotUser(); - - List allVodafoneRecordings = recordingService.findAllVodafoneRecordings(); - - allVodafoneRecordings.stream() - .filter(r -> recordingService.reencodedVersionExists(r.getId())) - .forEach(r -> recordingService.deleteById(r.getId())); - } - -} diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibility.java b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibility.java new file mode 100644 index 000000000..12f38b313 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibility.java @@ -0,0 +1,49 @@ +package uk.gov.hmcts.reform.preapi.tasks; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.reform.preapi.security.service.UserAuthenticationService; +import uk.gov.hmcts.reform.preapi.services.RecordingService; +import uk.gov.hmcts.reform.preapi.services.UserService; + +/** + * Decides whether to make *VF original recordings* visible, depending on HIDE_REENCODED_RECORDINGS flag + * Needs to be run after flag has been updated to hide/display VF recordings + * Re-encoded recordings' visibility is controlled directly by the flag in application code + * This approach uses the existing logic around deleted recordings to control visibility instead. + * It is intended as a temporary measure while we monitor re-encoded recordings for any issues. + * If all is well, VF originals can remain deleted, and we can delete this task. + */ +@Slf4j +@Component +public class UpdateRecordingsVisibility extends RobotUserTask { + + private final boolean hideReencodedRecordings; + private final RecordingService recordingService; + + @Autowired + public UpdateRecordingsVisibility(RecordingService recordingService, + UserService userService, + UserAuthenticationService userAuthenticationService, + @Value("${cron-user-email}") String cronUserEmail, + @Value("${HIDE_REENCODED_RECORDINGS:}") String hideReencodedRecordings) { + super(userService, userAuthenticationService, cronUserEmail); + this.recordingService = recordingService; + this.hideReencodedRecordings = Boolean.parseBoolean(hideReencodedRecordings); + } + + @Override + public void run() { + signInRobotUser(); + + if (hideReencodedRecordings) { + log.info("Re-encoded recordings are hidden. Undeleting originals..."); + recordingService.undeleteOriginalWhereReencodedVersionExists(); + } else { + recordingService.deleteOriginalWhereReencodedVersionExists(); + } + } + +} diff --git a/src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql b/src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql new file mode 100644 index 000000000..b38defec6 --- /dev/null +++ b/src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql @@ -0,0 +1,2 @@ +ALTER TABLE recordings + ADD COLUMN vf_original_reencoded BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java index 4d5b96867..f69ace9b2 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java @@ -1,9 +1,11 @@ package uk.gov.hmcts.reform.preapi.services; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; @@ -832,16 +834,56 @@ void findAllVodafoneRecordingsSuccess() { } @Test - @DisplayName("Check if re-encoded recording exists") - void checkIfReEncodedRecordingExists() { - UUID recordingId = UUID.randomUUID(); - when(recordingRepository.existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true)) - .thenReturn(true); + @DisplayName("Undelete VF originals") + void undeleteVFOriginals() { + Recording vfOriginalWithReencodedVersion = new Recording(); + vfOriginalWithReencodedVersion.setId(UUID.randomUUID()); + vfOriginalWithReencodedVersion.setDeleted(Boolean.TRUE); + vfOriginalWithReencodedVersion.setDeletedAt(Timestamp.from(Instant.now())); + vfOriginalWithReencodedVersion.setVfOriginalNowReencoded(Boolean.TRUE); - recordingService.reencodedVersionExists(recordingId); + when(recordingRepository.findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue()) + .thenReturn(List.of(vfOriginalWithReencodedVersion)); + + recordingService.undeleteOriginalWhereReencodedVersionExists(); verify(recordingRepository, times(1)) - .existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(recordingId, true); + .findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Recording.class); + verify(recordingRepository, times(1)).save(captor.capture()); + + Recording savedRecording = captor.getValue(); + assertThat(savedRecording.getId()).isEqualTo(vfOriginalWithReencodedVersion.getId()); + assertThat(savedRecording.isDeleted()).isFalse(); + assertThat(savedRecording.isVfOriginalNowReencoded()).isTrue(); + + verify(recordingRepository, times(1)).flush(); + + verifyNoMoreInteractions(recordingRepository); + } + + @Test + @DisplayName("Delete original VF recordings where re-encoded version exists") + void deleteVFWhereReEncodedVersionExists() { + Recording vfOriginalWithReencodedVersion = new Recording(); + vfOriginalWithReencodedVersion.setId(UUID.randomUUID()); + + when(recordingRepository.findVodafoneOriginalRecordingsWhereReencodedVersionExists()) + .thenReturn(List.of(vfOriginalWithReencodedVersion)); + + recordingService.deleteOriginalWhereReencodedVersionExists(); + + verify(recordingRepository, times(1)) + .findVodafoneOriginalRecordingsWhereReencodedVersionExists(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Recording.class); + verify(recordingRepository, times(1)).save(captor.capture()); + Recording capturedRecording = captor.getValue(); + Assertions.assertThat(capturedRecording.isDeleted()).isTrue(); + Assertions.assertThat(capturedRecording.isVfOriginalNowReencoded()).isTrue(); + + verify(recordingRepository, times(1)).flush(); verifyNoMoreInteractions(recordingRepository); } diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibilityTest.java similarity index 51% rename from src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java rename to src/test/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibilityTest.java index 8719f0703..1dc54e4ce 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/tasks/DeleteOriginalVodafoneRecordingsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/tasks/UpdateRecordingsVisibilityTest.java @@ -6,7 +6,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; import uk.gov.hmcts.reform.preapi.dto.AccessDTO; -import uk.gov.hmcts.reform.preapi.dto.RecordingDTO; import uk.gov.hmcts.reform.preapi.dto.base.BaseAppAccessDTO; import uk.gov.hmcts.reform.preapi.entities.User; import uk.gov.hmcts.reform.preapi.security.authentication.UserAuthentication; @@ -14,7 +13,6 @@ import uk.gov.hmcts.reform.preapi.services.RecordingService; import uk.gov.hmcts.reform.preapi.services.UserService; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -23,11 +21,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -@SpringBootTest(classes = DeleteOriginalVodafoneRecordings.class) -class DeleteOriginalVodafoneRecordingsTest { +@SpringBootTest(classes = UpdateRecordingsVisibility.class) +class UpdateRecordingsVisibilityTest { private static final String CRON_USER_EMAIL = "cron@example.com"; @@ -43,10 +41,6 @@ class DeleteOriginalVodafoneRecordingsTest { @MockitoBean private UserAuthenticationService userAuthenticationService; - UUID preRecordingId = UUID.randomUUID(); - UUID recordingWithReencodedVersion = UUID.randomUUID(); - UUID recordingWithoutReencodedVersion = UUID.randomUUID(); - @BeforeEach void setUp() { BaseAppAccessDTO appAccess = new BaseAppAccessDTO(); @@ -57,59 +51,43 @@ void setUp() { when(userService.findByEmail(CRON_USER_EMAIL)).thenReturn(access); when(userAuthenticationService.validateUser(any())).thenReturn(Optional.of(mock(UserAuthentication.class))); - - when(recordingService.reencodedVersionExists(recordingWithReencodedVersion)).thenReturn(true); - when(recordingService.reencodedVersionExists(recordingWithoutReencodedVersion)).thenReturn(false); } @Test - @DisplayName("Nothing happens if Hide Reencodes flag is set to true") - void nothingDeletedIfHideReencodesFlagIsSetToTrue() { - DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, - userService, - userAuthenticationService, - CRON_USER_EMAIL, - "true"); + @DisplayName("Undelete originals if Hide Reencodes flag is set to true") + void undeleteOriginalsIfHideReencodesFlagIsSetToTrue() { + UpdateRecordingsVisibility underTest = new UpdateRecordingsVisibility(recordingService, + userService, + userAuthenticationService, + CRON_USER_EMAIL, + "true"); underTest.run(); - verifyNoInteractions(userService); - verifyNoInteractions(userAuthenticationService); - verifyNoInteractions(recordingService); + verify(userService, times(1)).findByEmail(CRON_USER_EMAIL); + verify(userAuthenticationService, times(1)).validateUser(any()); + + verify(recordingService, times(1)).undeleteOriginalWhereReencodedVersionExists(); + + verifyNoMoreInteractions(recordingService); } @Test @DisplayName("Delete original VF recordings where re-encoded version exists") void deleteVFWhereReEncodedVersionExists() { - RecordingDTO preRecording = new RecordingDTO(); - preRecording.setId(preRecordingId); - - RecordingDTO vfRecording = new RecordingDTO(); - vfRecording.setId(recordingWithoutReencodedVersion); - - RecordingDTO reencodedRecording = new RecordingDTO(); - reencodedRecording.setId(recordingWithReencodedVersion); - - when(recordingService.findAllVodafoneRecordings()).thenReturn(List.of(vfRecording, reencodedRecording)); - - DeleteOriginalVodafoneRecordings underTest = new DeleteOriginalVodafoneRecordings(recordingService, - userService, - userAuthenticationService, - CRON_USER_EMAIL, - "false"); + UpdateRecordingsVisibility underTest = new UpdateRecordingsVisibility(recordingService, + userService, + userAuthenticationService, + CRON_USER_EMAIL, + "false"); underTest.run(); - verify(recordingService, times(1)).reencodedVersionExists(recordingWithReencodedVersion); - verify(recordingService, times(1)).reencodedVersionExists(recordingWithoutReencodedVersion); - verify(recordingService, times(0)).reencodedVersionExists(preRecordingId); - - verify(recordingService, times(1)).deleteById(recordingWithReencodedVersion); - verify(recordingService, times(0)).deleteById(recordingWithoutReencodedVersion); - verify(recordingService, times(0)).deleteById(preRecordingId); - verify(userService, times(1)).findByEmail(CRON_USER_EMAIL); - verify(userAuthenticationService, times(1)).validateUser(any()); + + verify(recordingService, times(1)).deleteOriginalWhereReencodedVersionExists(); + + verifyNoMoreInteractions(recordingService); } } From 9826ad3c3ebc2a27d453c53090c69aac9e73e5cf Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 12:06:50 +0100 Subject: [PATCH 05/11] Integration test --- .../preapi/services/RecordingServiceIT.java | 142 +++++++++++++++++- .../repositories/RecordingRepository.java | 6 +- 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java index 5ef9841ac..d3998f2bc 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java @@ -19,7 +19,6 @@ import uk.gov.hmcts.reform.preapi.entities.Court; import uk.gov.hmcts.reform.preapi.entities.Recording; import uk.gov.hmcts.reform.preapi.enums.CaseState; -import uk.gov.hmcts.reform.preapi.enums.CaseState; import uk.gov.hmcts.reform.preapi.enums.CourtType; import uk.gov.hmcts.reform.preapi.enums.RecordingOrigin; import uk.gov.hmcts.reform.preapi.enums.UpsertResult; @@ -32,7 +31,10 @@ import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -567,6 +569,113 @@ void searchRecordingsEnableMigratedDataToggleSuperUser() { assertThat(results2.getTotalElements()).isEqualTo(3); } + @Test + @Transactional + @DisplayName("Mark VF original recordings as deleted/not deleted") + void markVfOriginalRecordingsAsDeleted() { + mockAdminUser(); + + // Set up + CaptureSession preSampleCaptureSession = persistSampleCourtCaseBookingCaptureSession( + RecordingOrigin.PRE + ); + + CaptureSession vfCaptureSessionReencoded = persistSampleCourtCaseBookingCaptureSession( + RecordingOrigin.VODAFONE + ); + + CaptureSession vfCaptureSessionNoReencode = persistSampleCourtCaseBookingCaptureSession( + RecordingOrigin.VODAFONE + ); + + Recording preRecording = HelperFactory.createRecording( + preSampleCaptureSession, null, 1, + "", null + ); + entityManager.persist(preRecording); + + Recording vfOriginal1 = HelperFactory.createRecording( + vfCaptureSessionReencoded, null, 1, + "", null + ); + entityManager.persist(vfOriginal1); + + Recording reencodedVf1 = HelperFactory.createRecording( + vfCaptureSessionReencoded, vfOriginal1, 2, + "", null + ); + entityManager.persist(reencodedVf1); + + Recording vfOriginal2 = HelperFactory.createRecording( + vfCaptureSessionNoReencode, null, 1, + "", null + ); + entityManager.persist(vfOriginal2); + + entityManager.flush(); + + // Pre-test check + Map recordingsBeforeFlagSet = recordingService.findAll( + new SearchRecordings(), + false, Pageable.unpaged() + ) + .stream() + .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); + + checkResultsForDeletedRecordings(recordingsBeforeFlagSet, + List.of(), + List.of(preRecording.getId(), vfOriginal1.getId(), reencodedVf1.getId(), vfOriginal2.getId()) + ); + + // Test: delete originals + recordingService.deleteOriginalWhereReencodedVersionExists(); + entityManager.flush(); + + Map recordingsAfterDeletion = recordingService.findAll( + new SearchRecordings(), + false, + Pageable.unpaged() + ) + .stream() + .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); + + checkResultsForDeletedRecordings(recordingsAfterDeletion, + List.of(vfOriginal1.getId()), + List.of(preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId()) + ); + + recordingService.undeleteOriginalWhereReencodedVersionExists(); + entityManager.flush(); + + Map recordingsAfterUndeletion = recordingService.findAll( + new SearchRecordings(), + false, + Pageable.unpaged() + ) + .stream() + .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); + + checkResultsForDeletedRecordings(recordingsAfterUndeletion, + List.of(), + List.of(preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId(), + vfOriginal1.getId())); + + } + + private static void checkResultsForDeletedRecordings(Map results, + List shouldBeDeleted, + List shouldNotBeDeleted) { + + assertThat(results.size()).isEqualTo(shouldNotBeDeleted.size()); + + assertThat(results.keySet().containsAll(shouldNotBeDeleted)).isTrue(); + + for (UUID id : shouldBeDeleted) { + assertThat(results.containsKey(id)).isFalse(); + } + } + + private static void mockNonAdminUser(UUID courtId) { var mockAuth = mock(UserAuthentication.class); when(mockAuth.isAdmin()).thenReturn(false); @@ -574,4 +683,35 @@ private static void mockNonAdminUser(UUID courtId) { when(mockAuth.getCourtId()).thenReturn(courtId); SecurityContextHolder.getContext().setAuthentication(mockAuth); } + + private CaptureSession persistSampleCourtCaseBookingCaptureSession(RecordingOrigin recordingOrigin) { + Court court = HelperFactory.createCourt(CourtType.CROWN, "Example Court", "1234"); + entityManager.persist(court); + + Case sampleCase = HelperFactory.createCase(court, "CASE12345", true, null); + sampleCase.setOrigin(recordingOrigin); + entityManager.persist(sampleCase); + + Booking sampleBooking = HelperFactory.createBooking( + sampleCase, Timestamp.from(Instant.now()), + null, null + ); + entityManager.persist(sampleBooking); + + CaptureSession captureSession = HelperFactory.createCaptureSession( + sampleBooking, + recordingOrigin, + null, + null, + null, + null, + null, + null, + null, + null + ); + entityManager.persist(captureSession); + + return captureSession; + } } diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java index 10d325e9a..171378d29 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java @@ -217,9 +217,9 @@ List countRecordingsPerCase( AND r.deletedAt IS NULL AND r.id in (SELECT reencoded.parentRecording.id FROM Recording reencoded - where r.captureSession.origin = 'VODAFONE' - AND r.deletedAt IS NULL - AND r.reencode = true + where reencoded.captureSession.origin = 'VODAFONE' + AND reencoded.deletedAt IS NULL + AND reencoded.reencode = true ) """ ) From 1525b161e90482f1c4c856d186d909254c522f3d Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 12:12:44 +0100 Subject: [PATCH 06/11] Set duration --- .../hmcts/reform/preapi/services/RecordingServiceIT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java index d3998f2bc..7fb005b1e 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java @@ -592,26 +592,30 @@ void markVfOriginalRecordingsAsDeleted() { preSampleCaptureSession, null, 1, "", null ); + preRecording.setDuration(Duration.ofHours(1)); entityManager.persist(preRecording); Recording vfOriginal1 = HelperFactory.createRecording( vfCaptureSessionReencoded, null, 1, "", null ); + vfOriginal1.setDuration(Duration.ofMinutes(13)); entityManager.persist(vfOriginal1); + Recording reencodedVf1 = HelperFactory.createRecording( vfCaptureSessionReencoded, vfOriginal1, 2, "", null ); + reencodedVf1.setDuration(Duration.ofMinutes(13)); entityManager.persist(reencodedVf1); Recording vfOriginal2 = HelperFactory.createRecording( vfCaptureSessionNoReencode, null, 1, "", null ); + vfOriginal2.setDuration(Duration.ofMinutes(13)); entityManager.persist(vfOriginal2); - entityManager.flush(); // Pre-test check From 582e1ac13f33b44dfa31d0d7edf96388e0033b86 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 12:32:56 +0100 Subject: [PATCH 07/11] Refactored test for clarity --- .../preapi/services/RecordingServiceIT.java | 90 +++++++++---------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java index 7fb005b1e..9c42ce0b3 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java @@ -576,47 +576,13 @@ void markVfOriginalRecordingsAsDeleted() { mockAdminUser(); // Set up - CaptureSession preSampleCaptureSession = persistSampleCourtCaseBookingCaptureSession( - RecordingOrigin.PRE - ); - - CaptureSession vfCaptureSessionReencoded = persistSampleCourtCaseBookingCaptureSession( - RecordingOrigin.VODAFONE - ); + Recording preRecording = createSampleOriginalRecording(RecordingOrigin.PRE); - CaptureSession vfCaptureSessionNoReencode = persistSampleCourtCaseBookingCaptureSession( - RecordingOrigin.VODAFONE - ); + Recording vfOriginal1 = createSampleOriginalRecording(RecordingOrigin.VODAFONE); - Recording preRecording = HelperFactory.createRecording( - preSampleCaptureSession, null, 1, - "", null - ); - preRecording.setDuration(Duration.ofHours(1)); - entityManager.persist(preRecording); + Recording vfOriginal2 = createSampleOriginalRecording(RecordingOrigin.VODAFONE); - Recording vfOriginal1 = HelperFactory.createRecording( - vfCaptureSessionReencoded, null, 1, - "", null - ); - vfOriginal1.setDuration(Duration.ofMinutes(13)); - entityManager.persist(vfOriginal1); - - - Recording reencodedVf1 = HelperFactory.createRecording( - vfCaptureSessionReencoded, vfOriginal1, 2, - "", null - ); - reencodedVf1.setDuration(Duration.ofMinutes(13)); - entityManager.persist(reencodedVf1); - - Recording vfOriginal2 = HelperFactory.createRecording( - vfCaptureSessionNoReencode, null, 1, - "", null - ); - vfOriginal2.setDuration(Duration.ofMinutes(13)); - entityManager.persist(vfOriginal2); - entityManager.flush(); + Recording reencodedVf1 = createSampleReencodedRecording(vfOriginal1, 2); // Pre-test check Map recordingsBeforeFlagSet = recordingService.findAll( @@ -626,7 +592,8 @@ void markVfOriginalRecordingsAsDeleted() { .stream() .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); - checkResultsForDeletedRecordings(recordingsBeforeFlagSet, + checkResultsForDeletedRecordings( + recordingsBeforeFlagSet, List.of(), List.of(preRecording.getId(), vfOriginal1.getId(), reencodedVf1.getId(), vfOriginal2.getId()) ); @@ -643,9 +610,10 @@ void markVfOriginalRecordingsAsDeleted() { .stream() .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); - checkResultsForDeletedRecordings(recordingsAfterDeletion, - List.of(vfOriginal1.getId()), - List.of(preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId()) + checkResultsForDeletedRecordings( + recordingsAfterDeletion, + List.of(vfOriginal1.getId()), + List.of(preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId()) ); recordingService.undeleteOriginalWhereReencodedVersionExists(); @@ -659,11 +627,41 @@ void markVfOriginalRecordingsAsDeleted() { .stream() .collect(Collectors.toMap(RecordingDTO::getId, Function.identity())); - checkResultsForDeletedRecordings(recordingsAfterUndeletion, - List.of(), - List.of(preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId(), - vfOriginal1.getId())); + checkResultsForDeletedRecordings( + recordingsAfterUndeletion, + List.of(), + List.of( + preRecording.getId(), reencodedVf1.getId(), vfOriginal2.getId(), + vfOriginal1.getId() + ) + ); + + } + + private Recording createSampleOriginalRecording(RecordingOrigin recordingOrigin) { + CaptureSession sampleCaptureSession = persistSampleCourtCaseBookingCaptureSession( + recordingOrigin + ); + Recording recording = HelperFactory.createRecording( + sampleCaptureSession, null, 1, + "", null + ); + recording.setDuration(Duration.ofHours(1)); + entityManager.persist(recording); + return recording; + } + + private Recording createSampleReencodedRecording(Recording parentRecording, + Integer versionNumber) { + Recording recording = HelperFactory.createRecording( + parentRecording.getCaptureSession(), parentRecording, versionNumber, + "", null + ); + recording.setReencode(true); + recording.setDuration(Duration.ofHours(1)); + entityManager.persist(recording); + return recording; } private static void checkResultsForDeletedRecordings(Map results, From e40d5a0083432556c2a269b286c6bc8ee8b2449b Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 12:58:05 +0100 Subject: [PATCH 08/11] Auth for deleting or undeleting originals --- .../preapi/services/RecordingServiceIT.java | 24 +++++++++++++++++++ .../preapi/services/RecordingService.java | 13 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java index 9c42ce0b3..dfbc82614 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceIT.java @@ -638,6 +638,30 @@ void markVfOriginalRecordingsAsDeleted() { } + + @Test + @Transactional + @DisplayName("Non-admin user cannot mark VF original recordings as deleted/not deleted") + void nonAdminUserCannotMarkVfOriginalRecordingsAsDeleted() { + mockNonAdminUser(); + + String message = Assertions.assertThrows( + AccessDeniedException.class, + () -> recordingService.deleteOriginalWhereReencodedVersionExists() + ).getMessage(); + + assertThat(message) + .isEqualTo("User does not have sufficient privileges to delete original recordings"); + + String message2 = Assertions.assertThrows( + AccessDeniedException.class, + () -> recordingService.undeleteOriginalWhereReencodedVersionExists() + ).getMessage(); + + assertThat(message2) + .isEqualTo("User does not have sufficient privileges to undelete original recordings"); + } + private Recording createSampleOriginalRecording(RecordingOrigin recordingOrigin) { CaptureSession sampleCaptureSession = persistSampleCourtCaseBookingCaptureSession( recordingOrigin diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java index 6bb42cf63..c8ce39f3f 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/services/RecordingService.java @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -232,8 +233,12 @@ public void deleteById(UUID recordingId) { } @Transactional - @PreAuthorize("hasAnyRole('ROLE_SUPER_USER', 'ROLE_LEVEL_1')") public void deleteOriginalWhereReencodedVersionExists() { + UserAuthentication auth = (UserAuthentication) SecurityContextHolder.getContext().getAuthentication(); + if (!auth.isAdmin()) { + throw new AccessDeniedException("User does not have sufficient privileges to delete original recordings"); + } + recordingRepository.findVodafoneOriginalRecordingsWhereReencodedVersionExists() .forEach(recording -> { recording.setVfOriginalNowReencoded(Boolean.TRUE); @@ -246,8 +251,12 @@ public void deleteOriginalWhereReencodedVersionExists() { } @Transactional - @PreAuthorize("hasAnyRole('ROLE_SUPER_USER', 'ROLE_LEVEL_1')") public void undeleteOriginalWhereReencodedVersionExists() { + UserAuthentication auth = (UserAuthentication) SecurityContextHolder.getContext().getAuthentication(); + if (!auth.isAdmin()) { + throw new AccessDeniedException("User does not have sufficient privileges to undelete original recordings"); + } + recordingRepository.findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue() .forEach(recording -> { // Leave originalVfRecording flag set - doesn't need to be reset From 58847238938fc8bbad1b80ce07d39dfc658920d4 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 13:12:34 +0100 Subject: [PATCH 09/11] Fix test by adding auth --- .../preapi/services/RecordingServiceTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java index f69ace9b2..13bbca311 100644 --- a/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/preapi/services/RecordingServiceTest.java @@ -10,6 +10,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.bean.override.mockito.MockitoBean; import uk.gov.hmcts.reform.preapi.controllers.params.SearchRecordings; @@ -836,6 +837,10 @@ void findAllVodafoneRecordingsSuccess() { @Test @DisplayName("Undelete VF originals") void undeleteVFOriginals() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + Recording vfOriginalWithReencodedVersion = new Recording(); vfOriginalWithReencodedVersion.setId(UUID.randomUUID()); vfOriginalWithReencodedVersion.setDeleted(Boolean.TRUE); @@ -866,6 +871,10 @@ void undeleteVFOriginals() { @Test @DisplayName("Delete original VF recordings where re-encoded version exists") void deleteVFWhereReEncodedVersionExists() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(true); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + Recording vfOriginalWithReencodedVersion = new Recording(); vfOriginalWithReencodedVersion.setId(UUID.randomUUID()); @@ -887,4 +896,22 @@ void deleteVFWhereReEncodedVersionExists() { verifyNoMoreInteractions(recordingRepository); } + + @Test + @DisplayName("Need to be admin user to delete/undelete VF originals") + void needToBeAdminUserToDelete() { + var mockAuth = mock(UserAuthentication.class); + when(mockAuth.isAdmin()).thenReturn(false); + SecurityContextHolder.getContext().setAuthentication(mockAuth); + + assertThrows( + AccessDeniedException.class, + () -> recordingService.undeleteOriginalWhereReencodedVersionExists() + ); + + assertThrows( + AccessDeniedException.class, + () -> recordingService.deleteOriginalWhereReencodedVersionExists() + ); + } } From 4aabe06717791e8f039e832556e8ed577cc0fd92 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Tue, 21 Jul 2026 15:18:26 +0100 Subject: [PATCH 10/11] Delete unused method --- .../hmcts/reform/preapi/repositories/RecordingRepository.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java index 171378d29..25e0288f7 100644 --- a/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/preapi/repositories/RecordingRepository.java @@ -113,8 +113,6 @@ Page searchAllBy( List findAllByParentRecordingIsNull(); - boolean existsByDeletedAtIsNullAndParentRecordingIdIsAndReencodeIs(UUID id, boolean reencode); - boolean existsByCaptureSessionAndDeletedAtIsNull(CaptureSession captureSession); Optional findFirstByCaptureSessionAndDeletedAtIsNull(CaptureSession captureSession); From 6fd5d0aa07adac9bc6e9ef4aae6f84f9996aa178 Mon Sep 17 00:00:00 2001 From: Lydia Ralph Date: Wed, 22 Jul 2026 12:02:13 +0100 Subject: [PATCH 11/11] Renumber delta to avoid conflict with master --- ...dings.sql => V055__AddVfOriginalReencodedFlagToRecordings.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V054__AddVfOriginalReencodedFlagToRecordings.sql => V055__AddVfOriginalReencodedFlagToRecordings.sql} (100%) diff --git a/src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql b/src/main/resources/db/migration/V055__AddVfOriginalReencodedFlagToRecordings.sql similarity index 100% rename from src/main/resources/db/migration/V054__AddVfOriginalReencodedFlagToRecordings.sql rename to src/main/resources/db/migration/V055__AddVfOriginalReencodedFlagToRecordings.sql