Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -567,11 +569,175 @@ void searchRecordingsEnableMigratedDataToggleSuperUser() {
assertThat(results2.getTotalElements()).isEqualTo(3);
}

@Test
@Transactional
@DisplayName("Mark VF original recordings as deleted/not deleted")
void markVfOriginalRecordingsAsDeleted() {
mockAdminUser();

// Set up
Recording preRecording = createSampleOriginalRecording(RecordingOrigin.PRE);

Recording vfOriginal1 = createSampleOriginalRecording(RecordingOrigin.VODAFONE);

Recording vfOriginal2 = createSampleOriginalRecording(RecordingOrigin.VODAFONE);

Recording reencodedVf1 = createSampleReencodedRecording(vfOriginal1, 2);

// Pre-test check
Map<UUID, RecordingDTO> 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<UUID, RecordingDTO> 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<UUID, RecordingDTO> 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()
)
);

}


@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
);

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<UUID, RecordingDTO> results,
List<UUID> shouldBeDeleted,
List<UUID> 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);
when(mockAuth.isAppUser()).thenReturn(true);
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<EditRequest> editRequests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,20 @@ List<Object[]> countRecordingsPerCase(
"""
)
List<Recording> 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 reencoded.captureSession.origin = 'VODAFONE'
AND reencoded.deletedAt IS NULL
AND reencoded.reencode = true
)
"""
)
List<Recording> findVodafoneOriginalRecordingsWhereReencodedVersionExists();

List<Recording> findRecordingsByDeletedAtIsNotNullAndVfOriginalNowReencodedIsTrue();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -231,6 +232,42 @@ public void deleteById(UUID recordingId) {
recordingRepository.saveAndFlush(recording);
}

@Transactional
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);
recording.setDeletedAt(Timestamp.from(Instant.now()));
recording.setDeleted(Boolean.TRUE);
recordingRepository.save(recording);
});

recordingRepository.flush();
}

@Transactional
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
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> recording = recordingRepository.findFirstByCaptureSessionAndDeletedAtIsNull(captureSession);
Expand Down Expand Up @@ -303,13 +340,11 @@ public List<RecordingDTO> findAllDurationNull() {
.toList();
}


@Transactional
public List<RecordingDTO> findAllVodafoneRecordings() {
// return recordingRepository.findAllOriginVodafone().stream()
return recordingRepository.findAllOriginVodafoneNoDuration().stream()
.map(RecordingDTO::new)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -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();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE recordings
Comment thread
lydiaralphgov marked this conversation as resolved.
ADD COLUMN vf_original_reencoded BOOLEAN NOT NULL DEFAULT FALSE;
Loading
Loading