Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
import static org.assertj.core.api.Assertions.assertThat;

public class AuditControllerFT extends FunctionalTestBase {

@DisplayName("Should fail to update an audit record as they are immutable")
@Test
@DisplayName("Should fail to update an audit record as they are immutable")
void updateAuditFailure() throws JsonProcessingException {
var audit = new CreateAuditDTO();
audit.setId(UUID.randomUUID());
Expand All @@ -36,16 +35,8 @@ void updateAuditFailure() throws JsonProcessingException {
.isEqualTo("Data is immutable and cannot be changed. Id: " + audit.getId());
}

private Response putAudit(CreateAuditDTO dto) throws JsonProcessingException {
return doPutRequest(
AUDIT_ENDPOINT + dto.getId(),
OBJECT_MAPPER.writeValueAsString(dto),
TestingSupportRoles.SUPER_USER
);
}

@DisplayName("Should sort by created at desc")
@Test
@DisplayName("Should sort by created at desc")
void getAuditLogsSortBy() throws JsonProcessingException {
var audit1 = new CreateAuditDTO();
audit1.setId(UUID.randomUUID());
Expand Down Expand Up @@ -75,8 +66,8 @@ void getAuditLogsSortBy() throws JsonProcessingException {
assertThat(auditLogs1.get(2).getCreatedAt()).isAfter(auditLogs1.getLast().getCreatedAt());
}

@ParameterizedTest
@NullSource
@ParameterizedTest
@EnumSource(value = TestingSupportRoles.class, names = "SUPER_USER", mode = EnumSource.Mode.EXCLUDE)
@DisplayName("Unauthorised use of endpoints should return 403 (or 401 for null authorisation)")
void unauthorisedRequestsReturn403Or401(TestingSupportRoles testingSupportRole) throws JsonProcessingException {
Expand All @@ -87,4 +78,29 @@ void unauthorisedRequestsReturn403Or401(TestingSupportRoles testingSupportRole)
assertResponseCode(getAuditLogsResponse, 401);
}
}

@Test
@DisplayName("Should return 404 when trying to get a non-existent audit record")
void getNonExistentAudit() {
var getAuditResponse = doGetRequest(AUDIT_ENDPOINT + UUID.randomUUID(), TestingSupportRoles.SUPER_USER);
assertResponseCode(getAuditResponse, 404);
}

@Test
@DisplayName("Should return 200 when getting a valid audit record")
void getAuditById() {
var getAudits = doGetRequest("/testing-support/latest-audits", TestingSupportRoles.SUPER_USER);
var auditId = getAudits.jsonPath().getUUID("_embedded.auditList[0].id");

var getAuditResponse = doGetRequest(AUDIT_ENDPOINT + auditId, TestingSupportRoles.SUPER_USER);
assertResponseCode(getAuditResponse, 200);
}

private Response putAudit(CreateAuditDTO dto) throws JsonProcessingException {
return doPutRequest(
AUDIT_ENDPOINT + dto.getId(),
OBJECT_MAPPER.writeValueAsString(dto),
TestingSupportRoles.SUPER_USER
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,23 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import uk.gov.hmcts.reform.preapi.controllers.params.SearchAudits;
import uk.gov.hmcts.reform.preapi.dto.AuditDTO;
import uk.gov.hmcts.reform.preapi.dto.CreateAuditDTO;
import uk.gov.hmcts.reform.preapi.enums.AuditLogSource;
import uk.gov.hmcts.reform.preapi.exception.PathPayloadMismatchException;
import uk.gov.hmcts.reform.preapi.exception.RequestedPageOutOfRangeException;
import uk.gov.hmcts.reform.preapi.services.AuditService;

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

import static uk.gov.hmcts.reform.preapi.config.OpenAPIConfiguration.X_USER_ID_HEADER;
Expand Down Expand Up @@ -61,8 +66,55 @@ public ResponseEntity<Void> upsertAudit(@RequestHeader HttpHeaders headers,
return new ResponseEntity<>(HttpStatus.CREATED);
}

@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_USER')")
@Operation(operationId = "getAudit", summary = "Get an Audit Entry")
public HttpEntity<AuditDTO> getAuditById(@PathVariable UUID id) {
return ResponseEntity.ok(auditService.findById(id));
}

@GetMapping
@Operation(operationId = "getAuditLogs", summary = "Search all Audits")
@Parameter(
name = "after",
description = "The date time to search after",
schema = @Schema(implementation = LocalDateTime.class, format = "iso-date-time"),
example = "2021-01-01T00:00:00"
)
@Parameter(
name = "before",
description = "The date time to search before",
schema = @Schema(implementation = LocalDateTime.class, format = "iso-date-time"),
example = "2021-01-01T00:00:00"
)
@Parameter(
name = "functionalArea",
description = "The functional area to search by",
schema = @Schema(implementation = String.class),
example = "API"
)
@Parameter(
name = "source",
description = "The source to search by",
schema = @Schema(implementation = AuditLogSource.class)
)
@Parameter(
name = "userName",
description = "Partial user's name to search by",
schema = @Schema(implementation = String.class)
)
@Parameter(
name = "courtId",
description = "The court id of the audit to search by",
schema = @Schema(implementation = UUID.class),
example = "123e4567-e89b-12d3-a456-426614174000"
)
@Parameter(
name = "caseReference",
description = "The case reference of the audit to search by",
schema = @Schema(implementation = String.class),
example = "CASE12345"
)
@Parameter(
name = "page",
description = "The page number of search result to return",
Expand All @@ -77,13 +129,21 @@ public ResponseEntity<Void> upsertAudit(@RequestHeader HttpHeaders headers,
)
@PreAuthorize("hasAnyRole('ROLE_SUPER_USER')")
public HttpEntity<PagedModel<EntityModel<AuditDTO>>> searchAuditLogs(
@Parameter(hidden = true) @ModelAttribute SearchAudits params,
@SortDefault.SortDefaults(
@SortDefault(sort = "createdAt", direction = Sort.Direction.DESC)
)
@Parameter(hidden = true) Pageable pageable,
@Parameter(hidden = true) PagedResourcesAssembler<AuditDTO> assembler
) {
var resultPage = auditService.findAll(
params.getAfter() != null ? Timestamp.valueOf(params.getAfter()) : null,
params.getBefore() != null ? Timestamp.valueOf(params.getBefore()) : null,
params.getFunctionalArea(),
params.getSource(),
params.getUserName(),
params.getCourtId(),
params.getCaseReference(),
pageable
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ public HttpEntity<PagedModel<EntityModel<Audit>>> getLatestAudits(
@Parameter(hidden = true) Pageable pageable,
@Parameter(hidden = true) PagedResourcesAssembler<Audit> assembler
) {
var resultPage = auditRepository.searchAll(pageable);
var resultPage = auditRepository.searchAll(null, null, null, null, null, null, null, pageable);

if (pageable.getPageNumber() > resultPage.getTotalPages()) {
throw new RequestedPageOutOfRangeException(pageable.getPageNumber(), resultPage.getTotalPages());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package uk.gov.hmcts.reform.preapi.controllers.params;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import uk.gov.hmcts.reform.preapi.enums.AuditLogSource;

import java.time.LocalDateTime;
import java.util.UUID;

@Data
public class SearchAudits {

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime after;

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime before;

private String functionalArea;

private AuditLogSource source;

private String userName;

private UUID courtId;

private String caseReference;

public String getFunctionalArea() {
return functionalArea != null && !functionalArea.isEmpty() ? functionalArea : null;
}

public String getUserName() {
return userName != null && !userName.isEmpty() ? userName : null;
}

public String getCaseReference() {
return caseReference != null && !caseReference.isEmpty() ? caseReference : null;
}
}
39 changes: 24 additions & 15 deletions src/main/java/uk/gov/hmcts/reform/preapi/dto/AuditDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,46 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import uk.gov.hmcts.reform.preapi.dto.base.BaseUserDTO;
import uk.gov.hmcts.reform.preapi.entities.Audit;
import uk.gov.hmcts.reform.preapi.entities.User;

import java.sql.Timestamp;
import java.util.UUID;

@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(description = "AuditDTO")
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class AuditDTO extends CreateAuditDTO {

@Schema(description = "AuditCreatedAt")
@NotNull
@Schema(description = "AuditCreatedAt")
private Timestamp createdAt;

@Schema(description = "AuditCreatedBy")
@NotNull
private UUID createdBy;
@Schema(description = "AuditCreatedBy")
private BaseUserDTO createdBy;

public AuditDTO(Audit auditEntity) {
super();
this.id = auditEntity.getId();
this.tableName = auditEntity.getTableName();
this.tableRecordId = auditEntity.getTableRecordId();
this.source = auditEntity.getSource();
this.category = auditEntity.getCategory();
this.activity = auditEntity.getActivity();
this.functionalArea = auditEntity.getFunctionalArea();
this.auditDetails = auditEntity.getAuditDetails();
this.createdAt = auditEntity.getCreatedAt();
this.createdBy = auditEntity.getCreatedBy();
id = auditEntity.getId();
tableName = auditEntity.getTableName();
tableRecordId = auditEntity.getTableRecordId();
source = auditEntity.getSource();
category = auditEntity.getCategory();
activity = auditEntity.getActivity();
functionalArea = auditEntity.getFunctionalArea();
auditDetails = auditEntity.getAuditDetails();
createdAt = auditEntity.getCreatedAt();
if (auditEntity.getCreatedBy() != null) {
createdBy = new BaseUserDTO();
createdBy.setId(auditEntity.getCreatedBy());
}
}

public AuditDTO(Audit auditEntity, User user) {
this(auditEntity);
createdBy = new BaseUserDTO(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import uk.gov.hmcts.reform.preapi.entities.Audit;
import uk.gov.hmcts.reform.preapi.enums.AuditLogSource;

import java.sql.Timestamp;
import java.util.List;
import java.util.UUID;

Expand All @@ -29,8 +31,63 @@ AND CAST(FUNCTION('jsonb_extract_path_text', a.auditDetails, 'description') as t
@Query(
"""
SELECT a FROM Audit a
WHERE (CAST(:after as Timestamp) IS NULL
OR CAST(FUNCTION('TIMEZONE', 'Europe/London', a.createdAt) as Timestamp) >= :after)
AND (CAST(:before as Timestamp) IS NULL
OR CAST(FUNCTION('TIMEZONE', 'Europe/London', a.createdAt) as Timestamp) <= :before)
AND (:functionalArea IS NULL OR a.functionalArea ILIKE %:functionalArea%)
AND (CAST(:source as text) IS NULL OR a.source = :source)
AND (:userName IS NULL
OR EXISTS (
SELECT 1 FROM AppAccess aa
WHERE aa.id = a.createdBy
AND CONCAT(aa.user.firstName, ' ', aa.user.lastName) ILIKE %:userName%)
OR EXISTS (
SELECT 1 FROM PortalAccess pa
WHERE pa.id = a.createdBy
AND CONCAT(pa.user.firstName, ' ', pa.user.lastName) ILIKE %:userName%))
AND ((CAST(:courtId as uuid) IS NULL AND :caseReference IS NULL)
OR EXISTS (
SELECT 1 FROM Court court
WHERE :caseReference IS NULL
AND court.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR court.id = :courtId))
OR EXISTS (
SELECT 1 FROM Case c
WHERE c.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR c.court.id = :courtId)
AND (:caseReference IS NULL OR c.reference ILIKE %:caseReference%))
OR EXISTS (
SELECT 1 FROM Booking b
WHERE b.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR b.caseId.court.id = :courtId)
AND (:caseReference IS NULL OR b.caseId.reference ILIKE %:caseReference%))
OR EXISTS (
SELECT 1 FROM CaptureSession cs
WHERE cs.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR cs.booking.caseId.court.id = :courtId)
AND (:caseReference IS NULL OR cs.booking.caseId.reference ILIKE %:caseReference%))
OR EXISTS (
SELECT 1 FROM Recording r
WHERE r.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR r.captureSession.booking.caseId.court.id = :courtId)
AND (:caseReference IS NULL OR r.captureSession.booking.caseId.reference ILIKE %:caseReference%))
OR EXISTS (
SELECT 1 FROM Participant p
WHERE p.id = a.tableRecordId
AND (CAST(:courtId as uuid) IS NULL OR p.caseId.court.id = :courtId)
AND (:caseReference IS NULL OR p.caseId.reference ILIKE %:caseReference%)))
ORDER BY a.createdAt DESC
"""
)
Page<Audit> searchAll(Pageable pageable);
Page<Audit> searchAll(
@Param("after") Timestamp after,
@Param("before") Timestamp before,
@Param("functionalArea") String functionalArea,
@Param("source") AuditLogSource source,
@Param("userName") String userName,
@Param("courtId") UUID courtId,
@Param("caseReference") String caseReference,
Pageable pageable
);
}
Loading