Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bfcd63d
FACT-2618 Remove old service centre code (#271)
josh-uk Jun 17, 2026
1ddcff4
FACT-2618 Add service centre entities and update audit (#273)
josh-uk Jun 18, 2026
113c5b3
Merge branch 'master' into service-centres
josh-uk Jun 18, 2026
e750efb
FACT-2618 Added new controllers/services for service centres (#275)
josh-uk Jun 18, 2026
1045a54
FACT-2618 Added details endpoints/view (#279)
josh-uk Jun 22, 2026
5ae1b41
Merge branch 'master' into service-centres
josh-uk Jun 22, 2026
89ea59c
FACT-2618 search (#280)
josh-uk Jun 23, 2026
b0c9931
Potential fix for pull request finding 'CodeQL / Cross-site scripting'
josh-uk Jun 23, 2026
ee58139
Fix sonar issues
josh-uk Jun 23, 2026
539b631
FACT-2821 Migration (#281)
amstevenson Jun 23, 2026
08396bb
Sonar fix
josh-uk Jun 23, 2026
5fcebe0
Some clean up
josh-uk Jun 23, 2026
3792214
Remove unused param
josh-uk Jun 23, 2026
97afa5b
log fixes
josh-uk Jun 23, 2026
0b74ab8
Fix functional test
josh-uk Jun 23, 2026
6910e1e
unique slug in db
josh-uk Jun 24, 2026
1ddc5ef
Merge branch 'master' into service-centres
josh-uk Jun 24, 2026
06e332b
Drop court ID from audit
josh-uk Jun 24, 2026
328181a
Postgres 18 update - part 1
josh-uk Jun 25, 2026
0b1f0b5
Merge branch 'master' into service-centres
josh-uk Jun 25, 2026
7116693
Add court name and id list retrieval endpoint
hurricanepilot Jun 18, 2026
c90e3a1
Change UUID for test user to be a valid v4 UUID variant
hurricanepilot Jun 18, 2026
0025abb
Add missing service tests (coverage)
hurricanepilot Jun 18, 2026
f161a30
Switch to subject-based retrieval of audit name options
hurricanepilot Jun 19, 2026
9475ee3
naming
hurricanepilot Jun 19, 2026
78328d8
Add audit record retrieval endpoint
hurricanepilot Jun 22, 2026
b608602
Ensure user is part of response entity
hurricanepilot Jun 23, 2026
81eee03
Rename AuditSubject to AuditSubjectType to align with SC changes
hurricanepilot Jun 23, 2026
1092bc6
Refactor audit retrieval to include court and user details, and updat…
hurricanepilot Jun 23, 2026
b15f44f
Go to postgres 17
josh-uk Jun 25, 2026
6cadcfa
update to 17, force re-create
josh-uk Jun 25, 2026
a0faae0
Fixup tests after rebase
hurricanepilot Jun 25, 2026
5a7a31b
change tag
josh-uk Jun 25, 2026
145522b
Integrate service centres with audit subjectoptions endpoint
hurricanepilot Jun 25, 2026
8cb4a74
Merge branch 'service-centres' into FACT-2658-audit-view-updates
hurricanepilot Jun 25, 2026
eccf76d
Merge branch 'master' into FACT-2658-audit-view-updates
hurricanepilot Jun 25, 2026
b7814f1
Remove duplicate flyway scripts
hurricanepilot Jun 25, 2026
3e246a2
Fix broken test
hurricanepilot Jun 25, 2026
510b606
Fix rebase drift and align with master
hurricanepilot Jun 25, 2026
07c907b
Add tests for service centre name and id retrieval
hurricanepilot Jun 25, 2026
caf1a41
Add subject type filtering to the audit query code
hurricanepilot Jun 25, 2026
998c628
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jun 26, 2026
f097c16
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jun 26, 2026
4d6bf67
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jun 26, 2026
22ba74f
Made parameter names consistent with query fields and other methods
hurricanepilot Jun 26, 2026
5cee186
Merge branch 'FACT-2658-audit-view-updates' of github.com:hmcts/fact-…
hurricanepilot Jun 26, 2026
d6bcd9d
whitespace
hurricanepilot Jun 29, 2026
098ab4b
whitespace
hurricanepilot Jun 29, 2026
5e74f15
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jun 29, 2026
20487cf
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jun 29, 2026
bbed353
Merge branch 'master' into FACT-2658-audit-view-updates
hurricanepilot Jul 1, 2026
ffc91c8
whitespace
hurricanepilot Jul 1, 2026
a41dde7
Add @Synchronized around creation/update of test user
hurricanepilot Jul 1, 2026
7129ad3
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jul 1, 2026
f1420b2
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jul 2, 2026
b925a2d
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jul 2, 2026
567ea2d
Merge master into FACT-2658-audit-view-updates
github-actions[bot] Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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<String> extractActionTypesForEntity(final Response response, final String actionEntity) {
return response.jsonPath().getList(
"content.findAll { it.actionEntity == '" + actionEntity + "' }.actionType",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ private void assertAuditActionsForEntity(Court owningCourt, String testedEntity,
1000,
CREATED_AFTER,
null,
null,
owningCourt.getId().toString(),
null,
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Court> createTestCourts(int num) {
List<Court> results = new ArrayList<>();
for (int i = 0; i < num; i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
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;
import uk.gov.hmcts.reform.fact.data.api.services.AuditService;
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;
Expand All @@ -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;

Expand Down Expand Up @@ -52,6 +58,7 @@ public ResponseEntity<Page<Audit>> 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)
Expand Down Expand Up @@ -80,13 +87,29 @@ public ResponseEntity<Page<Audit>> getFilteredAndPaginatedAudits(
pageSize,
fromDate,
toDate,
subjectType,
courtId,
serviceCentreId,
emailMatch
)
);
}

@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<Audit> getAuditById(
@ValidUUID @PathVariable String auditId) {
return ResponseEntity.ok(auditService.getAuditById(UUID.fromString(auditId)));
}

@DeleteMapping("/v1")
@Operation(
summary = "Remove expired audits",
Expand All @@ -100,4 +123,15 @@ public ResponseEntity<Void> 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<Map<AuditSubjectType, List<NameAndId>>> getSubjectNameAndIdMap() {
return ResponseEntity.ok(this.auditService.getSubjectNameAndIdMap());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {}
Loading
Loading