diff --git a/README.md b/README.md index 30494e45..67b8fd62 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # FaCT Data API + [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=uk.gov.hmcts.reform%3Afact-data-api&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=uk.gov.hmcts.reform%3Afact-data-api) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=uk.gov.hmcts.reform%3Afact-data-api&metric=coverage)](https://sonarcloud.io/summary/new_code?id=uk.gov.hmcts.reform%3Afact-data-api) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=uk.gov.hmcts.reform%3Afact-data-api&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=uk.gov.hmcts.reform%3Afact-data-api) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuditControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuditControllerFunctionalTest.java index 4bec9646..6b661d64 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuditControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuditControllerFunctionalTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.NO_CONTENT; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; @Feature("Audit Controller") @@ -94,6 +95,42 @@ void shouldReturnExpectedAuditActionsForCourtAndAddressFlow() { .containsExactly("DELETE", "UPDATE", ACTION_INSERT); } + @Test + @DisplayName("GET /audits/{auditId}/v1 returns a single audit record for an existing id") + void shouldReturnSingleAuditRecordById() { + final UUID courtId = TestDataHelper.createCourt(http, "Test Court Audit By Id"); + final String auditsPath = String.format( + "/audits/v1?pageNumber=0&pageSize=%d&fromDate=%s&courtId=%s", + 50, + LocalDate.now().minusDays(1), + courtId + ); + + final Response auditsResponse = http.doGet(auditsPath); + AssertionHelper.assertStatus(auditsResponse, OK); + + final String auditId = auditsResponse.jsonPath().getString("content[0].id"); + assertThat(auditId).as("Expected at least one audit id to be present").isNotBlank(); + + final Response singleAuditResponse = http.doGet("/audits/" + auditId + "/v1"); + AssertionHelper.assertStatus(singleAuditResponse, OK); + + assertThat(singleAuditResponse.jsonPath().getString("id")) + .as("Returned audit id should match requested id") + .isEqualTo(auditId); + + assertThat(singleAuditResponse.jsonPath().getString("subjectId")) + .as("Returned audit should belong to the created court") + .isEqualTo(courtId.toString()); + } + + @Test + @DisplayName("GET /audits/{auditId}/v1 returns not found for unknown id") + void shouldReturnNotFoundForUnknownAuditId() { + final Response response = http.doGet("/audits/" + UUID.randomUUID() + "/v1"); + AssertionHelper.assertStatus(response, NOT_FOUND); + } + private static List extractActionTypesForEntity(final Response response, final String actionEntity) { return response.jsonPath().getList( "content.findAll { it.actionEntity == '" + actionEntity + "' }.actionType", diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditingTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditingTest.java index 3891496c..e9a7db14 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditingTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditingTest.java @@ -366,6 +366,7 @@ private void assertAuditActionsForEntity(Court owningCourt, String testedEntity, 1000, CREATED_AFTER, null, + null, owningCourt.getId().toString(), null, null diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java index f0510258..bbbd0373 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java @@ -11,6 +11,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentre; import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditActionType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; import uk.gov.hmcts.reform.fact.data.api.repositories.AuditRepository; import uk.gov.hmcts.reform.fact.data.api.repositories.CourtRepository; @@ -26,6 +27,7 @@ import java.util.Optional; import java.util.UUID; +import com.jayway.jsonpath.JsonPath; import io.qameta.allure.Feature; import org.apache.commons.lang3.RandomStringUtils; import org.hamcrest.Description; @@ -345,12 +347,101 @@ void getFilteredAndPaginatedAuditsReturnsBadRequestForInvalidPageNumber() throws .andExpect(status().isBadRequest()); } + @Test + @DisplayName("GET /audits/v1 returns results when subjectType is provided") + void getFilteredAndPaginatedAuditsReturnsResultsWhenSubjectTypeProvided() throws Exception { + createTestCourts(2); + createTestServiceCentre(); + + mvc.perform(get("/audits/v1") + .param("pageNumber", "0") + .param("pageSize", "10") + .param("fromDate", LocalDate.now().toString()) + .param("subjectType", AuditSubjectType.COURT.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").isArray()) + .andExpect(jsonPath("$.content.length()").value(2)) + .andExpect(jsonPath("$.content[*].subjectType") + .value(allElementsEqual(AuditSubjectType.COURT.name()))) + .andExpect(jsonPath("$.page.number").value(0)) + .andExpect(jsonPath("$.page.totalElements").value(2)); + } + + @Test + @DisplayName("GET /audits/v1 returns 400 when subjectType is invalid") + void getFilteredAndPaginatedAuditsReturnsBadRequestForInvalidSubjectType() throws Exception { + mvc.perform(get("/audits/v1") + .param("pageNumber", "0") + .param("pageSize", "5") + .param("fromDate", LocalDate.now().toString()) + .param("subjectType", "NOT_A_REAL_TYPE")) + .andExpect(status().isBadRequest()); + } + @Test @DisplayName("DELETE /audits/v1 returns 204") void deleteAuditsRemovesExpiredAuditsAndReturns204() throws Exception { mvc.perform(delete("/audits/v1")).andExpect(status().isNoContent()); } + @Test + @DisplayName("GET /audits/subjectoptions/v1 returns subject options map") + void getSubjectNameAndIdMapReturnsResults() throws Exception { + // Seed at least one court so COURT options are present + createTestCourts(1); + createTestServiceCentre(); + + mvc.perform(get("/audits/subjectoptions/v1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.COURT").isArray()) + .andExpect(jsonPath("$.SERVICE_CENTRE").isArray()) + .andExpect(jsonPath("$.COURT[0].name").exists()) + .andExpect(jsonPath("$.COURT[0].id").exists()) + .andExpect(jsonPath("$.SERVICE_CENTRE[0].name").exists()) + .andExpect(jsonPath("$.SERVICE_CENTRE[0].id").exists()); + } + + @Test + @DisplayName("GET /audits/{auditId}/v1 returns audit when id is valid") + void getAuditByIdReturnsAuditWhenIdIsValid() throws Exception { + Court court = createTestCourts(1).getFirst(); + + String auditsResponse = mvc.perform(get("/audits/v1") + .param("pageNumber", "0") + .param("pageSize", "10") + .param("fromDate", LocalDate.now().toString()) + .param("courtId", court.getId().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").isArray()) + .andExpect(jsonPath("$.content.length()").value(1)) + .andReturn() + .getResponse() + .getContentAsString(); + + String auditId = JsonPath.read(auditsResponse, "$.content[0].id"); + + mvc.perform(get("/audits/" + auditId + "/v1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(auditId)) + .andExpect(jsonPath("$.subjectId").value(court.getCourtId().toString())) + .andExpect(jsonPath("$.actionType").exists()) + .andExpect(jsonPath("$.createdAt").exists()); + } + + @Test + @DisplayName("GET /audits/{auditId}/v1 returns 400 when auditId is not a UUID") + void getAuditByIdReturnsBadRequestWhenAuditIdIsInvalid() throws Exception { + mvc.perform(get("/audits/not-a-valid-uuid/v1")) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("GET /audits/{auditId}/v1 returns 404 when audit does not exist") + void getAuditByIdReturnsNotFoundWhenAuditDoesNotExist() throws Exception { + mvc.perform(get("/audits/" + UUID.randomUUID() + "/v1")) + .andExpect(status().isNotFound()); + } + private List createTestCourts(int num) { List results = new ArrayList<>(); for (int i = 0; i < num; i++) { diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditController.java index b951e04b..85704706 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditController.java @@ -1,6 +1,8 @@ package uk.gov.hmcts.reform.fact.data.api.controllers; import uk.gov.hmcts.reform.fact.data.api.entities.Audit; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidDateRangeException; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController; @@ -8,6 +10,9 @@ import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidUUID; import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -21,6 +26,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -52,6 +58,7 @@ public ResponseEntity> getFilteredAndPaginatedAudits( @PositiveOrZero(message = "pageNumber must be greater than or equal to 0") int pageNumber, @RequestParam(name = "pageSize", defaultValue = "25") @Positive(message = "pageSize must be greater than 0") int pageSize, + @RequestParam(name = "subjectType", required = false) AuditSubjectType subjectType, @RequestParam(name = "courtId", required = false) @ValidUUID(allowNull = true) String courtId, @RequestParam(name = "serviceCentreId", required = false) @ValidUUID(allowNull = true) String serviceCentreId, @RequestParam(name = "email", required = false) @@ -80,6 +87,7 @@ public ResponseEntity> getFilteredAndPaginatedAudits( pageSize, fromDate, toDate, + subjectType, courtId, serviceCentreId, emailMatch @@ -87,6 +95,21 @@ public ResponseEntity> getFilteredAndPaginatedAudits( ); } + @GetMapping("/{auditId}/v1") + @Operation( + summary = "Retrieve a single audit record", + description = "Fetch the audit record that relates to the given id" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved Audit record"), + @ApiResponse(responseCode = "400", description = "Invalid request parameters supplied"), + @ApiResponse(responseCode = "404", description = "Audit record with the given id was not found") + }) + public ResponseEntity getAuditById( + @ValidUUID @PathVariable String auditId) { + return ResponseEntity.ok(auditService.getAuditById(UUID.fromString(auditId))); + } + @DeleteMapping("/v1") @Operation( summary = "Remove expired audits", @@ -100,4 +123,15 @@ public ResponseEntity removeExpiredAuditEntries() { return ResponseEntity.noContent().build(); } + @GetMapping("/subjectoptions/v1") + @Operation( + summary = "Retrieve the complete set of name->id value pairs for all supported audit subjects", + description = "Fetches a Map of all subject names with their corresponding ids, mapped to their subject type" + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved map of subject-> name+id pairs") + }) + public ResponseEntity>> getSubjectNameAndIdMap() { + return ResponseEntity.ok(this.auditService.getSubjectNameAndIdMap()); + } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Audit.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Audit.java index d4f60fc9..4b5fbc96 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Audit.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Audit.java @@ -62,7 +62,7 @@ public class Audit { @Schema(description = "The associated User") @ManyToOne - @JoinColumn(name = "user_id", insertable = false, updatable = false) + @JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false) private User user; @Schema(description = "The action type") diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/AuditSubjectType.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/AuditSubjectType.java index 6d97135c..4427dcf3 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/AuditSubjectType.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/AuditSubjectType.java @@ -1,5 +1,8 @@ package uk.gov.hmcts.reform.fact.data.api.entities.types; +/** + * Subject types that are applicable for auditing. + */ public enum AuditSubjectType { COURT, SERVICE_CENTRE diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/NameAndId.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/NameAndId.java new file mode 100644 index 00000000..ff3084e5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/NameAndId.java @@ -0,0 +1,11 @@ +package uk.gov.hmcts.reform.fact.data.api.entities.types; + +import java.util.UUID; + +/** + * re-usable Name and Id pair object for use in repository results. + * + * @param name the entity Name + * @param id the entity Id + */ +public record NameAndId(String name, UUID id) {} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/AuditRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/AuditRepository.java index daf2df1b..c3a250e2 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/AuditRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/AuditRepository.java @@ -4,10 +4,12 @@ import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import java.time.ZonedDateTime; +import java.util.Optional; import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -18,21 +20,38 @@ public interface AuditRepository extends JpaRepository { // ---------------------------------------------------- // Standard paged queries + @EntityGraph(attributePaths = {"user"}) Page findByCreatedAtAfter( ZonedDateTime createdAt, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) Page findByCreatedAtBetween( ZonedDateTime createdAtAfter, ZonedDateTime createdAtBefore, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) + Page findBySubjectTypeAndCreatedAtAfter( + AuditSubjectType subjectType, + ZonedDateTime createdAtAfter, + Pageable pageable); + + @EntityGraph(attributePaths = {"user"}) Page findBySubjectIdAndSubjectTypeAndCreatedAtAfter( UUID subjectId, AuditSubjectType subjectType, ZonedDateTime createdAt, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) + Page findBySubjectTypeAndCreatedAtBetween( + AuditSubjectType subjectType, + ZonedDateTime createdAtAfter, + ZonedDateTime createdAtBefore, + Pageable pageable); + + @EntityGraph(attributePaths = {"user"}) Page findBySubjectIdAndSubjectTypeAndCreatedAtBetween( UUID subjectId, AuditSubjectType subjectType, @@ -40,16 +59,14 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtBetween( ZonedDateTime createdAtBefore, Pageable pageable); + // ---------------------------------------------------- // Complex paged queries String SELECT_WITH_USER_JOIN = """ - select - a - from - Audit a - join - User u on a.userId = u.id + select a + from Audit a + join User u on a.userId = u.id """; String WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER = """ @@ -65,14 +82,17 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtBetween( """; String AND_SUBJECT_EQUALS = " and a.subjectId = :subjectId and a.subjectType = :subjectType"; + String AND_SUBJECT_TYPE_EQUALS = " and a.subjectType = :subjectType"; + @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER) Page findByCreatedAtAfterAndEmailAddressLike( ZonedDateTime createdAtAfter, String email, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_BETWEEN) Page findByCreatedAtBetweenAndEmailAddressLike( ZonedDateTime createdAtAfter, @@ -80,6 +100,7 @@ Page findByCreatedAtBetweenAndEmailAddressLike( String email, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER + AND_SUBJECT_EQUALS) Page findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( UUID subjectId, @@ -88,6 +109,7 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( String email, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_BETWEEN + AND_SUBJECT_EQUALS) Page findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( UUID subjectId, @@ -97,9 +119,28 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( String email, Pageable pageable); + @EntityGraph(attributePaths = {"user"}) + @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER + AND_SUBJECT_TYPE_EQUALS) + Page findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( + AuditSubjectType subjectType, + ZonedDateTime createdAtAfter, + String email, + Pageable pageable); + + @EntityGraph(attributePaths = {"user"}) + @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_BETWEEN + AND_SUBJECT_TYPE_EQUALS) + Page findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( + AuditSubjectType subjectType, + ZonedDateTime createdAtAfter, + ZonedDateTime createdAtBefore, + String email, + Pageable pageable); + + @EntityGraph(attributePaths = {"user"}) + Optional findWithUserById(UUID id); + // ---------------------------------------------------- // Housekeeping queries void deleteAllByCreatedAtBefore(ZonedDateTime createdAtBefore); - } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtRepository.java index a0a00604..2c66ffe2 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import uk.gov.hmcts.reform.fact.data.api.entities.Court; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import java.util.List; import java.util.Optional; @@ -139,4 +140,18 @@ ILIKE REPLACE(CONCAT('%', :query, '%'), ' ', '') */ Optional findNameAndSlugById(UUID id); + /** + * Retrieve all court names mapped to their IDs. + * + * @return the {@link List} of {@link NameAndId} objects + */ + @Query( + value = """ + SELECT c.name, c.id + FROM court c + ORDER BY c.name + """, + nativeQuery = true + ) + List findAllNameAndId(); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/ServiceCentreRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/ServiceCentreRepository.java index f27b7873..dfc11a09 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/ServiceCentreRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/ServiceCentreRepository.java @@ -7,6 +7,7 @@ import uk.gov.hmcts.reform.fact.data.api.dto.ServiceCentreWithDistance; import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentre; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import java.util.List; import java.util.Optional; @@ -90,4 +91,19 @@ List findNearestByServiceAreaAndAreaOfLawAndCatchment @Param("lon") double lon, @Param("limit") int limit ); + + /** + * Retrieve all service centre names mapped to their IDs. + * + * @return the {@link List} of {@link NameAndId} objects + */ + @Query( + value = """ + SELECT sc.name, sc.id + FROM service_centre sc + ORDER BY sc.name + """, + nativeQuery = true + ) + List findAllNameAndId(); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/AuditService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/AuditService.java index 6137a7db..680aed57 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/AuditService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/AuditService.java @@ -3,12 +3,16 @@ import uk.gov.hmcts.reform.fact.data.api.config.properties.AuditConfigurationProperties; import uk.gov.hmcts.reform.fact.data.api.entities.Audit; import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; +import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.AuditRepository; import java.time.LocalDate; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -26,6 +30,8 @@ public class AuditService { private final AuditRepository auditRepository; + private final CourtService courtService; + private final ServiceCentreService serviceCentreService; private final AuditConfigurationProperties auditConfiguration; /** @@ -41,8 +47,8 @@ public class AuditService { * @return a {@link Page} of {@link Audit} results. */ public Page getFilteredAndPaginatedAudits(int pageNumber, int pageSize, @NonNull LocalDate fromDate, - LocalDate toDate, String courtId, String serviceCentreId, - String email) { + LocalDate toDate, AuditSubjectType subjectType, String courtId, + String serviceCentreId, String email) { Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending()); @@ -56,7 +62,7 @@ public Page getFilteredAndPaginatedAudits(int pageNumber, int pageSize, @ ZoneOffset.UTC )).orElse(null); - SubjectFilter subjectFilter = resolveSubjectFilter(courtId, serviceCentreId); + SubjectFilter subjectFilter = resolveSubjectFilter(courtId, serviceCentreId, subjectType); return performAuditQuery(fromDateTime, toDateTime, subjectFilter, email, pageable); } @@ -80,7 +86,12 @@ private Page performAuditQuery(ZonedDateTime fromDateTime, ZonedDateTime boolean hasEmail = email != null && !email.isBlank(); if (subjectFilter != null) { - return performSubjectAuditQuery(fromDateTime, toDateTime, subjectFilter, email, pageable); + if (subjectFilter.subjectId != null) { + return performSubjectAuditQuery(fromDateTime, toDateTime, subjectFilter, email, pageable); + } else { + return performSubjectTypeAuditQuery( + fromDateTime, toDateTime, subjectFilter.subjectType(), email, pageable); + } } if (hasToDate && hasEmail) { return auditRepository.findByCreatedAtBetweenAndEmailAddressLike( @@ -118,7 +129,29 @@ private Page performSubjectAuditQuery(ZonedDateTime fromDateTime, ZonedDa subjectId, subjectType, fromDateTime, pageable); } - private static SubjectFilter resolveSubjectFilter(String courtId, String serviceCentreId) { + private Page performSubjectTypeAuditQuery(ZonedDateTime fromDateTime, ZonedDateTime toDateTime, + AuditSubjectType subjectType, String email, Pageable pageable) { + boolean hasToDate = toDateTime != null; + boolean hasEmail = email != null && !email.isBlank(); + + if (hasToDate && hasEmail) { + return auditRepository.findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( + subjectType, fromDateTime, toDateTime, email, pageable); + } + if (hasToDate) { + return auditRepository.findBySubjectTypeAndCreatedAtBetween( + subjectType, fromDateTime, toDateTime, pageable); + } + if (hasEmail) { + return auditRepository.findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( + subjectType, fromDateTime, email, pageable); + } + return auditRepository.findBySubjectTypeAndCreatedAtAfter( + subjectType, fromDateTime, pageable); + } + + private static SubjectFilter resolveSubjectFilter(String courtId, String serviceCentreId, + AuditSubjectType subjectType) { if (courtId != null && serviceCentreId != null) { throw new InvalidParameterCombinationException("Only one of courtId or serviceCentreId can be provided"); } @@ -128,8 +161,38 @@ private static SubjectFilter resolveSubjectFilter(String courtId, String service if (serviceCentreId != null) { return new SubjectFilter(UUID.fromString(serviceCentreId), AuditSubjectType.SERVICE_CENTRE); } + // if a subject type is provided without a specific subject id, we can still filter by subject type + if (subjectType != null) { + return new SubjectFilter(null, subjectType); + } return null; } private record SubjectFilter(UUID subjectId, AuditSubjectType subjectType) {} + + /** + * Retrieves the complete set of supported audit subjects with their corresponding name and id pairs. + * + * @return a {@link Map} of {@link AuditSubjectType} to a list of {@link NameAndId} pairs + * representing the names and ids of the subjects. + */ + public Map> getSubjectNameAndIdMap() { + return Map.of( + AuditSubjectType.COURT, courtService.getAllCourtNameAndIds(), + AuditSubjectType.SERVICE_CENTRE, serviceCentreService.getAllServiceCentreNameAndIds() + ); + } + + /** + * Retrieves an {@link Audit} by its id. + * + * @param auditId the ID of the audit record. + * @return the {@link Audit} record with the specified ID. + * @throws NotFoundException if no audit record is found with the given ID. + */ + public Audit getAuditById(final UUID auditId) { + return auditRepository.findWithUserById(auditId).orElseThrow(() -> + new NotFoundException("Audit not found, ID: " + auditId) + ); + } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtService.java index b3aef36c..8fca8c55 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtService.java @@ -6,6 +6,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.CourtDetails; import uk.gov.hmcts.reform.fact.data.api.entities.Region; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.CourtDetailsRepository; @@ -213,6 +214,24 @@ public CourtDetails getCourtDetailsBySlug(String courtSlug) { ); } + /** + * Get all court details. + * + * @return The list of court details entities. + */ + public List getAllCourtDetails() { + return courtDetailsRepository.findAll(); + } + + /** + * get all court names mapped to their ids. + * + * @return a {@link List} of court names with their ids. + */ + public List getAllCourtNameAndIds() { + return courtRepository.findAllNameAndId(); + } + /** * Marks courts received from CaTH as open and records which MRD IDs could not be matched. * diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreService.java index e368a52b..5dba03fb 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreService.java @@ -7,6 +7,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentre; import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentreDetails; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.ServiceAreaRepository; import uk.gov.hmcts.reform.fact.data.api.repositories.ServiceCentreDetailsRepository; @@ -133,6 +134,15 @@ public long deleteServiceCentresByNamePrefix(String serviceCentreNamePrefix) { return serviceCentresToDelete.size(); } + /** + * get all service centre names mapped to their ids. + * + * @return a {@link List} of service centre names with their ids. + */ + public List getAllServiceCentreNameAndIds() { + return serviceCentreRepository.findAllNameAndId(); + } + /** * Convert a service centre name into slug format. * diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/TestingSupportService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/TestingSupportService.java index 68d56a15..74870970 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/TestingSupportService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/TestingSupportService.java @@ -90,7 +90,7 @@ public class TestingSupportService { // used to generate alphanumeric strings private static final char[] ALPHA_NUMERICS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); private static final String TESTING_SUPPORT_USER_EMAIL = "testing-support-not-a-real-user@justice.gov.uk"; - private static final UUID TESTING_SUPPORT_USER_SSO_ID = UUID.fromString("00000000-0000-0000-0000-000000000002"); + private static final UUID TESTING_SUPPORT_USER_SSO_ID = UUID.fromString("00000000-0000-4000-a000-000000000002"); // ------------------------------------------------------------------------ // static data caches - we load these once and then reuse for all generated courts @@ -387,6 +387,7 @@ private ServiceCentre createServiceCentre(String name, UUID regionId, boolean ad return serviceCentreService.createServiceCentre(serviceCentre); } + @Synchronized private UUID getTestingSupportUserId() { User user = User.builder() .email(TESTING_SUPPORT_USER_EMAIL) diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java index 4b7883c7..341d2741 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/AuditControllerTest.java @@ -5,19 +5,23 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import uk.gov.hmcts.reform.fact.data.api.entities.Audit; import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Change; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidDateRangeException; +import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; import uk.gov.hmcts.reform.fact.data.api.services.AuditService; import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -62,6 +66,7 @@ void getFilteredAndPaginatedAuditsReturns200() { any(LocalDate.class), isNull(), isNull(), + isNull(), isNull() )).thenReturn(auditPage); @@ -71,6 +76,7 @@ void getFilteredAndPaginatedAuditsReturns200() { null, null, null, + null, LocalDate.now().minusDays(1), LocalDate.now() ); @@ -91,6 +97,7 @@ void getFilteredAndPaginatedAuditsThrowsInvalidDateRangeExceptionForInvalidDateR null, null, null, + null, fromDate, toDate ) @@ -104,6 +111,7 @@ void getFilteredAndPaginatedAuditsThrowsInvalidParameterCombinationForBothSubjec auditController.getFilteredAndPaginatedAudits( PAGE_NUMBER, PAGE_SIZE, + null, COURT_ID.toString(), SERVICE_CENTRE_ID.toString(), null, @@ -113,6 +121,64 @@ void getFilteredAndPaginatedAuditsThrowsInvalidParameterCombinationForBothSubjec ); } + @Test + void getSubjectNameAndIdMapReturns200() { + Map> subjectMap = Map.of( + AuditSubjectType.COURT, List.of( + new NameAndId("Exeter Law Courts", UUID.randomUUID()) + ), + AuditSubjectType.SERVICE_CENTRE, List.of( + new NameAndId("Traffic Enforcement Centre", UUID.randomUUID()) + ) + ); + + when(auditService.getSubjectNameAndIdMap()).thenReturn(subjectMap); + + ResponseEntity>> response = auditController.getSubjectNameAndIdMap(); + + assertEquals(HttpStatus.OK, response.getStatusCode(), RESPONSE_STATUS_MISMATCH); + assertEquals(subjectMap, response.getBody(), RESPONSE_BODY_MISMATCH); + verify(auditService).getSubjectNameAndIdMap(); + } + + @Test + void getAuditByIdReturns200() { + Audit audit = createAudit(); + + when(auditService.getAuditById(AUDIT_ID)).thenReturn(audit); + + ResponseEntity response = auditController.getAuditById(AUDIT_ID.toString()); + + assertEquals(HttpStatus.OK, response.getStatusCode(), RESPONSE_STATUS_MISMATCH); + assertEquals(audit, response.getBody(), RESPONSE_BODY_MISMATCH); + verify(auditService).getAuditById(AUDIT_ID); + } + + @Test + void getAuditByIdPropagatesExceptionWhenAuditDoesNotExist() { + NotFoundException exception = new NotFoundException("Audit not found"); + UUID missingAuditId = UUID.randomUUID(); + + when(auditService.getAuditById(missingAuditId)).thenThrow(exception); + + String missingAuditIdString = missingAuditId.toString(); + + NotFoundException thrown = assertThrows( + NotFoundException.class, + () -> auditController.getAuditById(missingAuditIdString)); + + assertEquals("Audit not found", thrown.getMessage()); + verify(auditService).getAuditById(missingAuditId); + } + + @Test + void getAuditByIdThrowsIllegalArgumentExceptionForInvalidUuid() { + assertThrows( + IllegalArgumentException.class, + () -> auditController.getAuditById("not-a-valid-uuid") + ); + } + private Audit createAudit() { return Audit.builder() .id(AUDIT_ID) diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/AuditServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/AuditServiceTest.java index 05f94eef..688908ca 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/AuditServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/AuditServiceTest.java @@ -14,13 +14,17 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Change; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; +import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.AuditRepository; import java.time.LocalDate; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -59,6 +63,10 @@ class AuditServiceTest { @Mock private AuditRepository auditRepository; @Mock + private CourtService courtService; + @Mock + private ServiceCentreService serviceCentreService; + @Mock private AuditConfigurationProperties auditConfigurationProperties; @InjectMocks @@ -83,7 +91,8 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCreatedAtAfter() { null, null, null, - null + null, + null ); assertThat(result.getContent()).hasSize(1); @@ -110,6 +119,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCreatedAtBetween() { toDate, null, null, + null, null ); @@ -136,6 +146,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfter() { PAGE_SIZE, fromDate, null, + null, COURT_ID.toString(), null, null @@ -165,6 +176,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetween() { PAGE_SIZE, fromDate, toDate, + null, COURT_ID.toString(), null, null @@ -196,6 +208,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCreatedAtAfterAndEmailAddressL null, null, null, + null, EMAIL ); @@ -224,6 +237,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCreatedAtBetweenAndEmailAddres toDate, null, null, + null, EMAIL ); @@ -251,6 +265,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfterAndEma PAGE_SIZE, fromDate, null, + null, COURT_ID.toString(), null, EMAIL @@ -281,6 +296,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetweenAndE PAGE_SIZE, fromDate, toDate, + null, COURT_ID.toString(), null, EMAIL @@ -325,6 +341,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByServiceCentreIdAndCreatedAtAft fromDate, null, null, + null, SERVICE_CENTRE_ID.toString(), null ); @@ -343,6 +360,7 @@ void shouldThrowInvalidParameterCombinationExceptionWhenBothSubjectIdsProvided() PAGE_SIZE, fromDate, null, + null, COURT_ID.toString(), SERVICE_CENTRE_ID.toString(), null @@ -354,7 +372,7 @@ void shouldThrowInvalidParameterCombinationExceptionWhenBothSubjectIdsProvided() void shouldThrowNullPointerExceptionWhenFromDateIsNull() { assertThrows( NullPointerException.class, () -> { - auditService.getFilteredAndPaginatedAudits(0, 1, null, null, null, null, null); + auditService.getFilteredAndPaginatedAudits(0, 1, null, null, null,null, null, null); } ); } @@ -376,6 +394,7 @@ void shouldCallSimpleQueryForBlankEmail() { null, null, null, + null, "" ); assertThat(result.getContent()).hasSize(1); @@ -388,6 +407,7 @@ void shouldCallSimpleQueryForBlankEmail() { null, null, null, + null, " " ); assertThat(result.getContent()).hasSize(1); @@ -397,6 +417,246 @@ void shouldCallSimpleQueryForBlankEmail() { ); } + @Test + void getSubjectNameAndIdMapShouldAllSubjectData() { + List courts = List.of( + new NameAndId("Birmingham Civil and Family Justice Centre", UUID.randomUUID()), + new NameAndId("Exeter Law Courts", UUID.randomUUID()) + ); + List serviceCentres = List.of( + new NameAndId("Traffic Enforcement Centre", UUID.randomUUID()) + ); + when(courtService.getAllCourtNameAndIds()).thenReturn(courts); + when(serviceCentreService.getAllServiceCentreNameAndIds()).thenReturn(serviceCentres); + + Map> result = auditService.getSubjectNameAndIdMap(); + + assertThat(result).hasSize(2).containsKeys(AuditSubjectType.COURT, AuditSubjectType.SERVICE_CENTRE); + assertThat(result.get(AuditSubjectType.COURT)).containsExactlyElementsOf(courts); + assertThat(result.get(AuditSubjectType.SERVICE_CENTRE)).containsExactlyElementsOf(serviceCentres); + verify(courtService).getAllCourtNameAndIds(); + verify(serviceCentreService).getAllServiceCentreNameAndIds(); + } + + @Test + void getSubjectNameAndIdMapShouldReturnEmptyListsWhenNoSubjectsExist() { + when(courtService.getAllCourtNameAndIds()).thenReturn(List.of()); + when(serviceCentreService.getAllServiceCentreNameAndIds()).thenReturn(List.of()); + + Map> result = auditService.getSubjectNameAndIdMap(); + + assertThat(result).containsKeys(AuditSubjectType.COURT, AuditSubjectType.SERVICE_CENTRE); + assertThat(result.get(AuditSubjectType.COURT)).isEmpty(); + assertThat(result.get(AuditSubjectType.SERVICE_CENTRE)).isEmpty(); + verify(courtService).getAllCourtNameAndIds(); + verify(serviceCentreService).getAllServiceCentreNameAndIds(); + } + + @Test + void getAuditByIdShouldReturnAuditWhenRecordExists() { + Audit audit = createAudit(); + + when(auditRepository.findWithUserById(AUDIT_ID)).thenReturn(Optional.of(audit)); + + Audit result = auditService.getAuditById(AUDIT_ID); + + assertThat(result).isSameAs(audit); + verify(auditRepository).findWithUserById(AUDIT_ID); + } + + @Test + void getAuditByIdShouldThrowNotFoundExceptionWhenRecordDoesNotExist() { + when(auditRepository.findWithUserById(AUDIT_ID)).thenReturn(Optional.empty()); + + NotFoundException exception = assertThrows( + NotFoundException.class, + () -> auditService.getAuditById(AUDIT_ID) + ); + + assertThat(exception.getMessage()).isEqualTo("Audit not found, ID: " + AUDIT_ID); + verify(auditRepository).findWithUserById(AUDIT_ID); + } + + @Test + void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtAfterWhenOnlySubjectTypeProvided() { + when(auditRepository.findBySubjectTypeAndCreatedAtAfter( + eq(AuditSubjectType.COURT), + eq(fromDateTime), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page result = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + null, + AuditSubjectType.COURT, + null, + null, + null + ); + + assertThat(result.getContent()).hasSize(1); + verify(auditRepository).findBySubjectTypeAndCreatedAtAfter( + eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + ); + } + + @Test + void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtBetweenWhenSubjectTypeAndToDateProvided() { + when(auditRepository.findBySubjectTypeAndCreatedAtBetween( + eq(AuditSubjectType.COURT), + eq(fromDateTime), + eq(toDateTime), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page result = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + toDate, + AuditSubjectType.COURT, + null, + null, + null + ); + + assertThat(result.getContent()).hasSize(1); + verify(auditRepository).findBySubjectTypeAndCreatedAtBetween( + eq(AuditSubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) + ); + } + + @Test + void shouldCallFindBySubjectTypeAndCreatedAtAfterAndEmailAddressLikeWhenSubjectTypeAndEmailProvided() { + when(auditRepository.findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( + eq(AuditSubjectType.SERVICE_CENTRE), + eq(fromDateTime), + eq(EMAIL), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page result = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + null, + AuditSubjectType.SERVICE_CENTRE, + null, + null, + EMAIL + ); + + assertThat(result.getContent()).hasSize(1); + verify(auditRepository).findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( + eq(AuditSubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(EMAIL), any(Pageable.class) + ); + } + + @Test + void shouldCallFindBySubjectTypeAndCreatedAtBetweenAndEmailAddressLikeWhenAllFiltersProvided() { + when(auditRepository.findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( + eq(AuditSubjectType.SERVICE_CENTRE), + eq(fromDateTime), + eq(toDateTime), + eq(EMAIL), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page result = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + toDate, + AuditSubjectType.SERVICE_CENTRE, + null, + null, + EMAIL + ); + + assertThat(result.getContent()).hasSize(1); + verify(auditRepository).findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( + eq(AuditSubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(toDateTime), eq(EMAIL), any(Pageable.class) + ); + } + + @Test + void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimpleSubjectTypeQuery() { + when(auditRepository.findBySubjectTypeAndCreatedAtAfter( + eq(AuditSubjectType.COURT), + eq(fromDateTime), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page firstResult = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + null, + AuditSubjectType.COURT, + null, + null, + "" + ); + + Page secondResult = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + null, + AuditSubjectType.COURT, + null, + null, + " " + ); + + assertThat(firstResult.getContent()).hasSize(1); + assertThat(secondResult.getContent()).hasSize(1); + verify(auditRepository, times(2)).findBySubjectTypeAndCreatedAtAfter( + eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + ); + } + + @Test + void getFilteredAndPaginatedAuditsShouldPrioritiseCourtIdOverSubjectTypeWhenBothProvided() { + when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfter( + eq(COURT_ID), + eq(AuditSubjectType.COURT), + eq(fromDateTime), + any(Pageable.class) + ) + ).thenReturn(new PageImpl<>(List.of(createAudit()))); + + Page result = + auditService.getFilteredAndPaginatedAudits( + PAGE_NUMBER, + PAGE_SIZE, + fromDate, + null, + AuditSubjectType.SERVICE_CENTRE, + COURT_ID.toString(), + null, + null + ); + + assertThat(result.getContent()).hasSize(1); + verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtAfter( + eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + ); + } + private Audit createAudit() { return Audit.builder() .id(AUDIT_ID) diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtServiceTest.java index 07fc42c3..35e482aa 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtServiceTest.java @@ -17,6 +17,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.CourtDetails; import uk.gov.hmcts.reform.fact.data.api.entities.Region; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.InvalidParameterCombinationException; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.CourtDetailsRepository; @@ -26,6 +27,7 @@ import feign.Request; import feign.RequestTemplate; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -787,4 +789,48 @@ void getCourtDetailsBySlugThrowsNotFoundExceptionWhenCourtDoesNotExist() { assertThat(exception.getMessage()).isEqualTo("Court not found, slug: " + courtSlug); } + @Test + void getAllCourtDetailsReturnsCourtDetailsListWhenFound() { + List courtDetailsList = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + UUID courtId = UUID.randomUUID(); + CourtDetails courtDetails = new CourtDetails(); + courtDetails.setId(courtId); + courtDetails.setName(String.format("Test Court %s", (char) (i + 0x41))); + courtDetailsList.add(courtDetails); + } + + when(courtDetailsRepository.findAll()).thenReturn(courtDetailsList); + + List result = courtService.getAllCourtDetails(); + + assertThat(result) + .isNotNull() + .isEqualTo(courtDetailsList); + } + + @Test + void getAllCourtNameAndIdsReturnsRepositoryResults() { + NameAndId first = new NameAndId("Court A", UUID.randomUUID()); + NameAndId second = new NameAndId("Court B", UUID.randomUUID()); + List expected = List.of(first, second); + + when(courtRepository.findAllNameAndId()).thenReturn(expected); + + List result = courtService.getAllCourtNameAndIds(); + + assertThat(result).isEqualTo(expected); + verify(courtRepository).findAllNameAndId(); + } + + @Test + void getAllCourtNameAndIdsReturnsEmptyListWhenNoCourtsExist() { + when(courtRepository.findAllNameAndId()).thenReturn(Collections.emptyList()); + + List result = courtService.getAllCourtNameAndIds(); + + assertThat(result).isEmpty(); + verify(courtRepository).findAllNameAndId(); + } } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreServiceTest.java index 6d819b5d..b705f6db 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/ServiceCentreServiceTest.java @@ -10,11 +10,13 @@ import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentre; import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentreDetails; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.NameAndId; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.repositories.ServiceAreaRepository; import uk.gov.hmcts.reform.fact.data.api.repositories.ServiceCentreDetailsRepository; import uk.gov.hmcts.reform.fact.data.api.repositories.ServiceCentreRepository; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -210,4 +212,28 @@ void deleteServiceCentresByNamePrefixDeletesMatchingRowsForTestingSupport() { assertThat(deleted).isEqualTo(2); verify(serviceCentreRepository).deleteAllInBatch(serviceCentres); } + + @Test + void getAllServiceCentreNameAndIdsReturnsRepositoryResults() { + NameAndId first = new NameAndId("ServiceCentre A", UUID.randomUUID()); + NameAndId second = new NameAndId("ServiceCentre B", UUID.randomUUID()); + List expected = List.of(first, second); + + when(serviceCentreRepository.findAllNameAndId()).thenReturn(expected); + + List result = serviceCentreService.getAllServiceCentreNameAndIds(); + + assertThat(result).isEqualTo(expected); + verify(serviceCentreRepository).findAllNameAndId(); + } + + @Test + void getAllServiceCentreNameAndIdsReturnsEmptyListWhenNoServiceCentresExist() { + when(serviceCentreRepository.findAllNameAndId()).thenReturn(Collections.emptyList()); + + List result = serviceCentreService.getAllServiceCentreNameAndIds(); + + assertThat(result).isEmpty(); + verify(serviceCentreRepository).findAllNameAndId(); + } }