diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/RejectedZipByNameEndpointTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/RejectedZipByNameEndpointTest.java new file mode 100644 index 0000000000..db2d649156 --- /dev/null +++ b/src/functionalTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/RejectedZipByNameEndpointTest.java @@ -0,0 +1,107 @@ +package uk.gov.hmcts.reform.bulkscanprocessor.controllers; + +import com.azure.core.util.Context; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.models.BlobItem; +import com.azure.storage.blob.models.DeleteSnapshotsOptionType; +import com.azure.storage.blob.models.ListBlobsOptions; +import io.restassured.RestAssured; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.jayway.awaitility.Awaitility.await; +import static io.restassured.RestAssured.given; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static uk.gov.hmcts.reform.bulkscanprocessor.config.TestConfiguration.SCAN_DELAY; +import static uk.gov.hmcts.reform.bulkscanprocessor.config.TestConfiguration.TEST_URL; + +public class RejectedZipByNameEndpointTest extends BaseFunctionalTest { + + private List filesToDeleteAfterTest = new ArrayList<>(); + + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + } + + @AfterEach + public void tearDown() { + for (String filename : filesToDeleteAfterTest) { + var inBlobClient = inputContainer.getBlobClient(filename); + if (inBlobClient.exists()) { + inBlobClient.deleteWithResponse(DeleteSnapshotsOptionType.INCLUDE, null, null, Context.NONE); + } + + var rejBlobClient = rejectedContainer.getBlobClient(filename); + + if (rejBlobClient.exists()) { + rejBlobClient.deleteWithResponse(DeleteSnapshotsOptionType.INCLUDE, null, null, Context.NONE); + } + } + } + + @Test + public void should_return_rejected_zip_files_by_name() { + String destZipFilename = testHelper.getRandomFilename(); + + testHelper.uploadZipFile( + inputContainer, + Arrays.asList("1111006.pdf"), + null, // missing metadata file + destZipFilename + ); + + filesToDeleteAfterTest.add(destZipFilename); + + await(destZipFilename + " file should be deleted") + .atMost(SCAN_DELAY + 60_000, TimeUnit.MILLISECONDS) + .pollInterval(2, TimeUnit.SECONDS) + .until(() -> testHelper.storageHasFile(inputContainer, destZipFilename), is(false)); + + assertThat(testHelper.storageHasFile(rejectedContainer, destZipFilename)).isTrue(); + assertThat(searchByName(rejectedContainer, destZipFilename)).hasSize(1); + + given() + .baseUri(TEST_URL) + .relaxedHTTPSValidation() + .get("/reports/rejected-zip-files/name/" + destZipFilename) + .then().statusCode(200) + .body("rejected_zip_files.event", hasItem("FILE_VALIDATION_FAILURE")); + + } + + @Test + public void should_return_empty_list_if_there_is_no_rejected_zip_files() { + + String destZipFilename = testHelper.getRandomFilename(); + RestAssured + .given() + .baseUri(TEST_URL) + .relaxedHTTPSValidation() + .get("/reports/rejected-zip-files/name/" + destZipFilename) + .then().statusCode(200) + .assertThat() + .body("count", equalTo(0)); + + } + + private List searchByName(BlobContainerClient container, String fileName) { + ListBlobsOptions listOptions = new ListBlobsOptions(); + listOptions.getDetails().setRetrieveSnapshots(true); + listOptions.setPrefix(fileName); + return container.listBlobs(listOptions, null, null) + .stream() + .collect(toList()); + } + +} diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsControllerTest.java index 84b7cee19d..cec753da80 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsControllerTest.java @@ -630,6 +630,65 @@ void should_return_rejected_zip_files() throws Exception { )); } + @Test + void should_return_rejected_zip_files_by_name() throws Exception { + UUID uuid1 = randomUUID(); + UUID uuid2 = randomUUID(); + + given(rejectedZipFilesService.getRejectedZipFiles("a.zip")) + .willReturn(asList( + new RejectedZipFileItem( + "a.zip", + "A", + LocalDateTime.parse("2021-04-16T09:01:43.029000").toInstant(ZoneOffset.UTC), + uuid1, + "FILE_VALIDATION_FAILURE" + ), + new RejectedZipFileItem( + "a.zip", + "A", + LocalDateTime.parse("2021-04-16T09:01:44.029000").toInstant(ZoneOffset.UTC), + uuid2, + "DOC_SIGNATURE_FAILURE" + ) + )); + + mockMvc + .perform(get("/reports/rejected-zip-files/name/a.zip")) + .andExpect(status().isOk()) + .andExpect(content().json( + "{" + + "'count': 2," + + "'rejected_zip_files': [" + + " {" + + " 'zip_file_name': 'a.zip'," + + " 'container': 'A'," + + " 'processing_started_date_time': '2021-04-16T09:01:43.029'," + + " 'envelope_id': '" + uuid1 + "'," + + " 'event': 'FILE_VALIDATION_FAILURE'" + + " }," + + " {" + + " 'zip_file_name': 'a.zip'," + + " 'container': 'A'," + + " 'processing_started_date_time': '2021-04-16T09:01:44.029'," + + " 'envelope_id': '" + uuid2 + "'," + + " 'event': 'DOC_SIGNATURE_FAILURE'" + + " }" + + "]" + + "}" + )); + } + + @Test + void should_return_empty_list_if_no_rejected_zip_files_match_name() throws Exception { + mockMvc + .perform(get("/reports/rejected-zip-files/name/a.zip")) + .andExpect(status().isOk()) + .andExpect(content().json( + "{}" + )); + } + @Test void should_return_received_scannable_items() throws Exception { given(receivedScannableItemsService.getReceivedScannableItems(LocalDate.parse("2021-04-16"))) diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/RejectedZipFileRepositoryTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/RejectedZipFileRepositoryTest.java index 20317aae47..50167b0825 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/RejectedZipFileRepositoryTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/RejectedZipFileRepositoryTest.java @@ -22,8 +22,10 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static uk.gov.hmcts.reform.bulkscanprocessor.model.common.Classification.EXCEPTION; import static uk.gov.hmcts.reform.bulkscanprocessor.model.common.Event.DOC_FAILURE; +import static uk.gov.hmcts.reform.bulkscanprocessor.model.common.Event.DOC_SIGNATURE_FAILURE; import static uk.gov.hmcts.reform.bulkscanprocessor.model.common.Event.FILE_VALIDATION_FAILURE; import static uk.gov.hmcts.reform.bulkscanprocessor.model.common.Event.ZIPFILE_PROCESSING_STARTED; @@ -160,6 +162,87 @@ void should_return_single_result_by_date_if_envelope_exists_with_multiple_failur ); } + @Test + public void should_return_rejected_zip_files_with_matching_name() { + Instant eventDate = Instant.parse("2019-02-15T14:15:23.456Z"); + + dbHasEvents( + event("c2", "test2.zip", eventDate, DOC_FAILURE), + event("c2", "test2.zip", eventDate, FILE_VALIDATION_FAILURE), + event("c2", "test2.zip", eventDate, DOC_SIGNATURE_FAILURE), + event("c2", "test3.zip", eventDate, FILE_VALIDATION_FAILURE) + ); + + Envelope existingEnvelope + = envelope("c2", "test2.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", "test5"); + dbHasEnvelope(existingEnvelope); + dbHasEnvelope(envelope("c3", "test3.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", null)); + + List result = reportRepo.getRejectedZipFilesReportFor("test2.zip"); + + assertThat(result) + .hasSize(3) + .extracting("zipFileName", "event") + .contains(tuple("test2.zip", "DOC_FAILURE"), + tuple("test2.zip", "FILE_VALIDATION_FAILURE"), + tuple("test2.zip", "DOC_SIGNATURE_FAILURE")) + .doesNotContain(tuple("test3.zip", "FILE_VALIDATION_FAILURE")); + } + + @Test + public void should_group_rejected_zip_files_with_matching_name_if_same_event_same_day() { + Instant eventDate = Instant.parse("2019-02-15T14:15:23.456Z"); + Instant eventDate2 = Instant.parse("2019-02-16T14:15:23.456Z"); + dbHasEvents( + event("c2", "test2.zip", eventDate, FILE_VALIDATION_FAILURE), + event("c2", "test2.zip", eventDate, FILE_VALIDATION_FAILURE), + event("c2", "test2.zip", eventDate2, FILE_VALIDATION_FAILURE), + event("c2", "test2.zip", eventDate2, FILE_VALIDATION_FAILURE) + ); + + Envelope existingEnvelope + = envelope("c2", "test2.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", "test5"); + dbHasEnvelope(existingEnvelope); + dbHasEnvelope(envelope("c3", "test3.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", null)); + + List result = reportRepo.getRejectedZipFilesReportFor("test2.zip"); + + assertThat(result) + .hasSize(2) + .extracting("zipFileName", "event") + .contains(tuple("test2.zip", "FILE_VALIDATION_FAILURE"), tuple("test2.zip", "FILE_VALIDATION_FAILURE")); + } + + @Test + public void should_not_return_rejected_zip_files_with_matching_name_if_not_failure_event() { + Instant eventDate = Instant.parse("2019-02-15T14:15:23.456Z"); + dbHasEvents( + event("c2", "test2.zip", eventDate, ZIPFILE_PROCESSING_STARTED) + ); + Envelope existingEnvelope + = envelope("c2", "test2.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", "test5"); + dbHasEnvelope(existingEnvelope); + + List result = reportRepo.getRejectedZipFilesReportFor("test2.zip"); + + assertThat(result).isEmpty(); + } + + @Test + public void should_not_return_rejected_zip_files_if_no_event_with_matching_name() { + Instant eventDate = Instant.parse("2019-02-15T14:15:23.456Z"); + dbHasEvents( + event("c2", "test2.zip", eventDate, FILE_VALIDATION_FAILURE) + ); + Envelope existingEnvelope + = envelope("c2", "test2.zip", Status.COMPLETED, EXCEPTION, "ccd-id-1", "ccd-action-1", "test5"); + dbHasEnvelope(existingEnvelope); + + List result = reportRepo.getRejectedZipFilesReportFor("test22.zip"); + + assertThat(result).isEmpty(); + } + private void dbHasEvents(ProcessEvent... events) { eventRepo.saveAll(asList(events)); } diff --git a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsController.java b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsController.java index 9d76b72834..485c387747 100644 --- a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsController.java +++ b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/controllers/ReportsController.java @@ -1,6 +1,7 @@ package uk.gov.hmcts.reform.bulkscanprocessor.controllers; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.ClockProvider; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpHeaders; @@ -8,6 +9,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -243,6 +245,31 @@ public RejectedZipFilesResponse getRejectedZipFiles( ); } + /** + * Retrieves rejected zip files by name. + * @param name The name of the rejected zip file + * @return RejectedZipFilesResponse list of rejected zip files matching given name + */ + @GetMapping(path = "/rejected-zip-files/name/{name}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(description = "Retrieves rejected files by name") + @ApiResponse(responseCode = "200", description = "Success") + public RejectedZipFilesResponse getRejectedZipFilesByName(@PathVariable String name) { + List result = rejectedZipFilesService.getRejectedZipFiles(name); + return new RejectedZipFilesResponse( + result.size(), + result + .stream() + .map(file -> new RejectedZipFileData( + file.getZipFileName(), + file.getContainer(), + LocalDateTime.ofInstant(file.getProcessingStartedEventDate(), ZoneId.of("UTC")), + file.getEnvelopeId(), + file.getEvent() + )) + .collect(toList()) + ); + } + /** * Retrieves reconciliation report. * @param statement The reconciliation statement diff --git a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/reports/RejectedZipFileRepository.java b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/reports/RejectedZipFileRepository.java index 9cfde5a5be..7d980aa8dd 100644 --- a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/reports/RejectedZipFileRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/entity/reports/RejectedZipFileRepository.java @@ -34,4 +34,28 @@ public interface RejectedZipFileRepository extends JpaRepository + " envelopeId" ) List getRejectedZipFilesReportFor(@Param("date") LocalDate date); + + @Query( + nativeQuery = true, + value = "SELECT " + + " process_events.container, " + + " process_events.zipfilename, " + + " process_events.event, " + + " MIN(process_events.createdat) AS processingStartedEventDate, " + + " Cast(envelopes.id as varchar) as envelopeId " + + "FROM process_events " + + "LEFT OUTER JOIN envelopes " + + " ON envelopes.container = process_events.container " + + " AND envelopes.zipfilename = process_events.zipfilename " + + "WHERE process_events.event " + + " IN ('DOC_FAILURE', 'FILE_VALIDATION_FAILURE', 'DOC_SIGNATURE_FAILURE') " + + " AND process_events.zipfilename = :name " + + "GROUP BY process_events.container, " + + " process_events.zipfilename, " + + " process_events.event, " + + " process_events.createdat, " + + " envelopeId " + + "ORDER BY process_events.createdat DESC" + ) + List getRejectedZipFilesReportFor(@Param("name") String name); } diff --git a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/model/common/Event.java b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/model/common/Event.java index 1fba3fdfc5..9647793263 100644 --- a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/model/common/Event.java +++ b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/model/common/Event.java @@ -19,5 +19,6 @@ public enum Event { COMPLETED, // the processing of the envelope completed successfully // when envelope status needs to be updated for reprocessing (used manually to set the event with reason) MANUAL_STATUS_CHANGE, - MANUAL_RETRIGGER_PROCESSING + MANUAL_RETRIGGER_PROCESSING, + DOC_SIGNATURE_FAILURE } diff --git a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesService.java b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesService.java index b8cdba072d..094c93cb41 100644 --- a/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesService.java +++ b/src/main/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesService.java @@ -31,4 +31,13 @@ public RejectedZipFilesService(RejectedZipFileRepository rejectedZipFileReposito public List getRejectedZipFiles(LocalDate date) { return rejectedZipFileRepository.getRejectedZipFilesReportFor(date); } + + /** + * Get the list of rejected zip files with a specific name. + * @param name the name the rejected zip files should match + * @return The list of rejected zip files + */ + public List getRejectedZipFiles(String name) { + return rejectedZipFileRepository.getRejectedZipFilesReportFor(name); + } } diff --git a/src/test/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesServiceTest.java b/src/test/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesServiceTest.java index cba23359bb..3e6971ba0c 100644 --- a/src/test/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/bulkscanprocessor/services/reports/RejectedZipFilesServiceTest.java @@ -61,6 +61,38 @@ void should_return_rejected_files() { assertThat(res).isSameAs(rejectedFiles); } + @Test + public void should_return_rejected_zip_files_by_name() { + List rejectedFiles = asList( + new RejectedZipFileItem( + "test1.zip", + "c1", + Instant.now(), + null, + "FILE_VALIDATION_FAILURE" + ), + new RejectedZipFileItem( + "test1.zip", + "c2", + Instant.now(), + null, + "DOC_SIGNATURE_FAILURE" + ) + ); + + given(rejectedZipFileRepository.getRejectedZipFilesReportFor("test1.zip")) + .willReturn(rejectedFiles); + + List res = rejectedZipFilesService.getRejectedZipFiles("test1.zip"); + assertThat(res).isSameAs(rejectedFiles); + } + + @Test + public void should_return_empty_list_if_no_rejected_zip_files_match_name() { + List res = rejectedZipFilesService.getRejectedZipFiles("test1.zip"); + assertThat(res).isEmpty(); + } + @Test void should_rethrow_exception() { // given