Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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