Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f19a47f
Refactor email notifications to use common notifier
lydiaralphgov Jun 3, 2026
6105686
Create email parameters from edit request
lydiaralphgov Jun 11, 2026
4ea2c36
Edit email template
lydiaralphgov Jun 11, 2026
10ebdbd
Gov Notify email service
lydiaralphgov Jun 11, 2026
1b1f08d
Main edit services
lydiaralphgov Jun 11, 2026
c5da093
Tidy up
lydiaralphgov Jun 11, 2026
215a70f
Merge branch 'master' into S28-3289/edit-request-emails
lydiaralphgov Jun 11, 2026
d5ac032
Checkstyle
lydiaralphgov Jun 11, 2026
2979948
Correction
lydiaralphgov Jun 11, 2026
12be299
Fixed test
lydiaralphgov Jun 11, 2026
55bab0d
Absorb error when constructing parameters for edit email
lydiaralphgov Jun 11, 2026
1b0e83a
Extra unit test to document current behaviour: new edit requests do n…
lydiaralphgov Jun 11, 2026
c491202
Fix code quality issues
lydiaralphgov Jun 11, 2026
5a777f9
Corrections
lydiaralphgov Jun 11, 2026
461fbe5
More code quality improvements
lydiaralphgov Jun 11, 2026
bce2f2e
More test coverage
lydiaralphgov Jun 12, 2026
de7c982
Add static method for testing edit request status
lydiaralphgov Jun 15, 2026
464d900
CHANGE: trigger notification service whenever status is updated
lydiaralphgov Jun 15, 2026
63601b4
Checkstyle
lydiaralphgov Jun 15, 2026
a638870
Sonar issues
lydiaralphgov Jun 15, 2026
e7c0763
Functional test stubs
lydiaralphgov Jun 16, 2026
78771c9
Merge branch 'master' into S28-3289/edit-request-emails
lydiaralphgov Jun 25, 2026
b30d723
Basic test outline
lydiaralphgov Jun 29, 2026
cbde8d5
First stab at functional tests for first half of auto editing
lydiaralphgov Jul 21, 2026
cad50dc
Checkstyle
lydiaralphgov Jul 21, 2026
b47276c
Update tests
lydiaralphgov Jul 21, 2026
e45202e
Refactor test to reduce duplication
lydiaralphgov Jul 21, 2026
ebe3131
Checkstyle
lydiaralphgov Jul 21, 2026
738c636
More refactor and corrections
lydiaralphgov Jul 22, 2026
de776fa
Checkstyle
lydiaralphgov Jul 22, 2026
a810c5a
Update integration test
lydiaralphgov Jul 22, 2026
107e11e
Update test
lydiaralphgov Jul 22, 2026
1097072
Delete duplicate test
lydiaralphgov Jul 22, 2026
f438bc5
Temp
lydiaralphgov Jul 22, 2026
0a55160
Code quality
lydiaralphgov Jul 22, 2026
40deeb0
Add test for transition from approved to processing
lydiaralphgov Jul 22, 2026
1d0fcdc
Corrections
lydiaralphgov Jul 23, 2026
7013a5e
Correct status codes
lydiaralphgov Jul 23, 2026
645d94e
Corrections
lydiaralphgov Jul 23, 2026
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 @@ -104,9 +104,10 @@ void editRequestFromCsvForbidden(TestingSupportRoles role) {

@Test
@DisplayName("Should not create an edit with a csv that has unsafe data in fields")
void editRequestWithUnsafeDataCsv() throws JsonProcessingException {
void editRequestWithUnsafeDataCsv() {
CreateRecordingResponse recordingDetails = createRecording();
RecordingDTO recordingDTO = assertRecordingExists(recordingDetails.recordingId(), true).as(RecordingDTO.class);
RecordingDTO recordingDTO = assertRecordingExists(recordingDetails.recordingId(), true)
.as(RecordingDTO.class);

when(azureFinalStorageService.getMp4FileName(recordingDetails.recordingId().toString()))
.thenReturn(recordingDTO.getFilename());
Expand Down Expand Up @@ -183,7 +184,7 @@ void reencodeOnlyEditRequestSuccess() throws JsonProcessingException {
TestingSupportRoles.SUPER_USER
);

assertResponseCode(putResponse, 201);
assertResponseCode(putResponse, 204);

EditRequestDTO getResponse = doGetRequest(EDIT_ENDPOINT + "/" + editRequestId, TestingSupportRoles.SUPER_USER)
.as(EditRequestDTO.class);
Expand Down Expand Up @@ -223,7 +224,7 @@ void editRequestWithNotificationsDisabledSuccess() throws JsonProcessingExceptio
TestingSupportRoles.SUPER_USER
);

assertResponseCode(putResponse, 201);
assertResponseCode(putResponse, 204);

EditRequestDTO getResponse = doGetRequest(EDIT_ENDPOINT + "/" + editRequestId, TestingSupportRoles.SUPER_USER)
.as(EditRequestDTO.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package uk.gov.hmcts.reform.preapi.controllers;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.restassured.response.Response;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import uk.gov.hmcts.reform.preapi.controllers.params.TestingSupportRoles;
import uk.gov.hmcts.reform.preapi.dto.CreateEditRequestDTO;
import uk.gov.hmcts.reform.preapi.dto.EditCutInstructionDTO;
import uk.gov.hmcts.reform.preapi.dto.RecordingDTO;
import uk.gov.hmcts.reform.preapi.entities.Booking;
import uk.gov.hmcts.reform.preapi.entities.CaptureSession;
import uk.gov.hmcts.reform.preapi.entities.Case;
import uk.gov.hmcts.reform.preapi.entities.Court;
import uk.gov.hmcts.reform.preapi.entities.Recording;
import uk.gov.hmcts.reform.preapi.enums.EditRequestStatus;
import uk.gov.hmcts.reform.preapi.media.storage.AzureFinalStorageService;
import uk.gov.hmcts.reform.preapi.util.FunctionalTestBase;

import java.sql.Timestamp;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

import static java.lang.String.format;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.when;

// I split this out to capture the first half of the editing process, which doesn't yet have any tests
class EditControllerFullyAutomatedFT extends FunctionalTestBase {
private static final String EDIT_ENDPOINT = "/edits";

@MockitoBean
private AzureFinalStorageService azureFinalStorageService;

private UUID recordingId;
private RecordingDTO recordingDTO;
private Recording recording;

@MockitoBean
private CaptureSession captureSession;

@MockitoBean
private Booking booking;

@MockitoBean
private Case legalCase;

@MockitoBean
private Court mockCourt;


@BeforeEach
void setUp() {
CreateRecordingResponse recordingDetails = createRecording();
recordingId = recordingDetails.recordingId();
recording = new Recording();
recording.setId(recordingId);
recording.setCaptureSession(captureSession);
recording.setDuration(Duration.ofMinutes(30));
recording.setVersion(1);
recording.setCreatedAt(Timestamp.valueOf(LocalDateTime.now()));
recording.setFilename("Test_filename.mp4");

when(captureSession.getId()).thenReturn(recordingDetails.captureSessionId());
when(captureSession.getBooking()).thenReturn(booking);

when(booking.getId()).thenReturn(recordingDetails.bookingId());
when(booking.getCaseId()).thenReturn(legalCase);

when(legalCase.getCourt()).thenReturn(mockCourt);
when(mockCourt.getGroupEmail()).thenReturn("mock@email.com");

recordingDTO = assertRecordingExists(recordingDetails.recordingId(), true)
.as(RecordingDTO.class);

when(azureFinalStorageService.getMp4FileName(recordingDetails.recordingId().toString()))
.thenReturn(recordingDTO.getFilename());
when(azureFinalStorageService.getRecordingDuration(recordingDetails.recordingId()))
.thenReturn(recordingDTO.getDuration());
}

@Test
@DisplayName("Should create a DRAFT edit request, update it and submit it. Should be read-only after submission.")
void editRequestSuccess() throws JsonProcessingException {
CreateEditRequestDTO createEditRequestDTO = new CreateEditRequestDTO();
UUID createEditRequestId = UUID.randomUUID();
createEditRequestDTO.setId(createEditRequestId);
createEditRequestDTO.setStatus(EditRequestStatus.DRAFT);
createEditRequestDTO.setSourceRecordingId(recordingId);

// Create as DRAFT
Response createdAsDraft = upsertEditRequestAndGetResponse(createEditRequestId, createEditRequestDTO);
assertResponseCode(createdAsDraft, 200);
Assertions.assertThat(createdAsDraft.jsonPath().getString("id"))
.isEqualTo(createEditRequestId.toString());
Assertions.assertThat(createdAsDraft.jsonPath().getString("status"))
.isEqualTo(EditRequestStatus.DRAFT.name());
Assertions.assertThat(createdAsDraft.jsonPath().getString("source_recording.id"))
.isEqualTo(createEditRequestDTO.getSourceRecordingId().toString());
Assertions.assertThat(createdAsDraft.jsonPath().getList("edit_instruction.requestedInstructions"))
.isEmpty();

// Update as DRAFT
List<EditCutInstructionDTO> editInstructions = List.of(EditCutInstructionDTO.builder()
.startOfCut("00:00:02")
.endOfCut("00:00:03")
.build());
createEditRequestDTO.setEditInstructions(editInstructions);

Response updatedAsDraft = upsertEditRequestAndGetResponse(createEditRequestId, createEditRequestDTO);
assertResponseCode(updatedAsDraft, 200);
Assertions.assertThat(updatedAsDraft.jsonPath().getString("id"))
.isEqualTo(createEditRequestId.toString());
Assertions.assertThat(updatedAsDraft.jsonPath().getString("status"))
.isEqualTo(EditRequestStatus.DRAFT.name());
Assertions.assertThat(updatedAsDraft.jsonPath().getString("source_recording.id"))
.isEqualTo(createEditRequestDTO.getSourceRecordingId().toString());

Assertions.assertThat(updatedAsDraft.jsonPath().getList("edit_instruction.requestedInstructions"))
.size().isEqualTo(editInstructions.size());
Assertions.assertThat(updatedAsDraft.jsonPath()
.getInt("edit_instruction.requestedInstructions[0].start"))
.isEqualTo(2);
Assertions.assertThat(updatedAsDraft.jsonPath()
.getInt("edit_instruction.requestedInstructions[0].end"))
.isEqualTo(3);

// Submit
createEditRequestDTO.setStatus(EditRequestStatus.SUBMITTED);
createEditRequestDTO.setJointlyAgreed(true);
Response submitted = upsertEditRequestAndGetResponse(createEditRequestId, createEditRequestDTO);
assertResponseCode(submitted, 200);
Assertions.assertThat(submitted.jsonPath().getString("id"))
.isEqualTo(createEditRequestId.toString());
Assertions.assertThat(submitted.jsonPath().getString("status"))
.isEqualTo(EditRequestStatus.SUBMITTED.name());

// Attempt to update edit instructions after submission should fail
List<EditCutInstructionDTO> updatedEditInstructions = List.of(EditCutInstructionDTO.builder()
.startOfCut("00:00:06")
.endOfCut("00:00:07")
.build());
createEditRequestDTO.setEditInstructions(updatedEditInstructions);

Response resubmittedWithChangedInstructions = doPutRequest(
EDIT_ENDPOINT + "/" + createEditRequestId,
OBJECT_MAPPER.writeValueAsString(createEditRequestDTO),
TestingSupportRoles.SUPER_USER
);
assertResponseCode(resubmittedWithChangedInstructions, 400);
Assertions.assertThat(resubmittedWithChangedInstructions.jsonPath().getString("message"))
.isEqualTo(format(
"Cannot alter edit request instructions after submission: "
+ "edit request %s has status %s",
createEditRequestDTO.getId(), createEditRequestDTO.getStatus().toString()
));
}

@Test
@DisplayName("Should record an audit trail when edit request is submitted")
void editRequestSubmissionAuditLog() throws JsonProcessingException {
CreateEditRequestDTO createEditRequestDTO = createEditRequestDTO(recordingId);

// Submit
createEditRequestDTO.setStatus(EditRequestStatus.SUBMITTED);
String requestBody = OBJECT_MAPPER.writeValueAsString(createEditRequestDTO);

Response firstResponse = doPutRequest(
EDIT_ENDPOINT + "/" + createEditRequestDTO.getId(),
requestBody,
TestingSupportRoles.SUPER_USER
);

assertResponseCode(firstResponse, 204);

// TODO: Finish test here when https://tools.hmcts.net/jira/browse/S28-3556 is done
// Response auditResponse = doGetRequest(AUDIT_ENDPOINT...)
}

@Test
@DisplayName("Should not create an edit request with unsafe data in rejection reason fields")
void editRequestWithUnsafeDataRejectionReason() throws JsonProcessingException {
CreateEditRequestDTO createEditRequestDTO = createEditRequestDTO(recordingId);

// Submit
createEditRequestDTO.setStatus(EditRequestStatus.REJECTED);
createEditRequestDTO.setRejectionReason("this & is unsafe");
String requestBody = OBJECT_MAPPER.writeValueAsString(createEditRequestDTO);

Response putResponse = doPutRequest(
EDIT_ENDPOINT + "/" + createEditRequestDTO.getId(),
requestBody,
TestingSupportRoles.SUPER_USER
);
assertResponseCode(putResponse, 400);

assertThat(putResponse.getBody().asString()).contains("contains potentially malicious content");
}

@Test
@DisplayName("Should not create an edit request with unsafe data in approved by fields")
void editRequestWithUnsafeDataApprovedBy() throws JsonProcessingException {
CreateEditRequestDTO createEditRequestDTO = createEditRequestDTO(recordingId);

// Approved
createEditRequestDTO.setStatus(EditRequestStatus.APPROVED);
createEditRequestDTO.setRejectionReason(null);
createEditRequestDTO.setApprovedBy("this & is unsafe");
String requestBody = OBJECT_MAPPER.writeValueAsString(createEditRequestDTO);

Response putResponse = doPutRequest(
EDIT_ENDPOINT + "/" + createEditRequestDTO.getId(),
requestBody,
TestingSupportRoles.SUPER_USER
);
assertResponseCode(putResponse, 400);

assertThat(putResponse.getBody().asString()).contains("contains potentially malicious content");
}

@Test
@DisplayName("Should not create an edit request with unsafe data in reason fields")
void editRequestWithUnsafeDataReason() throws JsonProcessingException {
CreateEditRequestDTO createEditRequestDTO = createEditRequestDTO(recordingId);

// Approved
createEditRequestDTO.setStatus(EditRequestStatus.APPROVED);
List<EditCutInstructionDTO> editInstructions = createEditRequestDTO.getEditInstructions();
editInstructions.forEach(editInstruction -> {
editInstruction.setReason("this & is unsafe");
});
createEditRequestDTO.setEditInstructions(editInstructions);
String requestBody = OBJECT_MAPPER.writeValueAsString(createEditRequestDTO);

Response putResponse = doPutRequest(
EDIT_ENDPOINT + "/" + createEditRequestDTO.getId(),
requestBody,
TestingSupportRoles.SUPER_USER
);
assertResponseCode(putResponse, 400);

assertThat(putResponse.getBody().asString()).contains("contains potentially malicious content");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class RecordingControllerFT extends FunctionalTestBase {
@DisplayName("Scenario: Restore recording")
@Test
void undeleteRecording() {
var recordingDetails = createRecording();
CreateRecordingResponse recordingDetails = createRecording();
assertRecordingExists(recordingDetails.recordingId(), true);

var deleteResponse =
Expand Down
Loading
Loading