From 380a8805202d6979d57c13a971a11aa7cb0cf8fa Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Thu, 2 Jul 2026 12:32:52 +0100 Subject: [PATCH 01/11] Add full User entity to the JSON payload --- .../gov/hmcts/reform/fact/data/api/entities/CourtLock.java | 1 - .../fact/data/api/repositories/CourtLockRepository.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java index bfeff228..75651a13 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java @@ -58,7 +58,6 @@ public class CourtLock { @Column(name = "user_id") private UUID userId; - @JsonIgnore @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", insertable = false, updatable = false) private User user; diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java index 73fd09ef..a5227acb 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java @@ -6,6 +6,7 @@ import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; @@ -14,8 +15,10 @@ public interface CourtLockRepository extends JpaRepository { void deleteAllByUserId(UUID userId); - List findAllByCourtId(UUID userId); + @EntityGraph(attributePaths = {"user"}) + List findAllByCourtId(UUID courtId); + @EntityGraph(attributePaths = {"user"}) Optional findByCourtIdAndPage(UUID courtId, Page page); void deleteByCourtIdAndPage(UUID courtId, Page page); From 5b573c6d6ece79be4ad29e1b730b5ef312289946 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Thu, 2 Jul 2026 12:42:44 +0100 Subject: [PATCH 02/11] Expose court lock timeout as an env var --- src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e8d135ee..5ecbd671 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -92,7 +92,7 @@ user: retention-period: 365 courtLock: - timeout-minutes: 60 + timeout-minutes: ${COURT_LOCK_TIMEOUT_MINUTES:60} os: key: ${OS_KEY} From 9a83c09da514b1b7a1d5d74dd076c75ed8b96756 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Fri, 3 Jul 2026 08:16:46 +0100 Subject: [PATCH 03/11] Change empty results to return a 204 + null rather than 200 + null --- .../fact/data/api/controllers/CourtLockController.java | 7 +++++-- .../reform/fact/data/api/services/CourtLockService.java | 1 + .../fact/data/api/controllers/CourtLockControllerTest.java | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java index 69108403..e7c75053 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java @@ -56,13 +56,16 @@ public ResponseEntity> getCourtLocks( @Operation(summary = "Get lock status for a specific page of a court") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved lock status"), + @ApiResponse(responseCode = "204", description = "No lock data found for the specified page"), @ApiResponse(responseCode = "400", description = "Invalid court ID or page supplied"), @ApiResponse(responseCode = "404", description = "Court not found") }) - public ResponseEntity> getCourtLockStatus( + public ResponseEntity getCourtLockStatus( @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, @Parameter(description = "Page to check lock status", required = true) @PathVariable Page page) { - return ResponseEntity.ok(courtLockService.getPageLock(UUID.fromString(courtId), page)); + return courtLockService.getPageLock(UUID.fromString(courtId), page) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.noContent().build()); } @PostMapping("/v1/locks/{page}") diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java index 58f32482..0ee0bcd7 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java @@ -87,6 +87,7 @@ public CourtLock createOrUpdateLock(UUID courtId, Page page, UUID userId) { courtLock.setCourtId(court.getId()); courtLock.setUserId(user.getId()); + courtLock.setUser(user); courtLock.setPage(page); courtLock.setLockAcquired(ZonedDateTime.now()); diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java index afc03189..c2f44917 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java @@ -57,10 +57,10 @@ void getCourtLocksThrowsIllegalArgumentExceptionForInvalidUUID() { @Test void getCourtLockStatusReturns200() { - Optional lock = Optional.of(new CourtLock()); - when(courtLockService.getPageLock(COURT_ID, TEST_PAGE)).thenReturn(lock); + CourtLock lock = new CourtLock(); + when(courtLockService.getPageLock(COURT_ID, TEST_PAGE)).thenReturn(Optional.of(lock)); - ResponseEntity> response = courtLockController.getCourtLockStatus( + ResponseEntity response = courtLockController.getCourtLockStatus( COURT_ID.toString(), TEST_PAGE ); From 527fab17c9d61e13224018f76ce9f672bb82e74f Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Fri, 3 Jul 2026 08:17:08 +0100 Subject: [PATCH 04/11] Add additional Page types --- .../reform/fact/data/api/entities/types/Page.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java index 9f97c569..d6da6f21 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java @@ -2,6 +2,16 @@ public enum Page { COURT, + COURT_ADDRESS, COURT_ACCESSIBILITY, - COURT_LOCAL_AUTHORITY + COURT_CASES_HEARD, + COURT_CONTACT_DETAILS, + COURT_COUNTER_SERVICE_OPENING_HOURS, + COURT_COURT_OPENING_HOURS, + COURT_BUILDING_FACILITIES, + COURT_INFORMATION_FOR_PROFESSIONALS, + COURT_LOCAL_AUTHORITIES, + COURT_PHOTO, + COURT_TRANSLATION_AND_INTERPRETATION, + COURT_WARNING_NOTICE; } From 5f81973a6c1171a04b18e38cb582fb912695dc26 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Mon, 6 Jul 2026 16:49:18 +0100 Subject: [PATCH 05/11] Refactor court lock functionality to general lock management - Introduced Lock and LockService to replace CourtLock and CourtLockService. - Updated database schema to generalize locks for both courts and service centres. - Added LockCleanupCheck and LockTimeoutCheck annotations for method-level lock management. - Refactored controller and service methods to accommodate new lock structure. - Updated tests to reflect changes in lock handling and naming conventions. --- build.gradle | 2 + .../controllers/AuthFunctionalTest.java | 19 +-- ...java => LockControllerFunctionalTest.java} | 126 +++++++++-------- .../UserControllerFunctionalTest.java | 37 ++--- .../functional/helpers/TestDataHelper.java | 11 +- ...ollerTest.java => LockControllerTest.java} | 73 +++++----- .../api/controllers/UserControllerTest.java | 6 +- .../LockAspect.java} | 63 +++++++-- .../aspect/annotations/LockCleanupCheck.java | 14 ++ .../annotations/LockTimeoutCheck.java} | 4 +- .../api/controllers/CourtLockController.java | 103 -------------- .../data/api/controllers/LockController.java | 107 ++++++++++++++ .../data/api/controllers/UserController.java | 10 +- .../entities/{CourtLock.java => Lock.java} | 18 ++- .../fact/data/api/entities/types/Page.java | 26 ++-- .../api/repositories/CourtLockRepository.java | 25 ---- .../data/api/repositories/LockRepository.java | 32 +++++ .../data/api/services/CourtLockService.java | 107 -------------- .../fact/data/api/services/LockService.java | 131 ++++++++++++++++++ .../V1.40__generalise_lock_subject.sql | 20 +++ .../LockAspectTest.java} | 124 +++++++++-------- ...ollerTest.java => LockControllerTest.java} | 52 ++++--- .../api/controllers/UserControllerTest.java | 6 +- ...kServiceTest.java => LockServiceTest.java} | 71 +++++----- 24 files changed, 660 insertions(+), 527 deletions(-) rename src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/{CourtLockControllerFunctionalTest.java => LockControllerFunctionalTest.java} (68%) rename src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/{CourtLockControllerTest.java => LockControllerTest.java} (53%) rename src/main/java/uk/gov/hmcts/reform/fact/data/api/{validation/validator/CourtLockTimeoutValidator.java => aspect/LockAspect.java} (59%) create mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockCleanupCheck.java rename src/main/java/uk/gov/hmcts/reform/fact/data/api/{validation/annotations/CourtLockTimeoutCheck.java => aspect/annotations/LockTimeoutCheck.java} (77%) delete mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java create mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java rename src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/{CourtLock.java => Lock.java} (83%) delete mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java create mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java delete mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java create mode 100644 src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java create mode 100644 src/main/resources/db/migration/V1.40__generalise_lock_subject.sql rename src/test/java/uk/gov/hmcts/reform/fact/data/api/{validator/CourtLockTimeoutValidatorTest.java => aspect/LockAspectTest.java} (65%) rename src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/{CourtLockControllerTest.java => LockControllerTest.java} (61%) rename src/test/java/uk/gov/hmcts/reform/fact/data/api/services/{CourtLockServiceTest.java => LockServiceTest.java} (57%) diff --git a/build.gradle b/build.gradle index debe6f45..0096e014 100644 --- a/build.gradle +++ b/build.gradle @@ -210,6 +210,8 @@ dependencies { implementation group: 'com.slack.api', name: 'slack-api-client', version: '1.49.0' + implementation group: 'com.azure', name: 'azure-storage-blob', version: '12.26.0' + //Solves CVE-2025-48976 implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.6.0' diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java index 0deef5a9..86138451 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java @@ -25,6 +25,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentreContactDetails; import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.entities.types.AddressType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.DayOfTheWeek; import uk.gov.hmcts.reform.fact.data.api.entities.types.HearingEnhancementEquipment; import uk.gov.hmcts.reform.fact.data.api.entities.types.OpeningTimesDetail; @@ -524,32 +525,32 @@ void courtLocalAuthoritiesAuth() throws Exception { } @Test - @DisplayName("Court lock endpoints enforce admin-only writes") + @DisplayName("Lock endpoints enforce admin-only writes") void courtLockAuth() throws Exception { - UUID courtId = createCourtAsAdmin("Test Court Auth Lock"); + UUID courtId = createCourtAsAdmin("Test Auth Lock"); UUID userId = createUserAsAdmin("test.user.auth.lock"); - String lockPath = "/courts/" + courtId + "/v1/locks/" + Page.COURT; + String lockPath = "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL; Response postAdmin = http.doPost(lockPath, mapper.writeValueAsString(userId), adminToken); assertThat(postAdmin.statusCode()).isIn(200, 201); assertViewerAllowed( - http.doGet("/courts/" + courtId + "/v1/locks", adminToken), - http.doGet("/courts/" + courtId + "/v1/locks", viewerToken), - "/courts/{courtId}/v1/locks [GET]" + http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/", adminToken), + http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/", viewerToken), + "/locks/{subjectType}/{subjectId}/v1 [GET]" ); assertViewerAllowed( http.doGet(lockPath, adminToken), http.doGet(lockPath, viewerToken), - "/courts/{courtId}/v1/locks/{page} [GET]" + "/locks/{subjectType}/{subjectId}/v1/{page} [GET]" ); assertViewerForbidden( http.doPost(lockPath, mapper.writeValueAsString(userId), viewerToken), - "/courts/{courtId}/v1/locks/{page} [POST]" + "/locks/{subjectType}/{subjectId}/v1/{page} [POST]" ); assertViewerForbidden( http.doDelete(lockPath, viewerToken), - "/courts/{courtId}/v1/locks/{page} [DELETE]" + "/locks/{subjectType}/{subjectId}/v1/{page} [DELETE]" ); } diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtLockControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java similarity index 68% rename from src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtLockControllerFunctionalTest.java rename to src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java index b78a49cd..ac16b922 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/CourtLockControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java @@ -8,7 +8,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.functional.helpers.TestDataHelper; import uk.gov.hmcts.reform.fact.functional.http.HttpClient; @@ -24,7 +25,7 @@ @Feature("Court Lock Controller") @DisplayName("Court Lock Controller") -public final class CourtLockControllerFunctionalTest { +public final class LockControllerFunctionalTest { private static final HttpClient http = new HttpClient(); private static final ObjectMapper mapper = JsonMapper.builder() @@ -32,114 +33,111 @@ public final class CourtLockControllerFunctionalTest { .build(); @Test - @DisplayName("POST /courts/{courtId}/v1/locks/{page} creates lock successfully") + @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} creates lock successfully") void shouldCreateCourtLockSuccessfully() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.create"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Create"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock Create"); - final CourtLock createdLock = TestDataHelper.createCourtLock(http, courtId, Page.COURT, userId); + final Lock createdLock = TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userId); assertThat(createdLock.getId()) .as("Created lock should have auto-generated ID") .isNotNull(); - assertThat(createdLock.getCourtId()) + assertThat(createdLock.getSubjectId()) .as("Created lock court ID should match request court ID %s", courtId) .isEqualTo(courtId); assertThat(createdLock.getUserId()) .as("Created lock user ID should match request user ID %s", userId) .isEqualTo(userId); assertThat(createdLock.getPage()) - .as("Created lock page should match request page %s", Page.COURT) - .isEqualTo(Page.COURT); + .as("Created lock page should match request page %s", Page.GENERAL) + .isEqualTo(Page.GENERAL); assertThat(createdLock.getLockAcquired()) .as("Created lock should have lockAcquired timestamp set") .isNotNull(); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks/{page} returns lock status successfully") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/{page} returns lock status successfully") void shouldGetCourtLockStatusSuccessfully() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.getstatus"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Get Status"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock Get Status"); - TestDataHelper.createCourtLock(http, courtId, Page.COURT_ACCESSIBILITY, userId); + TestDataHelper.createCourtLock(http, courtId, Page.ACCESSIBILITY, userId); final Response getLockStatusResponse = http.doGet( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_ACCESSIBILITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.ACCESSIBILITY ); assertThat(getLockStatusResponse.statusCode()) .as("Expected 200 OK when getting lock status for court %s page %s", - courtId, Page.COURT_ACCESSIBILITY) + courtId, Page.ACCESSIBILITY + ) .isEqualTo(OK.value()); assertThat(getLockStatusResponse.contentType()) .as("Expected JSON content type when getting lock status") .contains("json"); - final CourtLock fetchedLock = mapper.readValue( + final Lock fetchedLock = mapper.readValue( getLockStatusResponse.getBody().asString(), - CourtLock.class + Lock.class ); - assertThat(fetchedLock.getCourtId()) + assertThat(fetchedLock.getSubjectId()) .as("Fetched lock court ID should match %s", courtId) .isEqualTo(courtId); assertThat(fetchedLock.getUserId()) .as("Fetched lock user ID should match %s", userId) .isEqualTo(userId); assertThat(fetchedLock.getPage()) - .as("Fetched lock page should match %s", Page.COURT_ACCESSIBILITY) - .isEqualTo(Page.COURT_ACCESSIBILITY); + .as("Fetched lock page should match %s", Page.ACCESSIBILITY) + .isEqualTo(Page.ACCESSIBILITY); assertThat(fetchedLock.getLockAcquired()) .as("Fetched lock should have lockAcquired timestamp") .isNotNull(); } @Test - @DisplayName("DELETE /courts/{courtId}/v1/locks/{page} deletes lock successfully") + @DisplayName("DELETE /locks/{subjectType}/{subjectId}/v1/{page} deletes lock successfully") void shouldDeleteCourtLockSuccessfully() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.delete"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Delete"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock Delete"); - TestDataHelper.createCourtLock(http, courtId, Page.COURT_LOCAL_AUTHORITY, userId); + TestDataHelper.createCourtLock(http, courtId, Page.LOCAL_AUTHORITIES, userId); final Response deleteLockResponse = http.doDelete( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_LOCAL_AUTHORITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(deleteLockResponse.statusCode()) .as("Expected 204 NO CONTENT when deleting lock for court %s page %s", - courtId, Page.COURT_LOCAL_AUTHORITY) + courtId, Page.LOCAL_AUTHORITIES) .isEqualTo(NO_CONTENT.value()); final Response getLockStatusResponse = http.doGet( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_LOCAL_AUTHORITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(getLockStatusResponse.statusCode()) - .as("Expected 200 OK when checking lock status after deletion") - .isEqualTo(OK.value()); - - assertThat(getLockStatusResponse.contentType()) - .as("Expected JSON content type when checking lock status") - .contains("json"); + .as("Expected 204 OK when checking lock status after deletion") + .isEqualTo(NO_CONTENT.value()); assertThat(getLockStatusResponse.getBody().asString()) .as("Expected null response body after lock deletion indicating no lock exists") - .isEqualTo("null"); + .isEqualTo(""); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks returns all locks for a court") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1 returns all locks for a court") void shouldGetAllLocksForCourtSuccessfully() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.getall"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Get All"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock Get All"); - TestDataHelper.createCourtLock(http, courtId, Page.COURT, userId); - TestDataHelper.createCourtLock(http, courtId, Page.COURT_ACCESSIBILITY, userId); + TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userId); + TestDataHelper.createCourtLock(http, courtId, Page.ACCESSIBILITY, userId); - final Response getAllLocksResponse = http.doGet("/courts/" + courtId + "/v1/locks"); + final Response getAllLocksResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1"); assertThat(getAllLocksResponse.statusCode()) .as("Expected 200 OK when getting all locks for court %s", courtId) @@ -149,7 +147,7 @@ void shouldGetAllLocksForCourtSuccessfully() throws Exception { .as("Expected JSON content type when getting all locks") .contains("json"); - final List locks = getAllLocksResponse.jsonPath().getList("", CourtLock.class); + final List locks = getAllLocksResponse.jsonPath().getList("", Lock.class); assertThat(locks) .as("Expected 2 locks for court %s", courtId) @@ -157,27 +155,27 @@ void shouldGetAllLocksForCourtSuccessfully() throws Exception { assertThat(locks) .as("All locks should belong to court %s", courtId) - .allSatisfy(lock -> assertThat(lock.getCourtId()) + .allSatisfy(lock -> assertThat(lock.getSubjectId()) .as("Lock court ID should be %s", courtId) .isEqualTo(courtId)); assertThat(locks) .as("Expected locks for pages COURT and COURT_ACCESSIBILITY") - .extracting(CourtLock::getPage) - .containsExactlyInAnyOrder(Page.COURT, Page.COURT_ACCESSIBILITY); + .extracting(Lock::getPage) + .containsExactlyInAnyOrder(Page.GENERAL, Page.ACCESSIBILITY); } @Test - @DisplayName("POST /courts/{courtId}/v1/locks/{page} returns 409 when page already locked by another user") + @DisplayName("POST /locks/{subjectType/{subjectId}/v1/{page} returns 409 when page already locked by another user") void shouldReturnConflictWhenPageLockedByAnotherUser() throws Exception { final UUID userAId = TestDataHelper.createUser(http, "test.user.conflict.usera"); final UUID userBId = TestDataHelper.createUser(http, "test.user.conflict.userb"); final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Conflict"); - TestDataHelper.createCourtLock(http, courtId, Page.COURT, userAId); + TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userAId); final Response userBLockResponse = http.doPost( - "/courts/" + courtId + "/v1/locks/" + Page.COURT, + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL, mapper.writeValueAsString(userBId) ); @@ -199,18 +197,18 @@ void shouldReturnConflictWhenPageLockedByAnotherUser() throws Exception { } @Test - @DisplayName("POST /courts/{courtId}/v1/locks/{page} allows same user to refresh lock") + @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} allows same user to refresh lock") void shouldAllowSameUserToRefreshLock() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.refresh"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock Refresh"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock Refresh"); - final CourtLock initialLock = TestDataHelper.createCourtLock(http, courtId, Page.COURT, userId); + final Lock initialLock = TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userId); final ZonedDateTime initialTimestamp = initialLock.getLockAcquired(); Thread.sleep(1000); - final CourtLock refreshedLock = TestDataHelper.createCourtLock(http, courtId, Page.COURT, userId); + final Lock refreshedLock = TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userId); assertThat(refreshedLock.getId()) .as("Lock ID should remain the same after refresh") @@ -220,7 +218,7 @@ void shouldAllowSameUserToRefreshLock() throws Exception { .as("Lock acquired timestamp should be updated after refresh") .isAfter(initialTimestamp); - assertThat(refreshedLock.getCourtId()) + assertThat(refreshedLock.getSubjectId()) .as("Lock court ID should remain %s", courtId) .isEqualTo(courtId); @@ -229,34 +227,34 @@ void shouldAllowSameUserToRefreshLock() throws Exception { .isEqualTo(userId); assertThat(refreshedLock.getPage()) - .as("Lock page should remain %s", Page.COURT) - .isEqualTo(Page.COURT); + .as("Lock page should remain %s", Page.GENERAL) + .isEqualTo(Page.GENERAL); } @Test @DisplayName("End-to-end: Create lock, get status, refresh lock, delete, verify deletion") void shouldCompleteEndToEndLockWorkflow() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.endtoend"); - final UUID courtId = TestDataHelper.createCourt(http, "Test Court Lock End To End"); + final UUID courtId = TestDataHelper.createCourt(http, "Test Lock End To End"); - final CourtLock createdLock = TestDataHelper - .createCourtLock(http, courtId, Page.COURT_LOCAL_AUTHORITY, userId); + final Lock createdLock = TestDataHelper + .createCourtLock(http, courtId, Page.LOCAL_AUTHORITIES, userId); - assertThat(createdLock.getCourtId()) + assertThat(createdLock.getSubjectId()) .as("Created lock court ID should be %s", courtId) .isEqualTo(courtId); final Response getLockStatusResponse = http.doGet( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_LOCAL_AUTHORITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(getLockStatusResponse.statusCode()) .as("Expected 200 OK when getting lock status") .isEqualTo(OK.value()); - final CourtLock fetchedLock = mapper.readValue( + final Lock fetchedLock = mapper.readValue( getLockStatusResponse.getBody().asString(), - CourtLock.class + Lock.class ); assertThat(fetchedLock.getId()) @@ -265,15 +263,15 @@ void shouldCompleteEndToEndLockWorkflow() throws Exception { Thread.sleep(1000); - final CourtLock refreshedLock = TestDataHelper - .createCourtLock(http, courtId, Page.COURT_LOCAL_AUTHORITY, userId); + final Lock refreshedLock = TestDataHelper + .createCourtLock(http, courtId, Page.LOCAL_AUTHORITIES, userId); assertThat(refreshedLock.getLockAcquired()) .as("Refreshed lock timestamp should be after original timestamp") .isAfter(createdLock.getLockAcquired()); final Response deleteLockResponse = http.doDelete( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_LOCAL_AUTHORITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(deleteLockResponse.statusCode()) @@ -281,16 +279,16 @@ void shouldCompleteEndToEndLockWorkflow() throws Exception { .isEqualTo(NO_CONTENT.value()); final Response verifyDeletedResponse = http.doGet( - "/courts/" + courtId + "/v1/locks/" + Page.COURT_LOCAL_AUTHORITY + "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(verifyDeletedResponse.statusCode()) - .as("Expected 200 OK when checking deleted lock status") - .isEqualTo(OK.value()); + .as("Expected 204 OK when checking deleted lock status") + .isEqualTo(NO_CONTENT.value()); assertThat(verifyDeletedResponse.getBody().asString()) .as("Expected null response after lock deletion") - .isEqualTo("null"); + .isEqualTo(""); } @AfterAll diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java index d4a179d8..33caf73a 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java @@ -9,8 +9,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import uk.gov.hmcts.reform.fact.data.api.entities.Court; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; import uk.gov.hmcts.reform.fact.data.api.entities.User; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; import uk.gov.hmcts.reform.fact.functional.helpers.TestDataHelper; @@ -271,17 +272,18 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { final UUID court2Id = TestDataHelper .createCourt(http, generateUniqueCourtName("Test Court Clear Locks Two")); - TestDataHelper.createCourtLock(http, court1Id, Page.COURT, userId); - TestDataHelper.createCourtLock(http, court2Id, Page.COURT_ACCESSIBILITY, userId); + TestDataHelper.createCourtLock(http, court1Id, Page.GENERAL, userId); + TestDataHelper.createCourtLock(http, court2Id, Page.ACCESSIBILITY, userId); - final Response getLocksBeforeClearResponse = http.doGet("/courts/" + court1Id + "/v1/locks"); + final Response getLocksBeforeClearResponse = http.doGet("/locks/" + AuditSubjectType.COURT + + "/" + court1Id + "/v1"); assertThat(getLocksBeforeClearResponse.statusCode()) .as("Expected 200 OK when getting locks before clearing") .isEqualTo(OK.value()); - final List locksBeforeClear = getLocksBeforeClearResponse.jsonPath() - .getList("", CourtLock.class); + final List locksBeforeClear = getLocksBeforeClearResponse.jsonPath() + .getList("", Lock.class); assertThat(locksBeforeClear) .as("Expected at least 1 lock before clearing for user %s", userId) @@ -293,26 +295,27 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { .as("Expected 204 NO CONTENT when clearing locks for user %s", userId) .isEqualTo(NO_CONTENT.value()); - final Response getLock1StatusResponse = http.doGet("/courts/" + court1Id + "/v1/locks/" + Page.COURT); + final Response getLock1StatusResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + + court1Id + "/v1/" + Page.GENERAL); assertThat(getLock1StatusResponse.statusCode()) - .as("Expected 200 OK when checking lock status after clearing") - .isEqualTo(OK.value()); + .as("Expected 204 OK when checking lock status after clearing") + .isEqualTo(NO_CONTENT.value()); assertThat(getLock1StatusResponse.getBody().asString()) - .as("Expected null response after clearing user locks") - .isEqualTo("null"); + .as("Expected empty response after clearing user locks") + .isEqualTo(""); - final Response getLock2StatusResponse = http.doGet("/courts/" - + court2Id + "/v1/locks/" + Page.COURT_ACCESSIBILITY); + final Response getLock2StatusResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + + court2Id + "/v1/" + Page.ACCESSIBILITY); assertThat(getLock2StatusResponse.statusCode()) - .as("Expected 200 OK when checking second lock status after clearing") - .isEqualTo(OK.value()); + .as("Expected 204 OK when checking second lock status after clearing") + .isEqualTo(NO_CONTENT.value()); assertThat(getLock2StatusResponse.getBody().asString()) - .as("Expected null response for second lock after clearing user locks") - .isEqualTo("null"); + .as("Expected empty response for second lock after clearing user locks") + .isEqualTo(""); } @Test diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java index effff935..bdad9ef3 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java @@ -6,9 +6,10 @@ import io.restassured.response.Response; import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.CourtFacilities; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; 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.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; @@ -298,10 +299,10 @@ public static uk.gov.hmcts.reform.fact.data.api.entities.CourtAreasOfLaw buildCo * @param userId the user ID * @return the created court lock */ - public static CourtLock createCourtLock(final HttpClient http, final UUID courtId, - final Page page, final UUID userId) throws Exception { + public static Lock createCourtLock(final HttpClient http, final UUID courtId, + final Page page, final UUID userId) throws Exception { final Response response = http.doPost( - "/courts/" + courtId + "/v1/locks/" + page, + "/locks/" + AuditSubjectType.COURT.name() + "/" + courtId + "/v1/" + page, mapper.writeValueAsString(userId) ); @@ -309,7 +310,7 @@ public static CourtLock createCourtLock(final HttpClient http, final UUID courtI .as("Expected 201 CREATED when creating lock for court %s page %s", courtId, page) .isEqualTo(CREATED.value()); - return mapper.readValue(response.getBody().asString(), CourtLock.class); + return mapper.readValue(response.getBody().asString(), Lock.class); } /** diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java similarity index 53% rename from src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java rename to src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java index 83dcb6ff..ba58fefb 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java @@ -10,10 +10,11 @@ import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import java.util.List; import java.util.Optional; @@ -29,9 +30,9 @@ @Feature("Court Local Controller") @DisplayName("Court Local Controller") -@WebMvcTest(CourtLockController.class) +@WebMvcTest(LockController.class) @AutoConfigureMockMvc(addFilters = false) -class CourtLockControllerTest { +class LockControllerTest { @Autowired private MockMvc mockMvc; @@ -40,99 +41,103 @@ class CourtLockControllerTest { private ObjectMapper objectMapper; @MockitoBean - private CourtLockService courtLockService; + private LockService lockService; private final UUID userId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); private final UUID courtId = UUID.fromString("222e4567-e89b-12d3-a456-426614174000"); private final UUID nonExistentCourtId = UUID.fromString("11111111-1111-1111-1111-111111111111"); - private final Page testPage = Page.COURT; + private final Page testPage = Page.GENERAL; @Test - @DisplayName("GET /courts/{courtId}/v1/locks returns court locks successfully") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns court locks successfully") void getCourtLocksReturnsSuccessfully() throws Exception { - List locks = List.of(new CourtLock()); - when(courtLockService.getLocksByCourtId(courtId)).thenReturn(locks); + List locks = List.of(new Lock()); + when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, courtId)).thenReturn(locks); - mockMvc.perform(get("/courts/{courtId}/v1/locks", courtId)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", AuditSubjectType.COURT, courtId)) .andExpect(status().isOk()) .andExpect(jsonPath("$").isArray()); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks returns 404 if court does not exist") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns 404 if court does not exist") void getCourtLocksNonExistentReturnsNotFound() throws Exception { - when(courtLockService.getLocksByCourtId(nonExistentCourtId)) + when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, nonExistentCourtId)) .thenThrow(new NotFoundException("Court not found")); - mockMvc.perform(get("/courts/{courtId}/v1/locks", nonExistentCourtId)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", AuditSubjectType.COURT, nonExistentCourtId)) .andExpect(status().isNotFound()); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks returns 400 for invalid UUID") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns 400 for invalid UUID") void getCourtLocksInvalidUUID() throws Exception { - mockMvc.perform(get("/courts/{courtId}/v1/locks", "invalid-uuid")) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1",AuditSubjectType.COURT, "invalid-uuid")) .andExpect(status().isBadRequest()); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks/{page} returns lock status successfully") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/{page} returns lock status successfully") void getCourtLockStatusReturnsSuccessfully() throws Exception { - CourtLock lock = new CourtLock(); - when(courtLockService.getPageLock(courtId, testPage)).thenReturn(Optional.of(lock)); + Lock lock = new Lock(); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, testPage)).thenReturn(Optional.of(lock)); - mockMvc.perform(get("/courts/{courtId}/v1/locks/{page}", courtId, testPage)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1/{page}", AuditSubjectType.COURT, courtId, testPage)) .andExpect(status().isOk()); } @Test - @DisplayName("GET /courts/{courtId}/v1/locks/{page} returns 404 if court not found") + @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court not found") void getCourtLockStatusNonExistentReturnsNotFound() throws Exception { - when(courtLockService.getPageLock(nonExistentCourtId, testPage)) + when(lockService.getPageLock(AuditSubjectType.COURT, nonExistentCourtId, testPage)) .thenThrow(new NotFoundException("Court not found")); - mockMvc.perform(get("/courts/{courtId}/v1/locks/{page}", nonExistentCourtId, testPage)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1/{page}", + AuditSubjectType.COURT, nonExistentCourtId, testPage)) .andExpect(status().isNotFound()); } @Test - @DisplayName("POST /courts/{courtId}/v1/locks/{page} creates lock successfully") + @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} creates lock successfully") void createCourtLockSuccessfully() throws Exception { - CourtLock lock = new CourtLock(); - when(courtLockService.createOrUpdateLock(courtId, testPage, userId)).thenReturn(lock); + Lock lock = new Lock(); + when(lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, testPage, userId)).thenReturn(lock); - mockMvc.perform(post("/courts/{courtId}/v1/locks/{page}", courtId, testPage) + mockMvc.perform(post("/locks/{subjectType}/{subjectId}/v1/{page}", AuditSubjectType.COURT, courtId, testPage) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(userId))) .andExpect(status().isCreated()); } @Test - @DisplayName("POST /courts/{courtId}/v1/locks/{page} returns 404 if court not found") + @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court not found") void createCourtLockNonExistentReturnsNotFound() throws Exception { - when(courtLockService.createOrUpdateLock(nonExistentCourtId, testPage, userId)) + when(lockService.createOrUpdateLock(AuditSubjectType.COURT, nonExistentCourtId, testPage, userId)) .thenThrow(new NotFoundException("Court not found")); - mockMvc.perform(post("/courts/{courtId}/v1/locks/{page}", nonExistentCourtId, testPage) + mockMvc.perform(post("/locks/{subjectType}/{subjectId}/v1/{page}", + AuditSubjectType.COURT, nonExistentCourtId, testPage) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(userId))) .andExpect(status().isNotFound()); } @Test - @DisplayName("DELETE /courts/{courtId}/v1/locks/{page} deletes lock successfully") + @DisplayName("DELETE /locks/{subjectType}/{subjectId}/v1/{page} deletes lock successfully") void deleteCourtLockSuccessfully() throws Exception { - mockMvc.perform(delete("/courts/{courtId}/v1/locks/{page}", courtId, testPage)) + mockMvc.perform(delete("/locks/{subjectType}/{subjectId}/v1/{page}", + AuditSubjectType.COURT, courtId, testPage)) .andExpect(status().isNoContent()); } @Test - @DisplayName("DELETE /courts/{courtId}/v1/locks/{page} returns 404 if court or lock not found") + @DisplayName("DELETE /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court or lock not found") void deleteCourtLockNonExistentReturnsNotFound() throws Exception { doThrow(new NotFoundException("Court or lock not found")) - .when(courtLockService).deleteLock(nonExistentCourtId, testPage); + .when(lockService).deleteLock(AuditSubjectType.COURT, nonExistentCourtId, testPage); - mockMvc.perform(delete("/courts/{courtId}/v1/locks/{page}", nonExistentCourtId, testPage)) + mockMvc.perform(delete("/locks/{subjectType}/{subjectId}/v1/{page}", + AuditSubjectType.COURT, nonExistentCourtId, testPage)) .andExpect(status().isNotFound()); } } diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java index 4c5c98c0..35bb34da 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java @@ -14,7 +14,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import uk.gov.hmcts.reform.fact.data.api.services.UserService; import java.util.List; @@ -45,7 +45,7 @@ class UserControllerTest { private UserService userService; @MockitoBean - private CourtLockService courtLockService; + private LockService lockService; private final UUID userId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); private final UUID nonExistentUserId = UUID.fromString("11111111-1111-1111-1111-111111111111"); @@ -131,7 +131,7 @@ void clearUserLocksSuccessfully() throws Exception { @DisplayName("DELETE /user/v1/{userId}/locks returns 404 if user not found") void clearUserLocksNonExistentUserReturnsNotFound() throws Exception { doThrow(new NotFoundException("User not found")) - .when(courtLockService).clearUserLocks(nonExistentUserId); + .when(lockService).clearUserLocks(nonExistentUserId); mockMvc.perform(delete("/user/v1/{userId}/locks", nonExistentUserId)) .andExpect(status().isNotFound()); diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/validator/CourtLockTimeoutValidator.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java similarity index 59% rename from src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/validator/CourtLockTimeoutValidator.java rename to src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java index abeebf20..c6992e3b 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/validator/CourtLockTimeoutValidator.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java @@ -1,6 +1,7 @@ -package uk.gov.hmcts.reform.fact.data.api.validation.validator; +package uk.gov.hmcts.reform.fact.data.api.aspect; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -11,9 +12,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ResponseStatusException; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import java.lang.reflect.Parameter; import java.time.Duration; @@ -22,34 +24,36 @@ import java.util.UUID; /** - * Aspect that intercepts methods annotated with @ValidateLockTimeout. + * Aspect that intercepts methods annotated with @LockTimeoutCheck. * Validates lock timeout before allowing the method to execute. */ +@Slf4j @Aspect @Component @RequiredArgsConstructor -public class CourtLockTimeoutValidator { +public class LockAspect { - private final CourtLockService courtLockService; + private final LockService lockService; @Value("${courtLock.timeout-minutes}") private long lockTimeoutMinutes; /** - * Executes BEFORE any method annotated with @ValidateLockTimeout. - * Extracts courtId, page, and userId from method parameters and validates lock. + * Executes BEFORE any method annotated with @LockTimeoutCheck. + * Extracts subjectType, subjectId, page, and userId from method parameters and validates lock. */ - @Before("@annotation(uk.gov.hmcts.reform.fact.data.api.validation.annotations.CourtLockTimeoutCheck)") + @Before("@annotation(uk.gov.hmcts.reform.fact.data.api.aspect.annotations.LockTimeoutCheck)") public void validateLockTimeout(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Parameter[] parameters = signature.getMethod().getParameters(); Object[] args = joinPoint.getArgs(); - UUID courtId = extractUuid(parameters, args, "courtId"); + AuditSubjectType subjectType = extractSubjectType(parameters, args, "subjectType"); + UUID subjectId = extractUuid(parameters, args, "subjectId"); Page page = extractPage(parameters, args, "page"); UUID userId = extractUuid(parameters, args, "userId"); - Optional lock = courtLockService.getPageLock(courtId, page); + Optional lock = lockService.getPageLock(subjectType, subjectId, page); if (lock.isEmpty() || lock.get().getUserId().equals(userId)) { return; // No lock or same user owns it } @@ -61,7 +65,34 @@ public void validateLockTimeout(JoinPoint joinPoint) { String.format("Page locked by another user (%d/%d min)", minutesLocked, lockTimeoutMinutes)); } - courtLockService.deleteLock(courtId, page); + lockService.deleteLock(subjectType, subjectId, page); + } + + /** + * Executes BEFORE any method annotated with @LockCleanupCheck. + */ + @Before("@annotation(uk.gov.hmcts.reform.fact.data.api.aspect.annotations.LockCleanupCheck)") + public void lockCleanup(JoinPoint joinPoint) { + try { + this.lockService.deleteExpiredLocks(); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + } + } + + /** + * Extracts SubjectType parameter by name from method arguments. + */ + private AuditSubjectType extractSubjectType(Parameter[] parameters, Object[] args, String paramName) { + for (int i = 0; i < parameters.length; i++) { + if (isNamedParameter(parameters[i], paramName)) { + Object value = args[i]; + return value instanceof String valueStr + ? AuditSubjectType.valueOf(valueStr.toUpperCase()) + : (AuditSubjectType) value; + } + } + throw new IllegalArgumentException("Required parameter not found: " + paramName); } /** @@ -71,7 +102,9 @@ private UUID extractUuid(Parameter[] parameters, Object[] args, String paramName for (int i = 0; i < parameters.length; i++) { if (isNamedParameter(parameters[i], paramName)) { Object value = args[i]; - return value instanceof String ? UUID.fromString((String) value) : (UUID) value; + return value instanceof String valueStr + ? UUID.fromString(valueStr) + : (UUID) value; } } throw new IllegalArgumentException("Required parameter not found: " + paramName); @@ -84,7 +117,9 @@ private Page extractPage(Parameter[] parameters, Object[] args, String paramName for (int i = 0; i < parameters.length; i++) { if (isNamedParameter(parameters[i], paramName)) { Object value = args[i]; - return value instanceof String ? Page.valueOf(((String) value).toUpperCase()) : (Page) value; + return value instanceof String valueStr + ? Page.valueOf(valueStr.toUpperCase()) + : (Page) value; } } throw new IllegalArgumentException("Required parameter not found: " + paramName); diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockCleanupCheck.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockCleanupCheck.java new file mode 100644 index 00000000..b33a0613 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockCleanupCheck.java @@ -0,0 +1,14 @@ +package uk.gov.hmcts.reform.fact.data.api.aspect.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * When applied to a method, forces the lock cleanup to occur before moving on. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface LockCleanupCheck { +} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/annotations/CourtLockTimeoutCheck.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockTimeoutCheck.java similarity index 77% rename from src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/annotations/CourtLockTimeoutCheck.java rename to src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockTimeoutCheck.java index 7fc4b822..feb75927 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/validation/annotations/CourtLockTimeoutCheck.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/annotations/LockTimeoutCheck.java @@ -1,4 +1,4 @@ -package uk.gov.hmcts.reform.fact.data.api.validation.annotations; +package uk.gov.hmcts.reform.fact.data.api.aspect.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -11,5 +11,5 @@ */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -public @interface CourtLockTimeoutCheck { +public @interface LockTimeoutCheck { } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java deleted file mode 100644 index e7c75053..00000000 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockController.java +++ /dev/null @@ -1,103 +0,0 @@ -package uk.gov.hmcts.reform.fact.data.api.controllers; - -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; -import uk.gov.hmcts.reform.fact.data.api.validation.annotations.CourtLockTimeoutCheck; -import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidUUID; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -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.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - -@SecuredFactRestController( - name = "Court Lock", - description = "Operations related to court lock services" -) -@RequestMapping("/courts/{courtId}") -@SuppressWarnings("java:S4684") -public class CourtLockController { - - private final CourtLockService courtLockService; - - public CourtLockController(CourtLockService courtLockService) { - this.courtLockService = courtLockService; - } - - @GetMapping("/v1/locks") - @Operation(summary = "Get all active court locks for a court") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully retrieved court locks"), - @ApiResponse(responseCode = "400", description = "Invalid court ID supplied"), - @ApiResponse(responseCode = "404", description = "Court not found") - }) - public ResponseEntity> getCourtLocks( - @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId) { - return ResponseEntity.ok(courtLockService.getLocksByCourtId(UUID.fromString(courtId))); - } - - @GetMapping("/v1/locks/{page}") - @Operation(summary = "Get lock status for a specific page of a court") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successfully retrieved lock status"), - @ApiResponse(responseCode = "204", description = "No lock data found for the specified page"), - @ApiResponse(responseCode = "400", description = "Invalid court ID or page supplied"), - @ApiResponse(responseCode = "404", description = "Court not found") - }) - public ResponseEntity getCourtLockStatus( - @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, - @Parameter(description = "Page to check lock status", required = true) @PathVariable Page page) { - return courtLockService.getPageLock(UUID.fromString(courtId), page) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.noContent().build()); - } - - @PostMapping("/v1/locks/{page}") - @CourtLockTimeoutCheck - @Operation(summary = "Create or update court lock for a specific page") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Successfully created or updated court lock"), - @ApiResponse(responseCode = "400", description = "Invalid court ID, page or user ID supplied"), - @ApiResponse(responseCode = "404", description = "Court or user not found"), - @ApiResponse(responseCode = "409", description = "Conflict with existing court lock") - }) - @PreAuthorize("@authService.isAdmin()") - public ResponseEntity createOrUpdateCourtLock( - @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, - @Parameter(description = "Page to lock", required = true) @PathVariable Page page, - @Parameter(description = "User ID creating the lock", required = true) @Valid @RequestBody UUID userId) { - return ResponseEntity.status(HttpStatus.CREATED) - .body(courtLockService.createOrUpdateLock(UUID.fromString(courtId), page, userId)); - } - - @DeleteMapping("/v1/locks/{page}") - @Operation(summary = "Delete a court lock for a specific page") - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Successfully deleted court lock"), - @ApiResponse(responseCode = "400", description = "Invalid court ID or page supplied"), - @ApiResponse(responseCode = "404", description = "Court or lock not found") - }) - @PreAuthorize("@authService.isAdmin()") - public ResponseEntity deleteCourtLock( - @Parameter(description = "UUID of the court", required = true) @ValidUUID @PathVariable String courtId, - @Parameter(description = "Page to delete lock", required = true) @PathVariable Page page) { - courtLockService.deleteLock(UUID.fromString(courtId), page); - return ResponseEntity.noContent().build(); - } -} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java new file mode 100644 index 00000000..5dc889d1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java @@ -0,0 +1,107 @@ +package uk.gov.hmcts.reform.fact.data.api.controllers; + +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; +import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; +import uk.gov.hmcts.reform.fact.data.api.aspect.annotations.LockTimeoutCheck; +import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidUUID; + +import java.util.List; +import java.util.UUID; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@SecuredFactRestController( + name = "Lock", + description = "Operations related to lock services" +) +@RequestMapping("/locks") +@SuppressWarnings("java:S4684") +public class LockController { + + private final LockService lockService; + + public LockController(LockService lockService) { + this.lockService = lockService; + } + + @GetMapping("/{subjectType}/{subjectId}/v1") + @Operation(summary = "Get all active locks for a subject") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved subject locks"), + @ApiResponse(responseCode = "400", description = "Invalid subject ID supplied"), + @ApiResponse(responseCode = "404", description = "Subject not found") + }) + public ResponseEntity> getSubjectLocks( + @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId) { + return ResponseEntity.ok(lockService.getAllSubjectLocks(subjectType, UUID.fromString(subjectId))); + } + + @GetMapping("/{subjectType}/{subjectId}/v1/{page}") + @Operation(summary = "Get lock status for a specific page of a subject") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved lock status"), + @ApiResponse(responseCode = "204", description = "No lock data found for the specified page"), + @ApiResponse(responseCode = "400", description = "Invalid subject ID or page supplied"), + @ApiResponse(responseCode = "404", description = "Subject not found") + }) + public ResponseEntity getSubjectLockStatus( + @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, + @Parameter(description = "Page to check lock status", required = true) @PathVariable Page page) { + return lockService.getPageLock(subjectType, UUID.fromString(subjectId), page) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.noContent().build()); + } + + @PostMapping("/{subjectType}/{subjectId}/v1/{page}") + @LockTimeoutCheck + @Operation(summary = "Create or update subject lock for a specific page") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Successfully created or updated subject lock"), + @ApiResponse(responseCode = "400", description = "Invalid subject ID, page or user ID supplied"), + @ApiResponse(responseCode = "404", description = "Subject or user not found"), + @ApiResponse(responseCode = "409", description = "Conflict with existing subject lock") + }) + @PreAuthorize("@authService.isAdmin()") + public ResponseEntity createOrUpdateSubjectLock( + @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, + @Parameter(description = "Page to lock", required = true) @PathVariable Page page, + @Parameter(description = "User ID creating the lock", required = true) @Valid @RequestBody UUID userId) { + return ResponseEntity.status(HttpStatus.CREATED) + .body(lockService.createOrUpdateLock(subjectType, UUID.fromString(subjectId), page, userId)); + } + + @DeleteMapping("/{subjectType}/{subjectId}/v1/{page}") + @Operation(summary = "Delete a subject lock for a specific page") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Successfully deleted subject lock"), + @ApiResponse(responseCode = "400", description = "Invalid subject ID or page supplied"), + @ApiResponse(responseCode = "404", description = "Subject or lock not found") + }) + @PreAuthorize("@authService.isAdmin()") + public ResponseEntity deleteSubjectLock( + @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, + @Parameter(description = "Page to delete lock", required = true) @PathVariable Page page) { + lockService.deleteLock(subjectType, UUID.fromString(subjectId), page); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserController.java index 5ae14c0c..524efa61 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserController.java @@ -3,7 +3,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import uk.gov.hmcts.reform.fact.data.api.services.UserService; import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidUUID; @@ -34,11 +34,11 @@ public class UserController { private final UserService userService; - private final CourtLockService courtLockService; + private final LockService lockService; - public UserController(UserService userService, CourtLockService courtLockService) { + public UserController(UserService userService, LockService lockService) { this.userService = userService; - this.courtLockService = courtLockService; + this.lockService = lockService; } @GetMapping("/v1/{userId}/favourites") @@ -96,7 +96,7 @@ public ResponseEntity deleteUserFavorite( @PreAuthorize("@authService.isAdmin()") public ResponseEntity clearUserLocks( @Parameter(description = "UUID of the user", required = true) @ValidUUID @PathVariable String userId) { - courtLockService.clearUserLocks(UUID.fromString(userId)); + lockService.clearUserLocks(UUID.fromString(userId)); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java similarity index 83% rename from src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java rename to src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java index 75651a13..16885482 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/CourtLock.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java @@ -1,11 +1,11 @@ package uk.gov.hmcts.reform.fact.data.api.entities; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import java.time.ZonedDateTime; import java.util.UUID; -import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -32,8 +32,8 @@ @NoArgsConstructor @Builder @Entity -@Table(name = "court_lock") -public class CourtLock { +@Table(name = "lock") +public class Lock { @Schema( description = "The internal ID - assigned by the server during creation", @@ -43,15 +43,13 @@ public class CourtLock { @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; - @Schema(description = "The ID of the associated Court", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull - @Column(name = "court_id") - private UUID courtId; + @Column(name = "subject_id") + private UUID subjectId; - @JsonIgnore - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "court_id", insertable = false, updatable = false) - private Court court; + @NotNull + @Enumerated(EnumType.STRING) + private AuditSubjectType subjectType; @Schema(description = "The ID of the associated User") @NotNull diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java index d6da6f21..50ad8205 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java @@ -1,17 +1,17 @@ package uk.gov.hmcts.reform.fact.data.api.entities.types; public enum Page { - COURT, - COURT_ADDRESS, - COURT_ACCESSIBILITY, - COURT_CASES_HEARD, - COURT_CONTACT_DETAILS, - COURT_COUNTER_SERVICE_OPENING_HOURS, - COURT_COURT_OPENING_HOURS, - COURT_BUILDING_FACILITIES, - COURT_INFORMATION_FOR_PROFESSIONALS, - COURT_LOCAL_AUTHORITIES, - COURT_PHOTO, - COURT_TRANSLATION_AND_INTERPRETATION, - COURT_WARNING_NOTICE; + GENERAL, + ADDRESS, + ACCESSIBILITY, + CASES_HEARD, + CONTACT_DETAILS, + COUNTER_SERVICE_OPENING_HOURS, + OPENING_HOURS, + BUILDING_FACILITIES, + INFORMATION_FOR_PROFESSIONALS, + LOCAL_AUTHORITIES, + PHOTO, + TRANSLATION_AND_INTERPRETATION, + WARNING_NOTICE; } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java deleted file mode 100644 index a5227acb..00000000 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/CourtLockRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package uk.gov.hmcts.reform.fact.data.api.repositories; - -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; - -@Repository -public interface CourtLockRepository extends JpaRepository { - void deleteAllByUserId(UUID userId); - - @EntityGraph(attributePaths = {"user"}) - List findAllByCourtId(UUID courtId); - - @EntityGraph(attributePaths = {"user"}) - Optional findByCourtIdAndPage(UUID courtId, Page page); - - void deleteByCourtIdAndPage(UUID courtId, Page page); -} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java new file mode 100644 index 00000000..64d801fa --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java @@ -0,0 +1,32 @@ +package uk.gov.hmcts.reform.fact.data.api.repositories; + +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; + +@Repository +public interface LockRepository extends JpaRepository { + void deleteAllByUserId(UUID userId); + + @EntityGraph(attributePaths = {"user"}) + List findAllBySubjectTypeAndSubjectId(AuditSubjectType subjectType, UUID subjectId); + + @EntityGraph(attributePaths = {"user"}) + Optional findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType subjectType, UUID subjectId, Page page); + + void deleteBySubjectTypeAndSubjectIdAndPage(AuditSubjectType subjectType, UUID subjectId, Page page); + + void deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(UUID userId, AuditSubjectType subjectType, Page page); + + void deleteByLockAcquiredBefore(ZonedDateTime lockAcquired); +} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java deleted file mode 100644 index 0ee0bcd7..00000000 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockService.java +++ /dev/null @@ -1,107 +0,0 @@ -package uk.gov.hmcts.reform.fact.data.api.services; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.repositories.CourtLockRepository; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; -import uk.gov.hmcts.reform.fact.data.api.entities.Court; -import uk.gov.hmcts.reform.fact.data.api.entities.User; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -/** - * Service class responsible for managing court lock operations. - * Provides functionality to handle court locking mechanisms and user-specific locks. - */ -@Service -public class CourtLockService { - - private final CourtLockRepository courtLockRepository; - private final UserService userService; - private final CourtService courtService; - - /** - * Constructs a new CourtLockService with required dependencies. - * - * @param courtLockRepository Repository for court lock operations - * @param courtService Service for court operations - * @param userService Service for user operations - */ - public CourtLockService(CourtLockRepository courtLockRepository, - CourtService courtService, - UserService userService) { - this.courtLockRepository = courtLockRepository; - this.courtService = courtService; - this.userService = userService; - } - - /** - * Delete all court locks for a given user id. - * - * @param userId The user id to clear locks for. - */ - @Transactional - public void clearUserLocks(UUID userId) { - courtLockRepository.deleteAllByUserId(userService.getUserById(userId).getId()); - } - - /** - * Gets all active locks for a specific court. - * - * @param courtId The court's unique identifier - * @return List of active court locks with court ID matching the provided ID - */ - public List getLocksByCourtId(UUID courtId) { - return courtLockRepository.findAllByCourtId(courtService.getCourtById(courtId).getId()); - } - - /** - * Checks if a specific page is locked for a court. - * - * @param courtId The court's unique identifier - * @param page The page to check - * @return Optional containing the lock if it exists - */ - public Optional getPageLock(UUID courtId, Page page) { - return courtLockRepository.findByCourtIdAndPage(courtService.getCourtById(courtId).getId(), page); - } - - /** - * Updates an existing court lock. - * - * @param courtId The court's unique identifier - * @param page The page to update - * @param userId The user's unique identifier - * @return The updated court lock - */ - public CourtLock createOrUpdateLock(UUID courtId, Page page, UUID userId) { - Court court = courtService.getCourtById(courtId); - User user = userService.getUserById(userId); - - CourtLock courtLock = courtLockRepository.findByCourtIdAndPage(court.getId(), page) - .orElse(new CourtLock()); - - courtLock.setCourtId(court.getId()); - courtLock.setUserId(user.getId()); - courtLock.setUser(user); - courtLock.setPage(page); - courtLock.setLockAcquired(ZonedDateTime.now()); - - return courtLockRepository.save(courtLock); - } - - /** - * Deletes a specific court lock. - * - * @param courtId The court's unique identifier - * @param page The page to unlock - */ - @Transactional - public void deleteLock(UUID courtId, Page page) { - courtLockRepository.deleteByCourtIdAndPage(courtService.getCourtById(courtId).getId(), page); - } -} diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java new file mode 100644 index 00000000..fdeb6591 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java @@ -0,0 +1,131 @@ +package uk.gov.hmcts.reform.fact.data.api.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; +import uk.gov.hmcts.reform.fact.data.api.repositories.LockRepository; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.User; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Service class responsible for managing court lock operations. + * Provides functionality to handle court locking mechanisms and user-specific locks. + */ +@Service +@RequiredArgsConstructor +public class LockService { + + @Value("${courtLock.timeout-minutes}") + private long lockTimeoutMinutes; + + private final LockRepository lockRepository; + private final UserService userService; + private final CourtService courtService; + private final ServiceCentreService serviceCentreService; + + /** + * Delete all court locks for a given user id. + * + * @param userId The user id to clear locks for. + */ + @Transactional + public void clearUserLocks(UUID userId) { + lockRepository.deleteAllByUserId(userService.getUserById(userId).getId()); + } + + /** + * get all active locks for the given subject. + * + * @param subjectType the subject type to get locks for + * @param subjectId the id of the subject + * @return the {@link List} of {@link Lock}s for the given subject + */ + public List getAllSubjectLocks(AuditSubjectType subjectType, UUID subjectId) { + UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + ? serviceCentreService.getServiceCentreById(subjectId).getId() + : courtService.getCourtById(subjectId).getId(); + + return lockRepository.findAllBySubjectTypeAndSubjectId(subjectType,id); + } + + /** + * Checks if a specific page is locked for a subject. + * + * @param subjectType the subject type to get locks for + * @param subjectId the id of the subject + * @param page The page to check + * @return Optional containing the lock if it exists + */ + public Optional getPageLock(AuditSubjectType subjectType, UUID subjectId, Page page) { + UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + ? serviceCentreService.getServiceCentreById(subjectId).getId() + : courtService.getCourtById(subjectId).getId(); + + return lockRepository.findBySubjectTypeAndSubjectIdAndPage(subjectType, id, page); + } + + /** + * Updates an existing subject lock. + * + * @param subjectType the subject type to get locks for + * @param subjectId the id of the subject + * @param page The page to update + * @param userId The user's unique identifier + * @return The updated court lock + */ + public Lock createOrUpdateLock(AuditSubjectType subjectType, UUID subjectId, Page page, UUID userId) { + UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + ? serviceCentreService.getServiceCentreById(subjectId).getId() + : courtService.getCourtById(subjectId).getId(); + + User user = userService.getUserById(userId); + + Lock lock = lockRepository.findBySubjectTypeAndSubjectIdAndPage(subjectType, id, page) + .orElse(new Lock()); + + lock.setSubjectId(id); + lock.setSubjectType(subjectType); + lock.setUserId(user.getId()); + lock.setUser(user); + lock.setPage(page); + lock.setLockAcquired(ZonedDateTime.now()); + + Lock savedLock = lockRepository.save(lock); + // remove any existing locks for other pages owned by this user + lockRepository.deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(user.getId(), subjectType, page); + return savedLock; + } + + /** + * Deletes a specific subject lock. + * + * @param subjectType the subject type to get locks for + * @param subjectId the id of the subject + * @param page The page to unlock + */ + @Transactional + public void deleteLock(AuditSubjectType subjectType, UUID subjectId, Page page) { + UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + ? serviceCentreService.getServiceCentreById(subjectId).getId() + : courtService.getCourtById(subjectId).getId(); + + lockRepository.deleteBySubjectTypeAndSubjectIdAndPage(subjectType, id, page); + } + + /** + * Deletes all expired locks. + */ + @Transactional + public void deleteExpiredLocks() { + lockRepository.deleteByLockAcquiredBefore(ZonedDateTime.now().minusMinutes(lockTimeoutMinutes)); + } +} diff --git a/src/main/resources/db/migration/V1.40__generalise_lock_subject.sql b/src/main/resources/db/migration/V1.40__generalise_lock_subject.sql new file mode 100644 index 00000000..6bc3a7ae --- /dev/null +++ b/src/main/resources/db/migration/V1.40__generalise_lock_subject.sql @@ -0,0 +1,20 @@ +-- remove the foreign key constraint on court_lock +ALTER TABLE court_lock + DROP CONSTRAINT fk_court_lock_court; + +-- rename court_lock to lock and add subject_type column to generalise +-- its usage for both courts and service centres +ALTER TABLE court_lock RENAME TO lock; + +ALTER TABLE lock + RENAME COLUMN court_id TO subject_id; + +ALTER TABLE lock + ADD COLUMN subject_type VARCHAR; + +UPDATE lock +SET subject_type = 'COURT' +WHERE subject_type IS NULL; + +ALTER TABLE lock + ALTER COLUMN subject_type SET NOT NULL; diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/validator/CourtLockTimeoutValidatorTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java similarity index 65% rename from src/test/java/uk/gov/hmcts/reform/fact/data/api/validator/CourtLockTimeoutValidatorTest.java rename to src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java index 42b8096c..4f28235e 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/validator/CourtLockTimeoutValidatorTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java @@ -1,4 +1,4 @@ -package uk.gov.hmcts.reform.fact.data.api.validator; +package uk.gov.hmcts.reform.fact.data.api.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; @@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ResponseStatusException; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; -import uk.gov.hmcts.reform.fact.data.api.validation.validator.CourtLockTimeoutValidator; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import java.lang.reflect.Method; import java.time.ZonedDateTime; @@ -32,10 +32,10 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class CourtLockTimeoutValidatorTest { +class LockAspectTest { @Mock - private CourtLockService courtLockService; + private LockService lockService; @Mock private JoinPoint joinPoint; @@ -43,16 +43,16 @@ class CourtLockTimeoutValidatorTest { @Mock private MethodSignature methodSignature; - private CourtLockTimeoutValidator validator; + private LockAspect validator; private final UUID courtId = UUID.randomUUID(); private final UUID userId = UUID.randomUUID(); private final UUID otherUserId = UUID.randomUUID(); - private final Page page = Page.COURT; + private final Page page = Page.GENERAL; @BeforeEach void setUp() { - validator = new CourtLockTimeoutValidator(courtLockService); + validator = new LockAspect(lockService); ReflectionTestUtils.setField(validator, "lockTimeoutMinutes", 60L); } @@ -60,29 +60,29 @@ void setUp() { @DisplayName("Should allow operation when no lock exists") void shouldAllowWhenNoLockExists() { setupJoinPoint(courtId, page, userId); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.empty()); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService, never()).deleteLock(any(), any()); + verify(lockService, never()).deleteLock(any(), any(), any()); } @Test @DisplayName("Should allow operation when same user owns the lock") void shouldAllowWhenSameUserOwnsLock() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(userId, ZonedDateTime.now().minusMinutes(30)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(userId, ZonedDateTime.now().minusMinutes(30)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService, never()).deleteLock(any(), any()); + verify(lockService, never()).deleteLock(any(), any(), any()); } @Test @DisplayName("Should throw 409 CONFLICT when lock is valid and held by another user") void shouldThrowConflictWhenLockIsValid() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(30)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(30)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); ResponseStatusException exception = assertThrows( ResponseStatusException.class, @@ -92,37 +92,37 @@ void shouldThrowConflictWhenLockIsValid() { // Then assertEquals(HttpStatus.CONFLICT, exception.getStatusCode()); assertEquals("Page locked by another user (30/60 min)", exception.getReason()); - verify(courtLockService, never()).deleteLock(any(), any()); + verify(lockService, never()).deleteLock(any(), any(), any()); } @Test @DisplayName("Should delete stale lock and allow operation when lock has exceeded timeout") void shouldDeleteStaleLockAndAllow() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(61)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(61)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService).deleteLock(courtId, page); + verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); } @Test @DisplayName("Should delete lock exactly at timeout threshold") void shouldDeleteLockAtExactTimeout() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(60)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(60)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService).deleteLock(courtId, page); + verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); } @Test @DisplayName("Should not delete lock just before timeout threshold") void shouldNotDeleteLockJustBeforeTimeout() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(59)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(59)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); ResponseStatusException exception = assertThrows( ResponseStatusException.class, @@ -130,18 +130,18 @@ void shouldNotDeleteLockJustBeforeTimeout() { ); assertEquals(HttpStatus.CONFLICT, exception.getStatusCode()); - verify(courtLockService, never()).deleteLock(any(), any()); + verify(lockService, never()).deleteLock(any(), any(), any()); } @Test @DisplayName("Should handle very old stale locks") void shouldHandleVeryOldStaleLocks() { setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusHours(24)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusHours(24)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService).deleteLock(courtId, page); + verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); } @Test @@ -149,11 +149,11 @@ void shouldHandleVeryOldStaleLocks() { void shouldRespectCustomTimeout() { ReflectionTestUtils.setField(validator, "lockTimeoutMinutes", 15L); setupJoinPoint(courtId, page, userId); - CourtLock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(20)); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.of(lock)); + Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(20)); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(courtLockService).deleteLock(courtId, page); + verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); } @Test @@ -165,7 +165,7 @@ void shouldThrowWhenCourtIdMissing() { IllegalArgumentException.class, () -> validator.validateLockTimeout(joinPoint) ); - assertEquals("Required parameter not found: courtId", exception.getMessage()); + assertEquals("Required parameter not found: subjectType", exception.getMessage()); } @Test @@ -196,7 +196,7 @@ void shouldThrowWhenUserIdMissing() { @DisplayName("Should handle courtId as String and convert to UUID") void shouldHandleCourtIdAsString() { setupJoinPointWithStringCourtId(courtId.toString(), page, userId); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.empty()); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); } @@ -204,29 +204,32 @@ void shouldHandleCourtIdAsString() { @Test @DisplayName("Should handle page as String and convert to enum") void shouldHandlePageAsString() { - setupJoinPointWithStringPage(courtId, "COURT", userId); - when(courtLockService.getPageLock(courtId, page)).thenReturn(Optional.empty()); + setupJoinPointWithStringPage(courtId, "GENERAL", userId); + when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); } // Helper methods - private CourtLock createLock(UUID userId, ZonedDateTime lockAcquired) { - CourtLock lock = new CourtLock(); + private Lock createLock(UUID userId, ZonedDateTime lockAcquired) { + Lock lock = new Lock(); lock.setUserId(userId); lock.setLockAcquired(lockAcquired); - lock.setCourtId(courtId); + lock.setSubjectType(AuditSubjectType.COURT); + lock.setSubjectId(courtId); lock.setPage(page); return lock; } private void setupJoinPoint(UUID courtId, Page page, UUID userId) { try { - Method method = TestController.class.getMethod("testMethod", UUID.class, Page.class, UUID.class); + Method method = TestController.class.getMethod("testMethod", + AuditSubjectType.class, UUID.class, Page.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{courtId, page, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{ + AuditSubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -235,10 +238,10 @@ private void setupJoinPoint(UUID courtId, Page page, UUID userId) { private void setupJoinPointWithStringCourtId(String courtId, Page page, UUID userId) { try { Method method = TestController.class.getMethod( - "testMethodWithStringCourtId", String.class, Page.class, UUID.class); + "testMethodWithStringSubjectId", AuditSubjectType.class, String.class, Page.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{courtId, page, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -247,10 +250,10 @@ private void setupJoinPointWithStringCourtId(String courtId, Page page, UUID use private void setupJoinPointWithStringPage(UUID courtId, String page, UUID userId) { try { Method method = TestController.class.getMethod( - "testMethodWithStringPage", UUID.class, String.class, UUID.class); + "testMethodWithStringPage", AuditSubjectType.class, UUID.class, String.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{courtId, page, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -258,7 +261,7 @@ private void setupJoinPointWithStringPage(UUID courtId, String page, UUID userId private void setupJoinPointWithoutCourtId(Page page, UUID userId) { try { - Method method = TestController.class.getMethod("testMethodWithoutCourtId", Page.class, UUID.class); + Method method = TestController.class.getMethod("testMethodWithoutSubject", Page.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); when(joinPoint.getArgs()).thenReturn(new Object[]{page, userId}); @@ -269,10 +272,11 @@ private void setupJoinPointWithoutCourtId(Page page, UUID userId) { private void setupJoinPointWithoutPage(UUID courtId, UUID userId) { try { - Method method = TestController.class.getMethod("testMethodWithoutPage", UUID.class, UUID.class); + Method method = TestController.class.getMethod("testMethodWithoutPage", + AuditSubjectType.class, UUID.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{courtId, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -280,10 +284,11 @@ private void setupJoinPointWithoutPage(UUID courtId, UUID userId) { private void setupJoinPointWithoutUserId(UUID courtId, Page page) { try { - Method method = TestController.class.getMethod("testMethodWithoutUserId", UUID.class, Page.class); + Method method = TestController.class.getMethod("testMethodWithoutUserId", + AuditSubjectType.class, UUID.class, Page.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{courtId, page}); + when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -293,35 +298,40 @@ private void setupJoinPointWithoutUserId(UUID courtId, Page page) { @SuppressWarnings("unused") static class TestController { public void testMethod( - @PathVariable("courtId") UUID courtId, + @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { } - public void testMethodWithStringCourtId( - @PathVariable("courtId") String courtId, + public void testMethodWithStringSubjectId( + @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectId") String subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { } public void testMethodWithStringPage( - @PathVariable("courtId") UUID courtId, + @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectId") UUID subjectId, @PathVariable("page") String page, @RequestParam("userId") UUID userId) { } - public void testMethodWithoutCourtId( + public void testMethodWithoutSubject( @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { } public void testMethodWithoutPage( - @PathVariable("courtId") UUID courtId, + @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectId") UUID subjectId, @RequestParam("userId") UUID userId) { } public void testMethodWithoutUserId( - @PathVariable("courtId") UUID courtId, + @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page) { } } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java similarity index 61% rename from src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java rename to src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java index c2f44917..3e780b23 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/CourtLockControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java @@ -7,9 +7,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import java.util.List; import java.util.Optional; @@ -20,28 +21,29 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class CourtLockControllerTest { +class LockControllerTest { private static final UUID COURT_ID = UUID.randomUUID(); private static final UUID USER_ID = UUID.randomUUID(); private static final String INVALID_UUID = "abcde"; - private static final Page TEST_PAGE = Page.COURT; + private static final Page TEST_PAGE = Page.GENERAL; private static final String RESPONSE_STATUS_MESSAGE = "Response status does not match"; private static final String RESPONSE_BODY_MESSAGE = "Response body does not match"; @Mock - private CourtLockService courtLockService; + private LockService lockService; @InjectMocks - private CourtLockController courtLockController; + private LockController lockController; @Test void getCourtLocksReturns200() { - List locks = List.of(new CourtLock()); - when(courtLockService.getLocksByCourtId(COURT_ID)).thenReturn(locks); + List locks = List.of(new Lock()); + when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, COURT_ID)).thenReturn(locks); - ResponseEntity> response = courtLockController.getCourtLocks(COURT_ID.toString()); + ResponseEntity> response = + lockController.getSubjectLocks(AuditSubjectType.COURT, COURT_ID.toString()); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).as(RESPONSE_BODY_MESSAGE).isEqualTo(locks); @@ -51,16 +53,17 @@ void getCourtLocksReturns200() { void getCourtLocksThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - courtLockController.getCourtLocks(INVALID_UUID) + lockController.getSubjectLocks(AuditSubjectType.COURT, INVALID_UUID) ); } @Test void getCourtLockStatusReturns200() { - CourtLock lock = new CourtLock(); - when(courtLockService.getPageLock(COURT_ID, TEST_PAGE)).thenReturn(Optional.of(lock)); + Lock lock = new Lock(); + when(lockService.getPageLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE)).thenReturn(Optional.of(lock)); - ResponseEntity response = courtLockController.getCourtLockStatus( + ResponseEntity response = lockController.getSubjectLockStatus( + AuditSubjectType.COURT, COURT_ID.toString(), TEST_PAGE ); @@ -73,16 +76,17 @@ void getCourtLockStatusReturns200() { void getCourtLockStatusThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - courtLockController.getCourtLockStatus(INVALID_UUID, TEST_PAGE) + lockController.getSubjectLockStatus(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE) ); } @Test void createCourtLockReturns201() { - CourtLock lock = new CourtLock(); - when(courtLockService.createOrUpdateLock(COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); + Lock lock = new Lock(); + when(lockService.createOrUpdateLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); - ResponseEntity response = courtLockController.createOrUpdateCourtLock( + ResponseEntity response = lockController.createOrUpdateSubjectLock( + AuditSubjectType.COURT, COURT_ID.toString(), TEST_PAGE, USER_ID @@ -96,16 +100,17 @@ void createCourtLockReturns201() { void createCourtLockThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - courtLockController.createOrUpdateCourtLock(INVALID_UUID, TEST_PAGE, USER_ID) + lockController.createOrUpdateSubjectLock(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE, USER_ID) ); } @Test void updateCourtLockReturns201() { - CourtLock lock = new CourtLock(); - when(courtLockService.createOrUpdateLock(COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); + Lock lock = new Lock(); + when(lockService.createOrUpdateLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); - ResponseEntity response = courtLockController.createOrUpdateCourtLock( + ResponseEntity response = lockController.createOrUpdateSubjectLock( + AuditSubjectType.COURT, COURT_ID.toString(), TEST_PAGE, USER_ID @@ -117,7 +122,8 @@ void updateCourtLockReturns201() { @Test void deleteCourtLockReturns204() { - ResponseEntity response = courtLockController.deleteCourtLock(COURT_ID.toString(), TEST_PAGE); + ResponseEntity response = + lockController.deleteSubjectLock(AuditSubjectType.COURT, COURT_ID.toString(), TEST_PAGE); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.NO_CONTENT); } @@ -126,7 +132,7 @@ void deleteCourtLockReturns204() { void deleteCourtLockThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - courtLockController.deleteCourtLock(INVALID_UUID, TEST_PAGE) + lockController.deleteSubjectLock(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE) ); } } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java index 8861fe1f..c568b4a7 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/UserControllerTest.java @@ -10,7 +10,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; -import uk.gov.hmcts.reform.fact.data.api.services.CourtLockService; +import uk.gov.hmcts.reform.fact.data.api.services.LockService; import uk.gov.hmcts.reform.fact.data.api.services.UserService; import java.util.List; @@ -37,7 +37,7 @@ class UserControllerTest { private UserService userService; @Mock - private CourtLockService courtLockService; + private LockService lockService; @InjectMocks private UserController userController; @@ -149,7 +149,7 @@ void clearUserLocksReturns204() { @Test void clearUserLocksThrowsNotFoundExceptionWhenUserNotFound() { doThrow(new NotFoundException("User not found")) - .when(courtLockService).clearUserLocks(UNKNOWN_USER_ID); + .when(lockService).clearUserLocks(UNKNOWN_USER_ID); assertThrows( NotFoundException.class, () -> diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java similarity index 57% rename from src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockServiceTest.java rename to src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java index 1ac8ec1e..218d7ac4 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/CourtLockServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java @@ -7,11 +7,12 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.reform.fact.data.api.entities.User; +import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.entities.Court; -import uk.gov.hmcts.reform.fact.data.api.entities.CourtLock; +import uk.gov.hmcts.reform.fact.data.api.entities.Lock; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; -import uk.gov.hmcts.reform.fact.data.api.repositories.CourtLockRepository; +import uk.gov.hmcts.reform.fact.data.api.repositories.LockRepository; import java.time.ZonedDateTime; import java.util.List; @@ -27,10 +28,10 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class CourtLockServiceTest { +class LockServiceTest { @Mock - private CourtLockRepository courtLockRepository; + private LockRepository lockRepository; @Mock private CourtService courtService; @@ -39,13 +40,13 @@ class CourtLockServiceTest { private UserService userService; @InjectMocks - private CourtLockService courtLockService; + private LockService lockService; private UUID userId; private User user; private UUID courtId; private Court court; - private CourtLock lock; + private Lock lock; @BeforeEach void setup() { @@ -59,25 +60,26 @@ void setup() { user.setId(userId); user.setEmail("email@justice.gov.uk"); - lock = new CourtLock(); - lock.setCourtId(courtId); - lock.setPage(Page.COURT); + lock = new Lock(); + lock.setSubjectType(AuditSubjectType.COURT); + lock.setSubjectId(courtId); + lock.setPage(Page.GENERAL); lock.setLockAcquired(ZonedDateTime.now()); } @Test void clearUserLocksShouldDeleteAllLocksForUser() { when(userService.getUserById(userId)).thenReturn(user); - courtLockService.clearUserLocks(userId); + lockService.clearUserLocks(userId); - verify(courtLockRepository).deleteAllByUserId(userId); + verify(lockRepository).deleteAllByUserId(userId); } @Test void clearUserLocksShouldThrowNotFoundExceptionWhenUserDoesNotExist() { when(userService.getUserById(userId)).thenThrow(new NotFoundException("User not found")); - assertThrows(NotFoundException.class, () -> courtLockService.clearUserLocks(userId)); + assertThrows(NotFoundException.class, () -> lockService.clearUserLocks(userId)); } @Test @@ -86,62 +88,65 @@ void deleteLocksByUserIdShouldHandleNoLocksForUser() { User newUser = new User(); newUser.setId(newUserId); when(userService.getUserById(newUserId)).thenReturn(newUser); - courtLockService.clearUserLocks(newUserId); - verify(courtLockRepository).deleteAllByUserId(newUserId); + lockService.clearUserLocks(newUserId); + verify(lockRepository).deleteAllByUserId(newUserId); } @Test void getLocksByCourtIdShouldGetLocksByCourtId() { when(courtService.getCourtById(courtId)).thenReturn(court); - when(courtLockRepository.findAllByCourtId(courtId)).thenReturn(List.of(lock)); - List result = courtLockService.getLocksByCourtId(courtId); + when(lockRepository.findAllBySubjectTypeAndSubjectId(AuditSubjectType.COURT, courtId)) + .thenReturn(List.of(lock)); + List result = lockService.getAllSubjectLocks(AuditSubjectType.COURT, courtId); assertEquals(1, result.size()); verify(courtService).getCourtById(courtId); - verify(courtLockRepository).findAllByCourtId(courtId); + verify(lockRepository).findAllBySubjectTypeAndSubjectId(AuditSubjectType.COURT, courtId); } @Test void getPageLockShouldGetPageLock() { - Page page = Page.COURT; + Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); - when(courtLockRepository.findByCourtIdAndPage(courtId, page)).thenReturn(Optional.of(lock)); - Optional result = courtLockService.getPageLock(courtId, page); + when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page)) + .thenReturn(Optional.of(lock)); + Optional result = lockService.getPageLock(AuditSubjectType.COURT, courtId, page); assertTrue(result.isPresent()); verify(courtService).getCourtById(courtId); - verify(courtLockRepository).findByCourtIdAndPage(courtId, page); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); } @Test void createLockShouldCreateLock() { when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); - when(courtLockRepository.save(any(CourtLock.class))).thenReturn(lock); - CourtLock result = courtLockService.createOrUpdateLock(courtId, Page.COURT, userId); + when(lockRepository.save(any(Lock.class))).thenReturn(lock); + Lock result = lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, Page.GENERAL, userId); assertNotNull(result); - verify(courtLockRepository).save(any(CourtLock.class)); + verify(lockRepository).save(any(Lock.class)); } @Test void createOrUpdateLockShouldUpdateExistingLock() { - Page page = Page.COURT; + Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); - when(courtLockRepository.findByCourtIdAndPage(courtId, page)).thenReturn(Optional.of(lock)); - when(courtLockRepository.save(any(CourtLock.class))).thenReturn(lock); - CourtLock result = courtLockService.createOrUpdateLock(courtId, page, userId); + when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page)) + .thenReturn(Optional.of(lock)); + when(lockRepository.save(any(Lock.class))).thenReturn(lock); + Lock result = lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, page, userId); assertNotNull(result); verify(courtService).getCourtById(courtId); - verify(courtLockRepository).findByCourtIdAndPage(courtId, page); - verify(courtLockRepository).save(any(CourtLock.class)); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); + verify(lockRepository).save(any(Lock.class)); } @Test void deleteLockShouldDeleteLock() { - Page page = Page.COURT; + Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); - courtLockService.deleteLock(courtId, page); + lockService.deleteLock(AuditSubjectType.COURT, courtId, page); verify(courtService).getCourtById(courtId); - verify(courtLockRepository).deleteByCourtIdAndPage(courtId, page); + verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); } } From 9144e81f072f2dd98b4e6a9798e114c424855e0f Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Mon, 6 Jul 2026 16:50:33 +0100 Subject: [PATCH 06/11] revert build.gradle change that downgrades the blob store api. --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0096e014..debe6f45 100644 --- a/build.gradle +++ b/build.gradle @@ -210,8 +210,6 @@ dependencies { implementation group: 'com.slack.api', name: 'slack-api-client', version: '1.49.0' - implementation group: 'com.azure', name: 'azure-storage-blob', version: '12.26.0' - //Solves CVE-2025-48976 implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.6.0' From ac28b6a8c6f0c3998cc6cc8fe82a82e6e447bedd Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Tue, 7 Jul 2026 09:04:31 +0100 Subject: [PATCH 07/11] Rename `AuditSubjectType` to `SubjectType` and add SPoE Page enum --- .../controllers/AuthFunctionalTest.java | 8 +- .../LockControllerFunctionalTest.java | 18 ++--- .../UserControllerFunctionalTest.java | 8 +- .../functional/helpers/TestDataHelper.java | 4 +- .../fact/data/api/audit/AuditingTest.java | 4 +- .../api/controllers/AuditControllerTest.java | 6 +- .../api/controllers/LockControllerTest.java | 34 ++++---- .../fact/data/api/aspect/LockAspect.java | 10 +-- .../data/api/controllers/AuditController.java | 6 +- .../data/api/controllers/LockController.java | 10 +-- .../reform/fact/data/api/entities/Audit.java | 4 +- .../api/entities/AuditableCourtEntity.java | 6 +- .../data/api/entities/AuditableEntity.java | 4 +- .../reform/fact/data/api/entities/Lock.java | 4 +- .../fact/data/api/entities/ServiceCentre.java | 6 +- .../api/entities/ServiceCentreAddress.java | 6 +- .../api/entities/ServiceCentreAreasOfLaw.java | 6 +- .../entities/ServiceCentreContactDetails.java | 6 +- .../fact/data/api/entities/types/Page.java | 1 + ...AuditSubjectType.java => SubjectType.java} | 2 +- .../api/repositories/AuditRepository.java | 18 ++--- .../data/api/repositories/LockRepository.java | 10 +-- .../fact/data/api/services/AuditService.java | 24 +++--- .../fact/data/api/services/LockService.java | 18 ++--- .../fact/data/api/aspect/LockAspectTest.java | 62 +++++++-------- .../AuditableCourtEntityListenerTest.java | 4 +- .../api/controllers/AuditControllerTest.java | 12 +-- .../api/controllers/LockControllerTest.java | 28 +++---- .../data/api/services/AuditServiceTest.java | 78 +++++++++---------- .../data/api/services/LockServiceTest.java | 28 +++---- 30 files changed, 218 insertions(+), 217 deletions(-) rename src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/{AuditSubjectType.java => SubjectType.java} (82%) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java index 86138451..af32dfc0 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/AuthFunctionalTest.java @@ -25,7 +25,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.ServiceCentreContactDetails; import uk.gov.hmcts.reform.fact.data.api.entities.User; import uk.gov.hmcts.reform.fact.data.api.entities.types.AddressType; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.DayOfTheWeek; import uk.gov.hmcts.reform.fact.data.api.entities.types.HearingEnhancementEquipment; import uk.gov.hmcts.reform.fact.data.api.entities.types.OpeningTimesDetail; @@ -529,14 +529,14 @@ void courtLocalAuthoritiesAuth() throws Exception { void courtLockAuth() throws Exception { UUID courtId = createCourtAsAdmin("Test Auth Lock"); UUID userId = createUserAsAdmin("test.user.auth.lock"); - String lockPath = "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL; + String lockPath = "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL; Response postAdmin = http.doPost(lockPath, mapper.writeValueAsString(userId), adminToken); assertThat(postAdmin.statusCode()).isIn(200, 201); assertViewerAllowed( - http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/", adminToken), - http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/", viewerToken), + http.doGet("/locks/" + SubjectType.COURT + "/" + courtId + "/v1/", adminToken), + http.doGet("/locks/" + SubjectType.COURT + "/" + courtId + "/v1/", viewerToken), "/locks/{subjectType}/{subjectId}/v1 [GET]" ); assertViewerAllowed( diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java index ac16b922..a146f7ee 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.functional.helpers.TestDataHelper; import uk.gov.hmcts.reform.fact.functional.http.HttpClient; @@ -66,7 +66,7 @@ void shouldGetCourtLockStatusSuccessfully() throws Exception { TestDataHelper.createCourtLock(http, courtId, Page.ACCESSIBILITY, userId); final Response getLockStatusResponse = http.doGet( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.ACCESSIBILITY + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.ACCESSIBILITY ); assertThat(getLockStatusResponse.statusCode()) @@ -107,7 +107,7 @@ void shouldDeleteCourtLockSuccessfully() throws Exception { TestDataHelper.createCourtLock(http, courtId, Page.LOCAL_AUTHORITIES, userId); final Response deleteLockResponse = http.doDelete( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(deleteLockResponse.statusCode()) @@ -116,7 +116,7 @@ void shouldDeleteCourtLockSuccessfully() throws Exception { .isEqualTo(NO_CONTENT.value()); final Response getLockStatusResponse = http.doGet( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(getLockStatusResponse.statusCode()) @@ -137,7 +137,7 @@ void shouldGetAllLocksForCourtSuccessfully() throws Exception { TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userId); TestDataHelper.createCourtLock(http, courtId, Page.ACCESSIBILITY, userId); - final Response getAllLocksResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1"); + final Response getAllLocksResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + courtId + "/v1"); assertThat(getAllLocksResponse.statusCode()) .as("Expected 200 OK when getting all locks for court %s", courtId) @@ -175,7 +175,7 @@ void shouldReturnConflictWhenPageLockedByAnotherUser() throws Exception { TestDataHelper.createCourtLock(http, courtId, Page.GENERAL, userAId); final Response userBLockResponse = http.doPost( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL, + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.GENERAL, mapper.writeValueAsString(userBId) ); @@ -245,7 +245,7 @@ void shouldCompleteEndToEndLockWorkflow() throws Exception { .isEqualTo(courtId); final Response getLockStatusResponse = http.doGet( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(getLockStatusResponse.statusCode()) @@ -271,7 +271,7 @@ void shouldCompleteEndToEndLockWorkflow() throws Exception { .isAfter(createdLock.getLockAcquired()); final Response deleteLockResponse = http.doDelete( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(deleteLockResponse.statusCode()) @@ -279,7 +279,7 @@ void shouldCompleteEndToEndLockWorkflow() throws Exception { .isEqualTo(NO_CONTENT.value()); final Response verifyDeletedResponse = http.doGet( - "/locks/" + AuditSubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES + "/locks/" + SubjectType.COURT + "/" + courtId + "/v1/" + Page.LOCAL_AUTHORITIES ); assertThat(verifyDeletedResponse.statusCode()) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java index 33caf73a..abd11375 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java @@ -11,7 +11,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; import uk.gov.hmcts.reform.fact.data.api.entities.User; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; import uk.gov.hmcts.reform.fact.functional.helpers.TestDataHelper; @@ -275,7 +275,7 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { TestDataHelper.createCourtLock(http, court1Id, Page.GENERAL, userId); TestDataHelper.createCourtLock(http, court2Id, Page.ACCESSIBILITY, userId); - final Response getLocksBeforeClearResponse = http.doGet("/locks/" + AuditSubjectType.COURT + final Response getLocksBeforeClearResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + court1Id + "/v1"); assertThat(getLocksBeforeClearResponse.statusCode()) @@ -295,7 +295,7 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { .as("Expected 204 NO CONTENT when clearing locks for user %s", userId) .isEqualTo(NO_CONTENT.value()); - final Response getLock1StatusResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + final Response getLock1StatusResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + court1Id + "/v1/" + Page.GENERAL); assertThat(getLock1StatusResponse.statusCode()) @@ -306,7 +306,7 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { .as("Expected empty response after clearing user locks") .isEqualTo(""); - final Response getLock2StatusResponse = http.doGet("/locks/" + AuditSubjectType.COURT + "/" + final Response getLock2StatusResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + court2Id + "/v1/" + Page.ACCESSIBILITY); assertThat(getLock2StatusResponse.statusCode()) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java index bdad9ef3..c36b776a 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/helpers/TestDataHelper.java @@ -9,7 +9,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Lock; 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.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.entities.types.UserRole; @@ -302,7 +302,7 @@ public static uk.gov.hmcts.reform.fact.data.api.entities.CourtAreasOfLaw buildCo public static Lock createCourtLock(final HttpClient http, final UUID courtId, final Page page, final UUID userId) throws Exception { final Response response = http.doPost( - "/locks/" + AuditSubjectType.COURT.name() + "/" + courtId + "/v1/" + page, + "/locks/" + SubjectType.COURT.name() + "/" + courtId + "/v1/" + page, mapper.writeValueAsString(userId) ); 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 e9a7db14..d3a56f0a 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 @@ -11,7 +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.SubjectType; 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.CourtPhotoRepository; @@ -142,7 +142,7 @@ void creatingAndUpdatingAServiceCentreShouldCreateAuditRecords() { Page audits = auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfter( serviceCentre.getId(), - AuditSubjectType.SERVICE_CENTRE, + SubjectType.SERVICE_CENTRE, CREATED_AFTER.atStartOfDay().atZone(java.time.ZoneOffset.UTC), org.springframework.data.domain.PageRequest.of(0, 1000) ); 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 bbbd0373..2e0d5834 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,7 +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.SubjectType; 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; @@ -357,12 +357,12 @@ void getFilteredAndPaginatedAuditsReturnsResultsWhenSubjectTypeProvided() throws .param("pageNumber", "0") .param("pageSize", "10") .param("fromDate", LocalDate.now().toString()) - .param("subjectType", AuditSubjectType.COURT.name())) + .param("subjectType", SubjectType.COURT.name())) .andExpect(status().isOk()) .andExpect(jsonPath("$.content").isArray()) .andExpect(jsonPath("$.content.length()").value(2)) .andExpect(jsonPath("$.content[*].subjectType") - .value(allElementsEqual(AuditSubjectType.COURT.name()))) + .value(allElementsEqual(SubjectType.COURT.name()))) .andExpect(jsonPath("$.page.number").value(0)) .andExpect(jsonPath("$.page.totalElements").value(2)); } diff --git a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java index ba58fefb..ffc6cfc4 100644 --- a/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java @@ -11,7 +11,7 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.services.LockService; @@ -52,9 +52,9 @@ class LockControllerTest { @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns court locks successfully") void getCourtLocksReturnsSuccessfully() throws Exception { List locks = List.of(new Lock()); - when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, courtId)).thenReturn(locks); + when(lockService.getAllSubjectLocks(SubjectType.COURT, courtId)).thenReturn(locks); - mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", AuditSubjectType.COURT, courtId)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", SubjectType.COURT, courtId)) .andExpect(status().isOk()) .andExpect(jsonPath("$").isArray()); } @@ -62,17 +62,17 @@ void getCourtLocksReturnsSuccessfully() throws Exception { @Test @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns 404 if court does not exist") void getCourtLocksNonExistentReturnsNotFound() throws Exception { - when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, nonExistentCourtId)) + when(lockService.getAllSubjectLocks(SubjectType.COURT, nonExistentCourtId)) .thenThrow(new NotFoundException("Court not found")); - mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", AuditSubjectType.COURT, nonExistentCourtId)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", SubjectType.COURT, nonExistentCourtId)) .andExpect(status().isNotFound()); } @Test @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/returns 400 for invalid UUID") void getCourtLocksInvalidUUID() throws Exception { - mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1",AuditSubjectType.COURT, "invalid-uuid")) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1", SubjectType.COURT, "invalid-uuid")) .andExpect(status().isBadRequest()); } @@ -80,20 +80,20 @@ void getCourtLocksInvalidUUID() throws Exception { @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/{page} returns lock status successfully") void getCourtLockStatusReturnsSuccessfully() throws Exception { Lock lock = new Lock(); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, testPage)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, testPage)).thenReturn(Optional.of(lock)); - mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1/{page}", AuditSubjectType.COURT, courtId, testPage)) + mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1/{page}", SubjectType.COURT, courtId, testPage)) .andExpect(status().isOk()); } @Test @DisplayName("GET /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court not found") void getCourtLockStatusNonExistentReturnsNotFound() throws Exception { - when(lockService.getPageLock(AuditSubjectType.COURT, nonExistentCourtId, testPage)) + when(lockService.getPageLock(SubjectType.COURT, nonExistentCourtId, testPage)) .thenThrow(new NotFoundException("Court not found")); mockMvc.perform(get("/locks/{subjectType}/{subjectId}/v1/{page}", - AuditSubjectType.COURT, nonExistentCourtId, testPage)) + SubjectType.COURT, nonExistentCourtId, testPage)) .andExpect(status().isNotFound()); } @@ -101,9 +101,9 @@ void getCourtLockStatusNonExistentReturnsNotFound() throws Exception { @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} creates lock successfully") void createCourtLockSuccessfully() throws Exception { Lock lock = new Lock(); - when(lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, testPage, userId)).thenReturn(lock); + when(lockService.createOrUpdateLock(SubjectType.COURT, courtId, testPage, userId)).thenReturn(lock); - mockMvc.perform(post("/locks/{subjectType}/{subjectId}/v1/{page}", AuditSubjectType.COURT, courtId, testPage) + mockMvc.perform(post("/locks/{subjectType}/{subjectId}/v1/{page}", SubjectType.COURT, courtId, testPage) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(userId))) .andExpect(status().isCreated()); @@ -112,11 +112,11 @@ void createCourtLockSuccessfully() throws Exception { @Test @DisplayName("POST /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court not found") void createCourtLockNonExistentReturnsNotFound() throws Exception { - when(lockService.createOrUpdateLock(AuditSubjectType.COURT, nonExistentCourtId, testPage, userId)) + when(lockService.createOrUpdateLock(SubjectType.COURT, nonExistentCourtId, testPage, userId)) .thenThrow(new NotFoundException("Court not found")); mockMvc.perform(post("/locks/{subjectType}/{subjectId}/v1/{page}", - AuditSubjectType.COURT, nonExistentCourtId, testPage) + SubjectType.COURT, nonExistentCourtId, testPage) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(userId))) .andExpect(status().isNotFound()); @@ -126,7 +126,7 @@ void createCourtLockNonExistentReturnsNotFound() throws Exception { @DisplayName("DELETE /locks/{subjectType}/{subjectId}/v1/{page} deletes lock successfully") void deleteCourtLockSuccessfully() throws Exception { mockMvc.perform(delete("/locks/{subjectType}/{subjectId}/v1/{page}", - AuditSubjectType.COURT, courtId, testPage)) + SubjectType.COURT, courtId, testPage)) .andExpect(status().isNoContent()); } @@ -134,10 +134,10 @@ void deleteCourtLockSuccessfully() throws Exception { @DisplayName("DELETE /locks/{subjectType}/{subjectId}/v1/{page} returns 404 if court or lock not found") void deleteCourtLockNonExistentReturnsNotFound() throws Exception { doThrow(new NotFoundException("Court or lock not found")) - .when(lockService).deleteLock(AuditSubjectType.COURT, nonExistentCourtId, testPage); + .when(lockService).deleteLock(SubjectType.COURT, nonExistentCourtId, testPage); mockMvc.perform(delete("/locks/{subjectType}/{subjectId}/v1/{page}", - AuditSubjectType.COURT, nonExistentCourtId, testPage)) + SubjectType.COURT, nonExistentCourtId, testPage)) .andExpect(status().isNotFound()); } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java index c6992e3b..e5a9a3b4 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ResponseStatusException; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.services.LockService; @@ -48,7 +48,7 @@ public void validateLockTimeout(JoinPoint joinPoint) { Parameter[] parameters = signature.getMethod().getParameters(); Object[] args = joinPoint.getArgs(); - AuditSubjectType subjectType = extractSubjectType(parameters, args, "subjectType"); + SubjectType subjectType = extractSubjectType(parameters, args, "subjectType"); UUID subjectId = extractUuid(parameters, args, "subjectId"); Page page = extractPage(parameters, args, "page"); UUID userId = extractUuid(parameters, args, "userId"); @@ -83,13 +83,13 @@ public void lockCleanup(JoinPoint joinPoint) { /** * Extracts SubjectType parameter by name from method arguments. */ - private AuditSubjectType extractSubjectType(Parameter[] parameters, Object[] args, String paramName) { + private SubjectType extractSubjectType(Parameter[] parameters, Object[] args, String paramName) { for (int i = 0; i < parameters.length; i++) { if (isNamedParameter(parameters[i], paramName)) { Object value = args[i]; return value instanceof String valueStr - ? AuditSubjectType.valueOf(valueStr.toUpperCase()) - : (AuditSubjectType) value; + ? SubjectType.valueOf(valueStr.toUpperCase()) + : (SubjectType) value; } } throw new IllegalArgumentException("Required parameter not found: " + paramName); 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 85704706..fbbec373 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,7 +1,7 @@ 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.SubjectType; 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; @@ -58,7 +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 = "subjectType", required = false) SubjectType 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) @@ -131,7 +131,7 @@ public ResponseEntity removeExpiredAuditEntries() { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved map of subject-> name+id pairs") }) - public ResponseEntity>> getSubjectNameAndIdMap() { + public ResponseEntity>> getSubjectNameAndIdMap() { return ResponseEntity.ok(this.auditService.getSubjectNameAndIdMap()); } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java index 5dc889d1..5467eb11 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java @@ -1,7 +1,7 @@ package uk.gov.hmcts.reform.fact.data.api.controllers; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.security.SecuredFactRestController; import uk.gov.hmcts.reform.fact.data.api.services.LockService; @@ -48,7 +48,7 @@ public LockController(LockService lockService) { @ApiResponse(responseCode = "404", description = "Subject not found") }) public ResponseEntity> getSubjectLocks( - @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "The subject type", required = true) @PathVariable SubjectType subjectType, @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId) { return ResponseEntity.ok(lockService.getAllSubjectLocks(subjectType, UUID.fromString(subjectId))); } @@ -62,7 +62,7 @@ public ResponseEntity> getSubjectLocks( @ApiResponse(responseCode = "404", description = "Subject not found") }) public ResponseEntity getSubjectLockStatus( - @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "The subject type", required = true) @PathVariable SubjectType subjectType, @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, @Parameter(description = "Page to check lock status", required = true) @PathVariable Page page) { return lockService.getPageLock(subjectType, UUID.fromString(subjectId), page) @@ -81,7 +81,7 @@ public ResponseEntity getSubjectLockStatus( }) @PreAuthorize("@authService.isAdmin()") public ResponseEntity createOrUpdateSubjectLock( - @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "The subject type", required = true) @PathVariable SubjectType subjectType, @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, @Parameter(description = "Page to lock", required = true) @PathVariable Page page, @Parameter(description = "User ID creating the lock", required = true) @Valid @RequestBody UUID userId) { @@ -98,7 +98,7 @@ public ResponseEntity createOrUpdateSubjectLock( }) @PreAuthorize("@authService.isAdmin()") public ResponseEntity deleteSubjectLock( - @Parameter(description = "The subject type", required = true) @PathVariable AuditSubjectType subjectType, + @Parameter(description = "The subject type", required = true) @PathVariable SubjectType subjectType, @Parameter(description = "UUID of the subject", required = true) @ValidUUID @PathVariable String subjectId, @Parameter(description = "Page to delete lock", required = true) @PathVariable Page page) { lockService.deleteLock(subjectType, UUID.fromString(subjectId), page); 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 4b5fbc96..d3b2cf07 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 @@ -1,7 +1,7 @@ package uk.gov.hmcts.reform.fact.data.api.entities; 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.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Change; import java.time.ZonedDateTime; @@ -54,7 +54,7 @@ public class Audit { @NotNull @Enumerated(EnumType.STRING) - private AuditSubjectType subjectType; + private SubjectType subjectType; @NotNull @Column(name = "user_id") diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableCourtEntity.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableCourtEntity.java index 1cf6f576..f6a225f1 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableCourtEntity.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableCourtEntity.java @@ -1,6 +1,6 @@ package uk.gov.hmcts.reform.fact.data.api.entities; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import java.util.UUID; @@ -13,7 +13,7 @@ default UUID getAuditSubjectId() { } @Override - default AuditSubjectType getAuditSubjectType() { - return AuditSubjectType.COURT; + default SubjectType getAuditSubjectType() { + return SubjectType.COURT; } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableEntity.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableEntity.java index d1438f40..8b68822f 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableEntity.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/AuditableEntity.java @@ -1,7 +1,7 @@ package uk.gov.hmcts.reform.fact.data.api.entities; import com.fasterxml.jackson.annotation.JsonIgnore; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import java.util.UUID; @@ -12,5 +12,5 @@ public interface AuditableEntity { UUID getAuditSubjectId(); @JsonIgnore - AuditSubjectType getAuditSubjectType(); + SubjectType getAuditSubjectType(); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java index 16885482..a1cae865 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java @@ -1,6 +1,6 @@ package uk.gov.hmcts.reform.fact.data.api.entities; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import java.time.ZonedDateTime; @@ -49,7 +49,7 @@ public class Lock { @NotNull @Enumerated(EnumType.STRING) - private AuditSubjectType subjectType; + private SubjectType subjectType; @Schema(description = "The ID of the associated User") @NotNull diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentre.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentre.java index 2542bab8..3a1dda23 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentre.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentre.java @@ -27,7 +27,7 @@ import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.type.SqlTypes; import uk.gov.hmcts.reform.fact.data.api.audit.AuditableCourtEntityListener; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.CatchmentType; import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidCourtSlug; @@ -117,7 +117,7 @@ public UUID getAuditSubjectId() { } @Override - public AuditSubjectType getAuditSubjectType() { - return AuditSubjectType.SERVICE_CENTRE; + public SubjectType getAuditSubjectType() { + return SubjectType.SERVICE_CENTRE; } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAddress.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAddress.java index ad95c327..7bce630b 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAddress.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAddress.java @@ -25,7 +25,7 @@ import lombok.NoArgsConstructor; import uk.gov.hmcts.reform.fact.data.api.audit.AuditableCourtEntityListener; import uk.gov.hmcts.reform.fact.data.api.controllers.ServiceCentreController.ServiceCentreDetailsView; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.AddressType; import uk.gov.hmcts.reform.fact.data.api.entities.validation.ValidationConstants; import uk.gov.hmcts.reform.fact.data.api.validation.annotations.ValidPostcode; @@ -107,7 +107,7 @@ public UUID getAuditSubjectId() { } @Override - public AuditSubjectType getAuditSubjectType() { - return AuditSubjectType.SERVICE_CENTRE; + public SubjectType getAuditSubjectType() { + return SubjectType.SERVICE_CENTRE; } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAreasOfLaw.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAreasOfLaw.java index 82783123..954df85d 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAreasOfLaw.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreAreasOfLaw.java @@ -26,7 +26,7 @@ import org.hibernate.type.SqlTypes; import uk.gov.hmcts.reform.fact.data.api.audit.AuditableCourtEntityListener; import uk.gov.hmcts.reform.fact.data.api.controllers.ServiceCentreController.ServiceCentreDetailsView; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import java.util.List; import java.util.Map; @@ -115,7 +115,7 @@ public UUID getAuditSubjectId() { } @Override - public AuditSubjectType getAuditSubjectType() { - return AuditSubjectType.SERVICE_CENTRE; + public SubjectType getAuditSubjectType() { + return SubjectType.SERVICE_CENTRE; } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreContactDetails.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreContactDetails.java index 667a3340..803f7ae4 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreContactDetails.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/ServiceCentreContactDetails.java @@ -26,7 +26,7 @@ import lombok.NoArgsConstructor; import uk.gov.hmcts.reform.fact.data.api.audit.AuditableCourtEntityListener; import uk.gov.hmcts.reform.fact.data.api.controllers.ServiceCentreController.ServiceCentreDetailsView; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.validation.ValidationConstants; import java.util.UUID; @@ -122,7 +122,7 @@ public UUID getAuditSubjectId() { } @Override - public AuditSubjectType getAuditSubjectType() { - return AuditSubjectType.SERVICE_CENTRE; + public SubjectType getAuditSubjectType() { + return SubjectType.SERVICE_CENTRE; } } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java index 50ad8205..6ca3eb73 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/Page.java @@ -13,5 +13,6 @@ public enum Page { LOCAL_AUTHORITIES, PHOTO, TRANSLATION_AND_INTERPRETATION, + SINGLE_POINT_OF_ENTRY, WARNING_NOTICE; } 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/SubjectType.java similarity index 82% rename from src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/AuditSubjectType.java rename to src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/types/SubjectType.java index 4427dcf3..532a4418 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/SubjectType.java @@ -3,7 +3,7 @@ /** * Subject types that are applicable for auditing. */ -public enum AuditSubjectType { +public enum SubjectType { COURT, SERVICE_CENTRE } 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 114d7a1c..99fea321 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 @@ -1,7 +1,7 @@ package uk.gov.hmcts.reform.fact.data.api.repositories; 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.SubjectType; import java.time.ZonedDateTime; import java.util.List; @@ -34,20 +34,20 @@ Page findByCreatedAtBetween( @EntityGraph(attributePaths = {"user"}) Page findBySubjectTypeAndCreatedAtAfter( - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, Pageable pageable); @EntityGraph(attributePaths = {"user"}) Page findBySubjectIdAndSubjectTypeAndCreatedAtAfter( UUID subjectId, - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAt, Pageable pageable); @EntityGraph(attributePaths = {"user"}) Page findBySubjectTypeAndCreatedAtBetween( - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, ZonedDateTime createdAtBefore, Pageable pageable); @@ -55,7 +55,7 @@ Page findBySubjectTypeAndCreatedAtBetween( @EntityGraph(attributePaths = {"user"}) Page findBySubjectIdAndSubjectTypeAndCreatedAtBetween( UUID subjectId, - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, ZonedDateTime createdAtBefore, Pageable pageable); @@ -105,7 +105,7 @@ Page findByCreatedAtBetweenAndEmailAddressLike( @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER + AND_SUBJECT_EQUALS) Page findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( UUID subjectId, - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, String email, Pageable pageable); @@ -114,7 +114,7 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_BETWEEN + AND_SUBJECT_EQUALS) Page findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( UUID subjectId, - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, ZonedDateTime createdAtBefore, String email, @@ -123,7 +123,7 @@ Page findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_AFTER + AND_SUBJECT_TYPE_EQUALS) Page findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, String email, Pageable pageable); @@ -131,7 +131,7 @@ Page findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( @EntityGraph(attributePaths = {"user"}) @Query(value = SELECT_WITH_USER_JOIN + WHERE_EMAIL_LIKE_AND_CREATED_AT_BETWEEN + AND_SUBJECT_TYPE_EQUALS) Page findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( - AuditSubjectType subjectType, + SubjectType subjectType, ZonedDateTime createdAtAfter, ZonedDateTime createdAtBefore, String email, diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java index 64d801fa..6215b5ce 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java @@ -11,7 +11,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; @Repository @@ -19,14 +19,14 @@ public interface LockRepository extends JpaRepository { void deleteAllByUserId(UUID userId); @EntityGraph(attributePaths = {"user"}) - List findAllBySubjectTypeAndSubjectId(AuditSubjectType subjectType, UUID subjectId); + List findAllBySubjectTypeAndSubjectId(SubjectType subjectType, UUID subjectId); @EntityGraph(attributePaths = {"user"}) - Optional findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType subjectType, UUID subjectId, Page page); + Optional findBySubjectTypeAndSubjectIdAndPage(SubjectType subjectType, UUID subjectId, Page page); - void deleteBySubjectTypeAndSubjectIdAndPage(AuditSubjectType subjectType, UUID subjectId, Page page); + void deleteBySubjectTypeAndSubjectIdAndPage(SubjectType subjectType, UUID subjectId, Page page); - void deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(UUID userId, AuditSubjectType subjectType, Page page); + void deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(UUID userId, SubjectType subjectType, Page page); void deleteByLockAcquiredBefore(ZonedDateTime lockAcquired); } 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 680aed57..a77feddb 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 @@ -2,7 +2,7 @@ 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.SubjectType; 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; @@ -47,7 +47,7 @@ public class AuditService { * @return a {@link Page} of {@link Audit} results. */ public Page getFilteredAndPaginatedAudits(int pageNumber, int pageSize, @NonNull LocalDate fromDate, - LocalDate toDate, AuditSubjectType subjectType, String courtId, + LocalDate toDate, SubjectType subjectType, String courtId, String serviceCentreId, String email) { Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending()); @@ -109,7 +109,7 @@ private Page performAuditQuery(ZonedDateTime fromDateTime, ZonedDateTime private Page performSubjectAuditQuery(ZonedDateTime fromDateTime, ZonedDateTime toDateTime, SubjectFilter subjectFilter, String email, Pageable pageable) { UUID subjectId = subjectFilter.subjectId(); - AuditSubjectType subjectType = subjectFilter.subjectType(); + SubjectType subjectType = subjectFilter.subjectType(); boolean hasToDate = toDateTime != null; boolean hasEmail = email != null && !email.isBlank(); @@ -130,7 +130,7 @@ private Page performSubjectAuditQuery(ZonedDateTime fromDateTime, ZonedDa } private Page performSubjectTypeAuditQuery(ZonedDateTime fromDateTime, ZonedDateTime toDateTime, - AuditSubjectType subjectType, String email, Pageable pageable) { + SubjectType subjectType, String email, Pageable pageable) { boolean hasToDate = toDateTime != null; boolean hasEmail = email != null && !email.isBlank(); @@ -151,15 +151,15 @@ private Page performSubjectTypeAuditQuery(ZonedDateTime fromDateTime, Zon } private static SubjectFilter resolveSubjectFilter(String courtId, String serviceCentreId, - AuditSubjectType subjectType) { + SubjectType subjectType) { if (courtId != null && serviceCentreId != null) { throw new InvalidParameterCombinationException("Only one of courtId or serviceCentreId can be provided"); } if (courtId != null) { - return new SubjectFilter(UUID.fromString(courtId), AuditSubjectType.COURT); + return new SubjectFilter(UUID.fromString(courtId), SubjectType.COURT); } if (serviceCentreId != null) { - return new SubjectFilter(UUID.fromString(serviceCentreId), AuditSubjectType.SERVICE_CENTRE); + return new SubjectFilter(UUID.fromString(serviceCentreId), SubjectType.SERVICE_CENTRE); } // if a subject type is provided without a specific subject id, we can still filter by subject type if (subjectType != null) { @@ -168,18 +168,18 @@ private static SubjectFilter resolveSubjectFilter(String courtId, String service return null; } - private record SubjectFilter(UUID subjectId, AuditSubjectType subjectType) {} + private record SubjectFilter(UUID subjectId, SubjectType 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 + * @return a {@link Map} of {@link SubjectType} to a list of {@link NameAndId} pairs * representing the names and ids of the subjects. */ - public Map> getSubjectNameAndIdMap() { + public Map> getSubjectNameAndIdMap() { return Map.of( - AuditSubjectType.COURT, courtService.getAllCourtNameAndIds(), - AuditSubjectType.SERVICE_CENTRE, serviceCentreService.getAllServiceCentreNameAndIds() + SubjectType.COURT, courtService.getAllCourtNameAndIds(), + SubjectType.SERVICE_CENTRE, serviceCentreService.getAllServiceCentreNameAndIds() ); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java index fdeb6591..30e27cf1 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.repositories.LockRepository; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; @@ -49,8 +49,8 @@ public void clearUserLocks(UUID userId) { * @param subjectId the id of the subject * @return the {@link List} of {@link Lock}s for the given subject */ - public List getAllSubjectLocks(AuditSubjectType subjectType, UUID subjectId) { - UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + public List getAllSubjectLocks(SubjectType subjectType, UUID subjectId) { + UUID id = subjectType == SubjectType.SERVICE_CENTRE ? serviceCentreService.getServiceCentreById(subjectId).getId() : courtService.getCourtById(subjectId).getId(); @@ -65,8 +65,8 @@ public List getAllSubjectLocks(AuditSubjectType subjectType, UUID subjectI * @param page The page to check * @return Optional containing the lock if it exists */ - public Optional getPageLock(AuditSubjectType subjectType, UUID subjectId, Page page) { - UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + public Optional getPageLock(SubjectType subjectType, UUID subjectId, Page page) { + UUID id = subjectType == SubjectType.SERVICE_CENTRE ? serviceCentreService.getServiceCentreById(subjectId).getId() : courtService.getCourtById(subjectId).getId(); @@ -82,8 +82,8 @@ public Optional getPageLock(AuditSubjectType subjectType, UUID subjectId, * @param userId The user's unique identifier * @return The updated court lock */ - public Lock createOrUpdateLock(AuditSubjectType subjectType, UUID subjectId, Page page, UUID userId) { - UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page page, UUID userId) { + UUID id = subjectType == SubjectType.SERVICE_CENTRE ? serviceCentreService.getServiceCentreById(subjectId).getId() : courtService.getCourtById(subjectId).getId(); @@ -113,8 +113,8 @@ public Lock createOrUpdateLock(AuditSubjectType subjectType, UUID subjectId, Pag * @param page The page to unlock */ @Transactional - public void deleteLock(AuditSubjectType subjectType, UUID subjectId, Page page) { - UUID id = subjectType == AuditSubjectType.SERVICE_CENTRE + public void deleteLock(SubjectType subjectType, UUID subjectId, Page page) { + UUID id = subjectType == SubjectType.SERVICE_CENTRE ? serviceCentreService.getServiceCentreById(subjectId).getId() : courtService.getCourtById(subjectId).getId(); diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java index 4f28235e..ada39fcd 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ResponseStatusException; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.services.LockService; @@ -60,7 +60,7 @@ void setUp() { @DisplayName("Should allow operation when no lock exists") void shouldAllowWhenNoLockExists() { setupJoinPoint(courtId, page, userId); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); verify(lockService, never()).deleteLock(any(), any(), any()); @@ -71,7 +71,7 @@ void shouldAllowWhenNoLockExists() { void shouldAllowWhenSameUserOwnsLock() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(userId, ZonedDateTime.now().minusMinutes(30)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); verify(lockService, never()).deleteLock(any(), any(), any()); @@ -82,7 +82,7 @@ void shouldAllowWhenSameUserOwnsLock() { void shouldThrowConflictWhenLockIsValid() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(30)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); ResponseStatusException exception = assertThrows( ResponseStatusException.class, @@ -100,10 +100,10 @@ void shouldThrowConflictWhenLockIsValid() { void shouldDeleteStaleLockAndAllow() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(61)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); + verify(lockService).deleteLock(SubjectType.COURT, courtId, page); } @Test @@ -111,10 +111,10 @@ void shouldDeleteStaleLockAndAllow() { void shouldDeleteLockAtExactTimeout() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(60)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); + verify(lockService).deleteLock(SubjectType.COURT, courtId, page); } @Test @@ -122,7 +122,7 @@ void shouldDeleteLockAtExactTimeout() { void shouldNotDeleteLockJustBeforeTimeout() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(59)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); ResponseStatusException exception = assertThrows( ResponseStatusException.class, @@ -138,10 +138,10 @@ void shouldNotDeleteLockJustBeforeTimeout() { void shouldHandleVeryOldStaleLocks() { setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusHours(24)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); + verify(lockService).deleteLock(SubjectType.COURT, courtId, page); } @Test @@ -150,10 +150,10 @@ void shouldRespectCustomTimeout() { ReflectionTestUtils.setField(validator, "lockTimeoutMinutes", 15L); setupJoinPoint(courtId, page, userId); Lock lock = createLock(otherUserId, ZonedDateTime.now().minusMinutes(20)); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.of(lock)); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); - verify(lockService).deleteLock(AuditSubjectType.COURT, courtId, page); + verify(lockService).deleteLock(SubjectType.COURT, courtId, page); } @Test @@ -196,7 +196,7 @@ void shouldThrowWhenUserIdMissing() { @DisplayName("Should handle courtId as String and convert to UUID") void shouldHandleCourtIdAsString() { setupJoinPointWithStringCourtId(courtId.toString(), page, userId); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); } @@ -205,7 +205,7 @@ void shouldHandleCourtIdAsString() { @DisplayName("Should handle page as String and convert to enum") void shouldHandlePageAsString() { setupJoinPointWithStringPage(courtId, "GENERAL", userId); - when(lockService.getPageLock(AuditSubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); + when(lockService.getPageLock(SubjectType.COURT, courtId, page)).thenReturn(Optional.empty()); assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); } @@ -216,7 +216,7 @@ private Lock createLock(UUID userId, ZonedDateTime lockAcquired) { Lock lock = new Lock(); lock.setUserId(userId); lock.setLockAcquired(lockAcquired); - lock.setSubjectType(AuditSubjectType.COURT); + lock.setSubjectType(SubjectType.COURT); lock.setSubjectId(courtId); lock.setPage(page); return lock; @@ -225,11 +225,11 @@ private Lock createLock(UUID userId, ZonedDateTime lockAcquired) { private void setupJoinPoint(UUID courtId, Page page, UUID userId) { try { Method method = TestController.class.getMethod("testMethod", - AuditSubjectType.class, UUID.class, Page.class, UUID.class); + SubjectType.class, UUID.class, Page.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); when(joinPoint.getArgs()).thenReturn(new Object[]{ - AuditSubjectType.COURT, courtId, page, userId}); + SubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -238,10 +238,10 @@ private void setupJoinPoint(UUID courtId, Page page, UUID userId) { private void setupJoinPointWithStringCourtId(String courtId, Page page, UUID userId) { try { Method method = TestController.class.getMethod( - "testMethodWithStringSubjectId", AuditSubjectType.class, String.class, Page.class, UUID.class); + "testMethodWithStringSubjectId", SubjectType.class, String.class, Page.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{SubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -250,10 +250,10 @@ private void setupJoinPointWithStringCourtId(String courtId, Page page, UUID use private void setupJoinPointWithStringPage(UUID courtId, String page, UUID userId) { try { Method method = TestController.class.getMethod( - "testMethodWithStringPage", AuditSubjectType.class, UUID.class, String.class, UUID.class); + "testMethodWithStringPage", SubjectType.class, UUID.class, String.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{SubjectType.COURT, courtId, page, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -273,10 +273,10 @@ private void setupJoinPointWithoutCourtId(Page page, UUID userId) { private void setupJoinPointWithoutPage(UUID courtId, UUID userId) { try { Method method = TestController.class.getMethod("testMethodWithoutPage", - AuditSubjectType.class, UUID.class, UUID.class); + SubjectType.class, UUID.class, UUID.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, userId}); + when(joinPoint.getArgs()).thenReturn(new Object[]{SubjectType.COURT, courtId, userId}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -285,10 +285,10 @@ private void setupJoinPointWithoutPage(UUID courtId, UUID userId) { private void setupJoinPointWithoutUserId(UUID courtId, Page page) { try { Method method = TestController.class.getMethod("testMethodWithoutUserId", - AuditSubjectType.class, UUID.class, Page.class); + SubjectType.class, UUID.class, Page.class); when(joinPoint.getSignature()).thenReturn(methodSignature); when(methodSignature.getMethod()).thenReturn(method); - when(joinPoint.getArgs()).thenReturn(new Object[]{AuditSubjectType.COURT, courtId, page}); + when(joinPoint.getArgs()).thenReturn(new Object[]{SubjectType.COURT, courtId, page}); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } @@ -298,21 +298,21 @@ private void setupJoinPointWithoutUserId(UUID courtId, Page page) { @SuppressWarnings("unused") static class TestController { public void testMethod( - @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { } public void testMethodWithStringSubjectId( - @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") String subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { } public void testMethodWithStringPage( - @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @PathVariable("page") String page, @RequestParam("userId") UUID userId) { @@ -324,13 +324,13 @@ public void testMethodWithoutSubject( } public void testMethodWithoutPage( - @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @RequestParam("userId") UUID userId) { } public void testMethodWithoutUserId( - @PathVariable("subjectType") AuditSubjectType subjectType, + @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page) { } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditableCourtEntityListenerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditableCourtEntityListenerTest.java index ab915c3f..2234a22c 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditableCourtEntityListenerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/audit/AuditableCourtEntityListenerTest.java @@ -10,7 +10,7 @@ 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.SubjectType; import java.util.UUID; @@ -120,7 +120,7 @@ void generateDiffsShouldReturnChangesWhenValuesDiffer() throws Exception { Audit audit = auditCaptor.getValue(); assertEquals(USER_ID, audit.getUserId()); assertEquals(COURT_ID, audit.getSubjectId()); - assertEquals(AuditSubjectType.COURT, audit.getSubjectType()); + assertEquals(SubjectType.COURT, audit.getSubjectType()); assertEquals(1, audit.getActionDataDiff().size()); assertEquals("name", audit.getActionDataDiff().getFirst().field()); } 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 341d2741..2ec3a364 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 @@ -10,7 +10,7 @@ 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.SubjectType; 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; @@ -123,18 +123,18 @@ void getFilteredAndPaginatedAuditsThrowsInvalidParameterCombinationForBothSubjec @Test void getSubjectNameAndIdMapReturns200() { - Map> subjectMap = Map.of( - AuditSubjectType.COURT, List.of( + Map> subjectMap = Map.of( + SubjectType.COURT, List.of( new NameAndId("Exeter Law Courts", UUID.randomUUID()) ), - AuditSubjectType.SERVICE_CENTRE, List.of( + SubjectType.SERVICE_CENTRE, List.of( new NameAndId("Traffic Enforcement Centre", UUID.randomUUID()) ) ); when(auditService.getSubjectNameAndIdMap()).thenReturn(subjectMap); - ResponseEntity>> response = auditController.getSubjectNameAndIdMap(); + ResponseEntity>> response = auditController.getSubjectNameAndIdMap(); assertEquals(HttpStatus.OK, response.getStatusCode(), RESPONSE_STATUS_MISMATCH); assertEquals(subjectMap, response.getBody(), RESPONSE_BODY_MISMATCH); @@ -183,7 +183,7 @@ private Audit createAudit() { return Audit.builder() .id(AUDIT_ID) .subjectId(COURT_ID) - .subjectType(AuditSubjectType.COURT) + .subjectType(SubjectType.COURT) .actionEntity(Court.class.getSimpleName()) .actionDataDiff(List.of( new Change("isOpen", Boolean.FALSE, Boolean.TRUE), diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java index 3e780b23..bbed02f7 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java @@ -8,7 +8,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; import uk.gov.hmcts.reform.fact.data.api.services.LockService; @@ -40,10 +40,10 @@ class LockControllerTest { @Test void getCourtLocksReturns200() { List locks = List.of(new Lock()); - when(lockService.getAllSubjectLocks(AuditSubjectType.COURT, COURT_ID)).thenReturn(locks); + when(lockService.getAllSubjectLocks(SubjectType.COURT, COURT_ID)).thenReturn(locks); ResponseEntity> response = - lockController.getSubjectLocks(AuditSubjectType.COURT, COURT_ID.toString()); + lockController.getSubjectLocks(SubjectType.COURT, COURT_ID.toString()); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).as(RESPONSE_BODY_MESSAGE).isEqualTo(locks); @@ -53,17 +53,17 @@ void getCourtLocksReturns200() { void getCourtLocksThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - lockController.getSubjectLocks(AuditSubjectType.COURT, INVALID_UUID) + lockController.getSubjectLocks(SubjectType.COURT, INVALID_UUID) ); } @Test void getCourtLockStatusReturns200() { Lock lock = new Lock(); - when(lockService.getPageLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE)).thenReturn(Optional.of(lock)); + when(lockService.getPageLock(SubjectType.COURT, COURT_ID, TEST_PAGE)).thenReturn(Optional.of(lock)); ResponseEntity response = lockController.getSubjectLockStatus( - AuditSubjectType.COURT, + SubjectType.COURT, COURT_ID.toString(), TEST_PAGE ); @@ -76,17 +76,17 @@ void getCourtLockStatusReturns200() { void getCourtLockStatusThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - lockController.getSubjectLockStatus(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE) + lockController.getSubjectLockStatus(SubjectType.COURT, INVALID_UUID, TEST_PAGE) ); } @Test void createCourtLockReturns201() { Lock lock = new Lock(); - when(lockService.createOrUpdateLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); + when(lockService.createOrUpdateLock(SubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); ResponseEntity response = lockController.createOrUpdateSubjectLock( - AuditSubjectType.COURT, + SubjectType.COURT, COURT_ID.toString(), TEST_PAGE, USER_ID @@ -100,17 +100,17 @@ void createCourtLockReturns201() { void createCourtLockThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - lockController.createOrUpdateSubjectLock(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE, USER_ID) + lockController.createOrUpdateSubjectLock(SubjectType.COURT, INVALID_UUID, TEST_PAGE, USER_ID) ); } @Test void updateCourtLockReturns201() { Lock lock = new Lock(); - when(lockService.createOrUpdateLock(AuditSubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); + when(lockService.createOrUpdateLock(SubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); ResponseEntity response = lockController.createOrUpdateSubjectLock( - AuditSubjectType.COURT, + SubjectType.COURT, COURT_ID.toString(), TEST_PAGE, USER_ID @@ -123,7 +123,7 @@ void updateCourtLockReturns201() { @Test void deleteCourtLockReturns204() { ResponseEntity response = - lockController.deleteSubjectLock(AuditSubjectType.COURT, COURT_ID.toString(), TEST_PAGE); + lockController.deleteSubjectLock(SubjectType.COURT, COURT_ID.toString(), TEST_PAGE); assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.NO_CONTENT); } @@ -132,7 +132,7 @@ void deleteCourtLockReturns204() { void deleteCourtLockThrowsIllegalArgumentExceptionForInvalidUUID() { assertThrows( IllegalArgumentException.class, () -> - lockController.deleteSubjectLock(AuditSubjectType.COURT, INVALID_UUID, TEST_PAGE) + lockController.deleteSubjectLock(SubjectType.COURT, INVALID_UUID, TEST_PAGE) ); } } 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 688908ca..ea522ee6 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 @@ -12,7 +12,7 @@ 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.Court; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; 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; @@ -134,7 +134,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfter() { when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfter( eq(COURT_ID), - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ) @@ -154,7 +154,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfter() { assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtAfter( - eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + eq(COURT_ID), eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ); } @@ -163,7 +163,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetween() { when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtBetween( eq(COURT_ID), - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) @@ -184,7 +184,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetween() { assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtBetween( - eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) + eq(COURT_ID), eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) ); } @@ -252,7 +252,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfterAndEma when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( eq(COURT_ID), - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), eq(EMAIL), any(Pageable.class) @@ -273,7 +273,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtAfterAndEma assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtAfterAndEmailAddressLike( - eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), eq(EMAIL), any(Pageable.class) + eq(COURT_ID), eq(SubjectType.COURT), eq(fromDateTime), eq(EMAIL), any(Pageable.class) ); } @@ -282,7 +282,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetweenAndE when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( eq(COURT_ID), - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), eq(EMAIL), @@ -304,7 +304,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByCourtIdAndCreatedAtBetweenAndE assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtBetweenAndEmailAddressLike( - eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), eq(toDateTime), eq(EMAIL), any(Pageable.class) + eq(COURT_ID), eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), eq(EMAIL), any(Pageable.class) ); } @@ -328,7 +328,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByServiceCentreIdAndCreatedAtAft when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfter( eq(SERVICE_CENTRE_ID), - eq(AuditSubjectType.SERVICE_CENTRE), + eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), any(Pageable.class) ) @@ -348,7 +348,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindByServiceCentreIdAndCreatedAtAft assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtAfter( - eq(SERVICE_CENTRE_ID), eq(AuditSubjectType.SERVICE_CENTRE), eq(fromDateTime), any(Pageable.class) + eq(SERVICE_CENTRE_ID), eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), any(Pageable.class) ); } @@ -429,11 +429,11 @@ void getSubjectNameAndIdMapShouldAllSubjectData() { when(courtService.getAllCourtNameAndIds()).thenReturn(courts); when(serviceCentreService.getAllServiceCentreNameAndIds()).thenReturn(serviceCentres); - Map> result = auditService.getSubjectNameAndIdMap(); + 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); + assertThat(result).hasSize(2).containsKeys(SubjectType.COURT, SubjectType.SERVICE_CENTRE); + assertThat(result.get(SubjectType.COURT)).containsExactlyElementsOf(courts); + assertThat(result.get(SubjectType.SERVICE_CENTRE)).containsExactlyElementsOf(serviceCentres); verify(courtService).getAllCourtNameAndIds(); verify(serviceCentreService).getAllServiceCentreNameAndIds(); } @@ -443,11 +443,11 @@ void getSubjectNameAndIdMapShouldReturnEmptyListsWhenNoSubjectsExist() { when(courtService.getAllCourtNameAndIds()).thenReturn(List.of()); when(serviceCentreService.getAllServiceCentreNameAndIds()).thenReturn(List.of()); - Map> result = auditService.getSubjectNameAndIdMap(); + 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(); + assertThat(result).containsKeys(SubjectType.COURT, SubjectType.SERVICE_CENTRE); + assertThat(result.get(SubjectType.COURT)).isEmpty(); + assertThat(result.get(SubjectType.SERVICE_CENTRE)).isEmpty(); verify(courtService).getAllCourtNameAndIds(); verify(serviceCentreService).getAllServiceCentreNameAndIds(); } @@ -480,7 +480,7 @@ void getAuditByIdShouldThrowNotFoundExceptionWhenRecordDoesNotExist() { @Test void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtAfterWhenOnlySubjectTypeProvided() { when(auditRepository.findBySubjectTypeAndCreatedAtAfter( - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ) @@ -492,7 +492,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtAfterWh PAGE_SIZE, fromDate, null, - AuditSubjectType.COURT, + SubjectType.COURT, null, null, null @@ -500,14 +500,14 @@ void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtAfterWh assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectTypeAndCreatedAtAfter( - eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ); } @Test void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtBetweenWhenSubjectTypeAndToDateProvided() { when(auditRepository.findBySubjectTypeAndCreatedAtBetween( - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) @@ -520,7 +520,7 @@ void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtBetween PAGE_SIZE, fromDate, toDate, - AuditSubjectType.COURT, + SubjectType.COURT, null, null, null @@ -528,14 +528,14 @@ void getFilteredAndPaginatedAuditsShouldCallFindBySubjectTypeAndCreatedAtBetween assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectTypeAndCreatedAtBetween( - eq(AuditSubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) + eq(SubjectType.COURT), eq(fromDateTime), eq(toDateTime), any(Pageable.class) ); } @Test void shouldCallFindBySubjectTypeAndCreatedAtAfterAndEmailAddressLikeWhenSubjectTypeAndEmailProvided() { when(auditRepository.findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( - eq(AuditSubjectType.SERVICE_CENTRE), + eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(EMAIL), any(Pageable.class) @@ -548,7 +548,7 @@ void shouldCallFindBySubjectTypeAndCreatedAtAfterAndEmailAddressLikeWhenSubjectT PAGE_SIZE, fromDate, null, - AuditSubjectType.SERVICE_CENTRE, + SubjectType.SERVICE_CENTRE, null, null, EMAIL @@ -556,14 +556,14 @@ void shouldCallFindBySubjectTypeAndCreatedAtAfterAndEmailAddressLikeWhenSubjectT assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectTypeAndCreatedAtAfterAndEmailAddressLike( - eq(AuditSubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(EMAIL), any(Pageable.class) + eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(EMAIL), any(Pageable.class) ); } @Test void shouldCallFindBySubjectTypeAndCreatedAtBetweenAndEmailAddressLikeWhenAllFiltersProvided() { when(auditRepository.findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( - eq(AuditSubjectType.SERVICE_CENTRE), + eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(toDateTime), eq(EMAIL), @@ -577,7 +577,7 @@ void shouldCallFindBySubjectTypeAndCreatedAtBetweenAndEmailAddressLikeWhenAllFil PAGE_SIZE, fromDate, toDate, - AuditSubjectType.SERVICE_CENTRE, + SubjectType.SERVICE_CENTRE, null, null, EMAIL @@ -585,14 +585,14 @@ void shouldCallFindBySubjectTypeAndCreatedAtBetweenAndEmailAddressLikeWhenAllFil assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectTypeAndCreatedAtBetweenAndEmailAddressLike( - eq(AuditSubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(toDateTime), eq(EMAIL), any(Pageable.class) + eq(SubjectType.SERVICE_CENTRE), eq(fromDateTime), eq(toDateTime), eq(EMAIL), any(Pageable.class) ); } @Test void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimpleSubjectTypeQuery() { when(auditRepository.findBySubjectTypeAndCreatedAtAfter( - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ) @@ -604,7 +604,7 @@ void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimp PAGE_SIZE, fromDate, null, - AuditSubjectType.COURT, + SubjectType.COURT, null, null, "" @@ -616,7 +616,7 @@ void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimp PAGE_SIZE, fromDate, null, - AuditSubjectType.COURT, + SubjectType.COURT, null, null, " " @@ -625,7 +625,7 @@ void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimp assertThat(firstResult.getContent()).hasSize(1); assertThat(secondResult.getContent()).hasSize(1); verify(auditRepository, times(2)).findBySubjectTypeAndCreatedAtAfter( - eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ); } @@ -633,7 +633,7 @@ void getFilteredAndPaginatedAuditsShouldIgnoreBlankEmailForSubjectTypeAndUseSimp void getFilteredAndPaginatedAuditsShouldPrioritiseCourtIdOverSubjectTypeWhenBothProvided() { when(auditRepository.findBySubjectIdAndSubjectTypeAndCreatedAtAfter( eq(COURT_ID), - eq(AuditSubjectType.COURT), + eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ) @@ -645,7 +645,7 @@ void getFilteredAndPaginatedAuditsShouldPrioritiseCourtIdOverSubjectTypeWhenBoth PAGE_SIZE, fromDate, null, - AuditSubjectType.SERVICE_CENTRE, + SubjectType.SERVICE_CENTRE, COURT_ID.toString(), null, null @@ -653,7 +653,7 @@ void getFilteredAndPaginatedAuditsShouldPrioritiseCourtIdOverSubjectTypeWhenBoth assertThat(result.getContent()).hasSize(1); verify(auditRepository).findBySubjectIdAndSubjectTypeAndCreatedAtAfter( - eq(COURT_ID), eq(AuditSubjectType.COURT), eq(fromDateTime), any(Pageable.class) + eq(COURT_ID), eq(SubjectType.COURT), eq(fromDateTime), any(Pageable.class) ); } @@ -661,7 +661,7 @@ private Audit createAudit() { return Audit.builder() .id(AUDIT_ID) .subjectId(COURT_ID) - .subjectType(AuditSubjectType.COURT) + .subjectType(SubjectType.COURT) .actionEntity(Court.class.getSimpleName()) .actionDataDiff(List.of( new Change("isOpen", Boolean.FALSE, Boolean.TRUE), diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java index 218d7ac4..2b45b3c0 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java @@ -7,7 +7,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.reform.fact.data.api.entities.User; -import uk.gov.hmcts.reform.fact.data.api.entities.types.AuditSubjectType; +import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; import uk.gov.hmcts.reform.fact.data.api.entities.Court; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; @@ -61,7 +61,7 @@ void setup() { user.setEmail("email@justice.gov.uk"); lock = new Lock(); - lock.setSubjectType(AuditSubjectType.COURT); + lock.setSubjectType(SubjectType.COURT); lock.setSubjectId(courtId); lock.setPage(Page.GENERAL); lock.setLockAcquired(ZonedDateTime.now()); @@ -95,24 +95,24 @@ void deleteLocksByUserIdShouldHandleNoLocksForUser() { @Test void getLocksByCourtIdShouldGetLocksByCourtId() { when(courtService.getCourtById(courtId)).thenReturn(court); - when(lockRepository.findAllBySubjectTypeAndSubjectId(AuditSubjectType.COURT, courtId)) + when(lockRepository.findAllBySubjectTypeAndSubjectId(SubjectType.COURT, courtId)) .thenReturn(List.of(lock)); - List result = lockService.getAllSubjectLocks(AuditSubjectType.COURT, courtId); + List result = lockService.getAllSubjectLocks(SubjectType.COURT, courtId); assertEquals(1, result.size()); verify(courtService).getCourtById(courtId); - verify(lockRepository).findAllBySubjectTypeAndSubjectId(AuditSubjectType.COURT, courtId); + verify(lockRepository).findAllBySubjectTypeAndSubjectId(SubjectType.COURT, courtId); } @Test void getPageLockShouldGetPageLock() { Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); - when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page)) + when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page)) .thenReturn(Optional.of(lock)); - Optional result = lockService.getPageLock(AuditSubjectType.COURT, courtId, page); + Optional result = lockService.getPageLock(SubjectType.COURT, courtId, page); assertTrue(result.isPresent()); verify(courtService).getCourtById(courtId); - verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); } @Test @@ -120,7 +120,7 @@ void createLockShouldCreateLock() { when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); when(lockRepository.save(any(Lock.class))).thenReturn(lock); - Lock result = lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, Page.GENERAL, userId); + Lock result = lockService.createOrUpdateLock(SubjectType.COURT, courtId, Page.GENERAL, userId); assertNotNull(result); verify(lockRepository).save(any(Lock.class)); } @@ -130,13 +130,13 @@ void createOrUpdateLockShouldUpdateExistingLock() { Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); - when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page)) + when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page)) .thenReturn(Optional.of(lock)); when(lockRepository.save(any(Lock.class))).thenReturn(lock); - Lock result = lockService.createOrUpdateLock(AuditSubjectType.COURT, courtId, page, userId); + Lock result = lockService.createOrUpdateLock(SubjectType.COURT, courtId, page, userId); assertNotNull(result); verify(courtService).getCourtById(courtId); - verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); verify(lockRepository).save(any(Lock.class)); } @@ -144,9 +144,9 @@ void createOrUpdateLockShouldUpdateExistingLock() { void deleteLockShouldDeleteLock() { Page page = Page.GENERAL; when(courtService.getCourtById(courtId)).thenReturn(court); - lockService.deleteLock(AuditSubjectType.COURT, courtId, page); + lockService.deleteLock(SubjectType.COURT, courtId, page); verify(courtService).getCourtById(courtId); - verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(AuditSubjectType.COURT, courtId, page); + verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); } } From d900b500080b5767bf1e34f319295be3ff19e482 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Tue, 7 Jul 2026 10:08:10 +0100 Subject: [PATCH 08/11] Refactor lock handling and clean up code --- .../controllers/LockControllerFunctionalTest.java | 6 +++--- .../controllers/UserControllerFunctionalTest.java | 9 ++++++++- .../reform/fact/data/api/controllers/LockController.java | 5 ++++- .../fact/data/api/repositories/LockRepository.java | 4 ++-- .../hmcts/reform/fact/data/api/services/LockService.java | 3 ++- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java index a146f7ee..19b062eb 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/LockControllerFunctionalTest.java @@ -150,8 +150,8 @@ void shouldGetAllLocksForCourtSuccessfully() throws Exception { final List locks = getAllLocksResponse.jsonPath().getList("", Lock.class); assertThat(locks) - .as("Expected 2 locks for court %s", courtId) - .hasSize(2); + .as("Expected 1 locks for court %s", courtId) + .hasSize(1); assertThat(locks) .as("All locks should belong to court %s", courtId) @@ -162,7 +162,7 @@ void shouldGetAllLocksForCourtSuccessfully() throws Exception { assertThat(locks) .as("Expected locks for pages COURT and COURT_ACCESSIBILITY") .extracting(Lock::getPage) - .containsExactlyInAnyOrder(Page.GENERAL, Page.ACCESSIBILITY); + .containsExactlyInAnyOrder(Page.ACCESSIBILITY); } @Test diff --git a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java index abd11375..3f990d55 100644 --- a/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java +++ b/src/functionalTest/java/uk/gov/hmcts/reform/fact/functional/controllers/UserControllerFunctionalTest.java @@ -267,13 +267,14 @@ void shouldReturnNotFoundWhenRemovingFavouriteForNonExistentUserSuccessfully() { @DisplayName("DELETE /user/v1/{userId}/locks clears user locks successfully") void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { final UUID userId = TestDataHelper.createUser(http, "test.user.clear.locks"); + final UUID userId2 = TestDataHelper.createUser(http, "test.user2.clear.locks"); final UUID court1Id = TestDataHelper .createCourt(http, generateUniqueCourtName("Test Court Clear Locks One")); final UUID court2Id = TestDataHelper .createCourt(http, generateUniqueCourtName("Test Court Clear Locks Two")); TestDataHelper.createCourtLock(http, court1Id, Page.GENERAL, userId); - TestDataHelper.createCourtLock(http, court2Id, Page.ACCESSIBILITY, userId); + TestDataHelper.createCourtLock(http, court2Id, Page.ACCESSIBILITY, userId2); final Response getLocksBeforeClearResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + court1Id + "/v1"); @@ -306,6 +307,12 @@ void shouldReturnNoContentWhenClearingUserLocksSuccessfully() throws Exception { .as("Expected empty response after clearing user locks") .isEqualTo(""); + final Response clearLocksResponse2 = http.doDelete("/user/v1/" + userId2 + "/locks"); + + assertThat(clearLocksResponse.statusCode()) + .as("Expected 204 NO CONTENT when clearing locks for user %s", userId2) + .isEqualTo(NO_CONTENT.value()); + final Response getLock2StatusResponse = http.doGet("/locks/" + SubjectType.COURT + "/" + court2Id + "/v1/" + Page.ACCESSIBILITY); diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java index 5467eb11..37c07e74 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockController.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.reform.fact.data.api.controllers; +import uk.gov.hmcts.reform.fact.data.api.aspect.annotations.LockCleanupCheck; import uk.gov.hmcts.reform.fact.data.api.entities.Lock; import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; import uk.gov.hmcts.reform.fact.data.api.entities.types.Page; @@ -40,6 +41,7 @@ public LockController(LockService lockService) { this.lockService = lockService; } + @LockCleanupCheck @GetMapping("/{subjectType}/{subjectId}/v1") @Operation(summary = "Get all active locks for a subject") @ApiResponses(value = { @@ -53,6 +55,7 @@ public ResponseEntity> getSubjectLocks( return ResponseEntity.ok(lockService.getAllSubjectLocks(subjectType, UUID.fromString(subjectId))); } + @LockCleanupCheck @GetMapping("/{subjectType}/{subjectId}/v1/{page}") @Operation(summary = "Get lock status for a specific page of a subject") @ApiResponses(value = { @@ -70,8 +73,8 @@ public ResponseEntity getSubjectLockStatus( .orElse(ResponseEntity.noContent().build()); } - @PostMapping("/{subjectType}/{subjectId}/v1/{page}") @LockTimeoutCheck + @PostMapping("/{subjectType}/{subjectId}/v1/{page}") @Operation(summary = "Create or update subject lock for a specific page") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Successfully created or updated subject lock"), diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java index 6215b5ce..11489f78 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java @@ -26,7 +26,7 @@ public interface LockRepository extends JpaRepository { void deleteBySubjectTypeAndSubjectIdAndPage(SubjectType subjectType, UUID subjectId, Page page); - void deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(UUID userId, SubjectType subjectType, Page page); - void deleteByLockAcquiredBefore(ZonedDateTime lockAcquired); + + void deleteAllByUserIdAndIdIsNot(UUID userId, UUID id); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java index 30e27cf1..05eeaf80 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java @@ -82,6 +82,7 @@ public Optional getPageLock(SubjectType subjectType, UUID subjectId, Page * @param userId The user's unique identifier * @return The updated court lock */ + @Transactional public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page page, UUID userId) { UUID id = subjectType == SubjectType.SERVICE_CENTRE ? serviceCentreService.getServiceCentreById(subjectId).getId() @@ -101,7 +102,7 @@ public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page pag Lock savedLock = lockRepository.save(lock); // remove any existing locks for other pages owned by this user - lockRepository.deleteAllByUserIdAndSubjectTypeIsNotAndPageIsNot(user.getId(), subjectType, page); + lockRepository.deleteAllByUserIdAndIdIsNot(user.getId(), savedLock.getId()); return savedLock; } From b8f08323803dcfc92929eb2b13fc7897f1d0d7e9 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Wed, 8 Jul 2026 11:48:07 +0100 Subject: [PATCH 09/11] Coverage --- .../fact/data/api/services/LockService.java | 2 +- .../fact/data/api/aspect/LockAspectTest.java | 20 +++++ .../data/api/services/LockServiceTest.java | 81 ++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java index 05eeaf80..2af3a323 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java @@ -102,7 +102,7 @@ public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page pag Lock savedLock = lockRepository.save(lock); // remove any existing locks for other pages owned by this user - lockRepository.deleteAllByUserIdAndIdIsNot(user.getId(), savedLock.getId()); + lockRepository.deleteAllByUserIdAndIdIsNot(userId, savedLock.getId()); return savedLock; } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java index ada39fcd..0039b98e 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java @@ -27,7 +27,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -210,6 +212,24 @@ void shouldHandlePageAsString() { assertDoesNotThrow(() -> validator.validateLockTimeout(joinPoint)); } + @Test + @DisplayName("Should call deleteExpiredLocks when LockCleanupCheck advice runs") + void shouldCleanupExpiredLocks() { + assertDoesNotThrow(() -> validator.lockCleanup(joinPoint)); + + verify(lockService, times(1)).deleteExpiredLocks(); + } + + @Test + @DisplayName("Should swallow exception when deleteExpiredLocks fails") + void shouldSwallowCleanupException() { + doThrow(new RuntimeException("cleanup failed")).when(lockService).deleteExpiredLocks(); + + assertDoesNotThrow(() -> validator.lockCleanup(joinPoint)); + + verify(lockService, times(1)).deleteExpiredLocks(); + } + // Helper methods private Lock createLock(UUID userId, ZonedDateTime lockAcquired) { diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java index 2b45b3c0..5732e79f 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java @@ -6,6 +6,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +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.SubjectType; import uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException; @@ -24,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,6 +40,9 @@ class LockServiceTest { @Mock private CourtService courtService; + @Mock + private ServiceCentreService serviceCentreService; + @Mock private UserService userService; @@ -47,6 +54,8 @@ class LockServiceTest { private UUID courtId; private Court court; private Lock lock; + private UUID serviceCentreId; + private ServiceCentre serviceCentre; @BeforeEach void setup() { @@ -61,10 +70,19 @@ void setup() { user.setEmail("email@justice.gov.uk"); lock = new Lock(); + lock.setId(UUID.randomUUID()); lock.setSubjectType(SubjectType.COURT); lock.setSubjectId(courtId); lock.setPage(Page.GENERAL); lock.setLockAcquired(ZonedDateTime.now()); + + serviceCentreId = UUID.randomUUID(); + serviceCentre = new ServiceCentre(); + serviceCentre.setId(serviceCentreId); + serviceCentre.setName("Test Service Centre"); + + // needed for deleteExpiredLocks test + ReflectionTestUtils.setField(lockService, "lockTimeoutMinutes", 30L); } @Test @@ -148,5 +166,66 @@ void deleteLockShouldDeleteLock() { verify(courtService).getCourtById(courtId); verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); } -} + @Test + void getLocksByServiceCentreIdShouldGetLocksByServiceCentreId() { + when(serviceCentreService.getServiceCentreById(serviceCentreId)).thenReturn(serviceCentre); + when(lockRepository.findAllBySubjectTypeAndSubjectId(SubjectType.SERVICE_CENTRE, serviceCentreId)) + .thenReturn(List.of(lock)); + + List result = lockService.getAllSubjectLocks(SubjectType.SERVICE_CENTRE, serviceCentreId); + + assertEquals(1, result.size()); + verify(serviceCentreService).getServiceCentreById(serviceCentreId); + verify(lockRepository).findAllBySubjectTypeAndSubjectId(SubjectType.SERVICE_CENTRE, serviceCentreId); + } + + @Test + void getPageLockShouldGetPageLockForServiceCentre() { + Page page = Page.GENERAL; + when(serviceCentreService.getServiceCentreById(serviceCentreId)).thenReturn(serviceCentre); + when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(SubjectType.SERVICE_CENTRE, serviceCentreId, page)) + .thenReturn(Optional.of(lock)); + + Optional result = lockService.getPageLock(SubjectType.SERVICE_CENTRE, serviceCentreId, page); + + assertTrue(result.isPresent()); + verify(serviceCentreService).getServiceCentreById(serviceCentreId); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(SubjectType.SERVICE_CENTRE, serviceCentreId, page); + } + + @Test + void createLockShouldCreateLockForServiceCentre() { + when(serviceCentreService.getServiceCentreById(serviceCentreId)).thenReturn(serviceCentre); + when(userService.getUserById(userId)).thenReturn(user); + when(lockRepository.save(any(Lock.class))).thenReturn(lock); + + Lock result = lockService.createOrUpdateLock(SubjectType.SERVICE_CENTRE, serviceCentreId, Page.GENERAL, userId); + + assertNotNull(result); + verify(serviceCentreService).getServiceCentreById(serviceCentreId); + verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage( + SubjectType.SERVICE_CENTRE, serviceCentreId, Page.GENERAL); + verify(lockRepository).save(any(Lock.class)); + verify(lockRepository).deleteAllByUserIdAndIdIsNot(eq(userId), any(UUID.class)); + } + + @Test + void deleteLockShouldDeleteLockForServiceCentre() { + Page page = Page.GENERAL; + when(serviceCentreService.getServiceCentreById(serviceCentreId)).thenReturn(serviceCentre); + + lockService.deleteLock(SubjectType.SERVICE_CENTRE, serviceCentreId, page); + + verify(serviceCentreService).getServiceCentreById(serviceCentreId); + verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage( + SubjectType.SERVICE_CENTRE, serviceCentreId, page); + } + + @Test + void deleteExpiredLocksShouldDeleteLocksBeforeConfiguredTimeout() { + lockService.deleteExpiredLocks(); + + verify(lockRepository).deleteByLockAcquiredBefore(any(ZonedDateTime.class)); + } +} From 922d7a1a7f3f0f9a809d361965db0b1a04799915 Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Wed, 8 Jul 2026 17:29:23 +0100 Subject: [PATCH 10/11] Rework lock service/repo to avoid concurrentcy issues + PR RC updates --- .../fact/data/api/aspect/LockAspect.java | 6 +- .../reform/fact/data/api/entities/Lock.java | 4 - .../data/api/repositories/LockRepository.java | 87 ++++++++++++++++++- .../fact/data/api/services/LockService.java | 71 ++++++++------- .../fact/data/api/aspect/LockAspectTest.java | 7 +- .../api/controllers/LockControllerTest.java | 18 +--- .../data/api/services/LockServiceTest.java | 48 +++++++--- 7 files changed, 171 insertions(+), 70 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java index e5a9a3b4..6cbdf123 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspect.java @@ -19,6 +19,7 @@ import java.lang.reflect.Parameter; import java.time.Duration; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Optional; import java.util.UUID; @@ -58,7 +59,10 @@ public void validateLockTimeout(JoinPoint joinPoint) { return; // No lock or same user owns it } - long minutesLocked = Duration.between(lock.get().getLockAcquired(), ZonedDateTime.now()).toMinutes(); + long minutesLocked = Duration.between( + lock.get().getLockAcquired(), + ZonedDateTime.now(ZoneOffset.UTC) + ).toMinutes(); if (minutesLocked < lockTimeoutMinutes) { throw new ResponseStatusException(HttpStatus.CONFLICT, diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java index a1cae865..10a2bec8 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/entities/Lock.java @@ -65,10 +65,6 @@ public class Lock { @Enumerated(EnumType.STRING) private Page page; - // TODO - consider using @UpdateTimestamp here to automate lock timestamping? - // as the intention is that a lock can be re-acquired to prevent - // being timed out, it may be prudent to automate the value in this field - // using an @UpdateTimestamp as well as removing the Setter. @Schema(description = "Acquire timestamp for the lock") @NotNull @TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC) diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java index 11489f78..61198975 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/repositories/LockRepository.java @@ -9,6 +9,9 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import uk.gov.hmcts.reform.fact.data.api.entities.types.SubjectType; @@ -16,7 +19,6 @@ @Repository public interface LockRepository extends JpaRepository { - void deleteAllByUserId(UUID userId); @EntityGraph(attributePaths = {"user"}) List findAllBySubjectTypeAndSubjectId(SubjectType subjectType, UUID subjectId); @@ -24,9 +26,86 @@ public interface LockRepository extends JpaRepository { @EntityGraph(attributePaths = {"user"}) Optional findBySubjectTypeAndSubjectIdAndPage(SubjectType subjectType, UUID subjectId, Page page); - void deleteBySubjectTypeAndSubjectIdAndPage(SubjectType subjectType, UUID subjectId, Page page); + @Modifying + @Query(value = """ + DELETE FROM + "lock" + WHERE + subject_type = :subjectType + AND + subject_id = :subjectId + AND + page = :page + """, nativeQuery = true) + void deleteBySubjectTypeAndSubjectIdAndPage( + @Param("subjectType") String subjectType, + @Param("subjectId") UUID subjectId, + @Param("page") String page + ); - void deleteByLockAcquiredBefore(ZonedDateTime lockAcquired); + @Modifying + @Query(value = """ + DELETE FROM + "lock" + WHERE + lock_acquired < :lockAcquired + """, nativeQuery = true) + void deleteByLockAcquiredBefore( + @Param("lockAcquired") ZonedDateTime lockAcquired + ); - void deleteAllByUserIdAndIdIsNot(UUID userId, UUID id); + @Modifying + @Query(value = """ + DELETE FROM + "lock" + WHERE + user_id = :userId + """, nativeQuery = true) + void deleteAllByUserId( + @Param("userId") UUID userId + ); + + @Query(value = """ + WITH updated AS ( + UPDATE "lock" + SET user_id = :userId, + lock_acquired = :lockAcquired + WHERE subject_type = :subjectType + AND subject_id = :subjectId + AND page = :page + RETURNING id + ), + inserted AS ( + INSERT INTO "lock" (id, subject_id, subject_type, user_id, page, lock_acquired) + SELECT :newLockId, :subjectId, :subjectType, :userId, :page, :lockAcquired + WHERE NOT EXISTS (SELECT 1 FROM updated) + RETURNING id + ), + kept AS ( + SELECT id FROM updated + UNION ALL + SELECT id FROM inserted + ), + remove_duplicate_page_locks AS ( + DELETE FROM "lock" + WHERE subject_type = :subjectType + AND subject_id = :subjectId + AND page = :page + AND id NOT IN (SELECT id FROM kept) + ), + remove_other_user_locks AS ( + DELETE FROM "lock" + WHERE user_id = :userId + AND id NOT IN (SELECT id FROM kept) + ) + SELECT id FROM kept LIMIT 1 + """, nativeQuery = true) + UUID upsertLockAndDeleteOtherUserLocks( + @Param("newLockId") UUID newLockId, + @Param("subjectType") String subjectType, + @Param("subjectId") UUID subjectId, + @Param("page") String page, + @Param("userId") UUID userId, + @Param("lockAcquired") ZonedDateTime lockAcquired + ); } diff --git a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java index 2af3a323..d2a2ef22 100644 --- a/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java +++ b/src/main/java/uk/gov/hmcts/reform/fact/data/api/services/LockService.java @@ -11,6 +11,7 @@ import uk.gov.hmcts.reform.fact.data.api.entities.Lock; import uk.gov.hmcts.reform.fact.data.api.entities.User; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -50,9 +51,7 @@ public void clearUserLocks(UUID userId) { * @return the {@link List} of {@link Lock}s for the given subject */ public List getAllSubjectLocks(SubjectType subjectType, UUID subjectId) { - UUID id = subjectType == SubjectType.SERVICE_CENTRE - ? serviceCentreService.getServiceCentreById(subjectId).getId() - : courtService.getCourtById(subjectId).getId(); + UUID id = verifySubject(subjectType, subjectId); return lockRepository.findAllBySubjectTypeAndSubjectId(subjectType,id); } @@ -66,9 +65,7 @@ public List getAllSubjectLocks(SubjectType subjectType, UUID subjectId) { * @return Optional containing the lock if it exists */ public Optional getPageLock(SubjectType subjectType, UUID subjectId, Page page) { - UUID id = subjectType == SubjectType.SERVICE_CENTRE - ? serviceCentreService.getServiceCentreById(subjectId).getId() - : courtService.getCourtById(subjectId).getId(); + UUID id = verifySubject(subjectType, subjectId); return lockRepository.findBySubjectTypeAndSubjectIdAndPage(subjectType, id, page); } @@ -84,26 +81,29 @@ public Optional getPageLock(SubjectType subjectType, UUID subjectId, Page */ @Transactional public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page page, UUID userId) { - UUID id = subjectType == SubjectType.SERVICE_CENTRE - ? serviceCentreService.getServiceCentreById(subjectId).getId() - : courtService.getCourtById(subjectId).getId(); + UUID id = verifySubject(subjectType, subjectId); User user = userService.getUserById(userId); - - Lock lock = lockRepository.findBySubjectTypeAndSubjectIdAndPage(subjectType, id, page) - .orElse(new Lock()); - - lock.setSubjectId(id); - lock.setSubjectType(subjectType); - lock.setUserId(user.getId()); - lock.setUser(user); - lock.setPage(page); - lock.setLockAcquired(ZonedDateTime.now()); - - Lock savedLock = lockRepository.save(lock); - // remove any existing locks for other pages owned by this user - lockRepository.deleteAllByUserIdAndIdIsNot(userId, savedLock.getId()); - return savedLock; + ZonedDateTime lockAcquired = ZonedDateTime.now(ZoneOffset.UTC); + + UUID lockId = lockRepository.upsertLockAndDeleteOtherUserLocks( + UUID.randomUUID(), + subjectType.name(), + id, + page.name(), + user.getId(), + lockAcquired + ); + + return Lock.builder() + .id(lockId) + .subjectId(id) + .subjectType(subjectType) + .userId(user.getId()) + .user(user) + .page(page) + .lockAcquired(lockAcquired) + .build(); } /** @@ -115,11 +115,9 @@ public Lock createOrUpdateLock(SubjectType subjectType, UUID subjectId, Page pag */ @Transactional public void deleteLock(SubjectType subjectType, UUID subjectId, Page page) { - UUID id = subjectType == SubjectType.SERVICE_CENTRE - ? serviceCentreService.getServiceCentreById(subjectId).getId() - : courtService.getCourtById(subjectId).getId(); + UUID id = verifySubject(subjectType, subjectId); - lockRepository.deleteBySubjectTypeAndSubjectIdAndPage(subjectType, id, page); + lockRepository.deleteBySubjectTypeAndSubjectIdAndPage(subjectType.name(), id, page.name()); } /** @@ -127,6 +125,21 @@ public void deleteLock(SubjectType subjectType, UUID subjectId, Page page) { */ @Transactional public void deleteExpiredLocks() { - lockRepository.deleteByLockAcquiredBefore(ZonedDateTime.now().minusMinutes(lockTimeoutMinutes)); + lockRepository.deleteByLockAcquiredBefore(ZonedDateTime.now(ZoneOffset.UTC).minusMinutes(lockTimeoutMinutes)); + } + + /** + * Verifies the subject type and id, returning the corresponding subject id. + * + * @param subjectType the {@link SubjectType} + * @param subjectId the id + * @return the subject id as a UUID + * + * @throws uk.gov.hmcts.reform.fact.data.api.errorhandling.exceptions.NotFoundException if the entity does not exist + */ + private UUID verifySubject(final SubjectType subjectType, final UUID subjectId) { + return subjectType == SubjectType.SERVICE_CENTRE + ? serviceCentreService.getServiceCentreById(subjectId).getId() + : courtService.getCourtById(subjectId).getId(); } } diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java index 0039b98e..18105aaa 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/aspect/LockAspectTest.java @@ -322,6 +322,7 @@ public void testMethod( @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { + // empty } public void testMethodWithStringSubjectId( @@ -329,6 +330,7 @@ public void testMethodWithStringSubjectId( @PathVariable("subjectId") String subjectId, @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { + // empty } public void testMethodWithStringPage( @@ -336,24 +338,27 @@ public void testMethodWithStringPage( @PathVariable("subjectId") UUID subjectId, @PathVariable("page") String page, @RequestParam("userId") UUID userId) { + // empty } public void testMethodWithoutSubject( @PathVariable("page") Page page, @RequestParam("userId") UUID userId) { + // empty } public void testMethodWithoutPage( @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @RequestParam("userId") UUID userId) { + // empty } public void testMethodWithoutUserId( @PathVariable("subjectType") SubjectType subjectType, @PathVariable("subjectId") UUID subjectId, @PathVariable("page") Page page) { + // empty } } } - diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java index bbed02f7..27f6251c 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/controllers/LockControllerTest.java @@ -81,7 +81,7 @@ void getCourtLockStatusThrowsIllegalArgumentExceptionForInvalidUUID() { } @Test - void createCourtLockReturns201() { + void createOrUpdateCourtLockReturns201() { Lock lock = new Lock(); when(lockService.createOrUpdateLock(SubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); @@ -104,22 +104,6 @@ void createCourtLockThrowsIllegalArgumentExceptionForInvalidUUID() { ); } - @Test - void updateCourtLockReturns201() { - Lock lock = new Lock(); - when(lockService.createOrUpdateLock(SubjectType.COURT, COURT_ID, TEST_PAGE, USER_ID)).thenReturn(lock); - - ResponseEntity response = lockController.createOrUpdateSubjectLock( - SubjectType.COURT, - COURT_ID.toString(), - TEST_PAGE, - USER_ID - ); - - assertThat(response.getStatusCode()).as(RESPONSE_STATUS_MESSAGE).isEqualTo(HttpStatus.CREATED); - assertThat(response.getBody()).as(RESPONSE_BODY_MESSAGE).isEqualTo(lock); - } - @Test void deleteCourtLockReturns204() { ResponseEntity response = diff --git a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java index 5732e79f..afc67fc5 100644 --- a/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/fact/data/api/services/LockServiceTest.java @@ -135,27 +135,42 @@ void getPageLockShouldGetPageLock() { @Test void createLockShouldCreateLock() { + UUID lockId = UUID.randomUUID(); when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); - when(lockRepository.save(any(Lock.class))).thenReturn(lock); + when(lockRepository.upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.COURT.name()), eq(courtId), + eq(Page.GENERAL.name()), eq(userId), any(ZonedDateTime.class) + )).thenReturn(lockId); + Lock result = lockService.createOrUpdateLock(SubjectType.COURT, courtId, Page.GENERAL, userId); + assertNotNull(result); - verify(lockRepository).save(any(Lock.class)); + assertEquals(lockId, result.getId()); + verify(lockRepository).upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.COURT.name()), eq(courtId), + eq(Page.GENERAL.name()), eq(userId), any(ZonedDateTime.class) + ); } @Test void createOrUpdateLockShouldUpdateExistingLock() { Page page = Page.GENERAL; + UUID lockId = UUID.randomUUID(); when(courtService.getCourtById(courtId)).thenReturn(court); when(userService.getUserById(userId)).thenReturn(user); - when(lockRepository.findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page)) - .thenReturn(Optional.of(lock)); - when(lockRepository.save(any(Lock.class))).thenReturn(lock); + when(lockRepository.upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.COURT.name()), eq(courtId), eq(page.name()), eq(userId), any(ZonedDateTime.class) + )).thenReturn(lockId); + Lock result = lockService.createOrUpdateLock(SubjectType.COURT, courtId, page, userId); + assertNotNull(result); + assertEquals(lockId, result.getId()); verify(courtService).getCourtById(courtId); - verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); - verify(lockRepository).save(any(Lock.class)); + verify(lockRepository).upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.COURT.name()), eq(courtId), eq(page.name()), eq(userId), any(ZonedDateTime.class) + ); } @Test @@ -164,7 +179,7 @@ void deleteLockShouldDeleteLock() { when(courtService.getCourtById(courtId)).thenReturn(court); lockService.deleteLock(SubjectType.COURT, courtId, page); verify(courtService).getCourtById(courtId); - verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT, courtId, page); + verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage(SubjectType.COURT.name(), courtId, page.name()); } @Test @@ -196,18 +211,23 @@ void getPageLockShouldGetPageLockForServiceCentre() { @Test void createLockShouldCreateLockForServiceCentre() { + UUID lockId = UUID.randomUUID(); when(serviceCentreService.getServiceCentreById(serviceCentreId)).thenReturn(serviceCentre); when(userService.getUserById(userId)).thenReturn(user); - when(lockRepository.save(any(Lock.class))).thenReturn(lock); + when(lockRepository.upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.SERVICE_CENTRE.name()), eq(serviceCentreId), eq(Page.GENERAL.name()), eq(userId), + any(ZonedDateTime.class) + )).thenReturn(lockId); Lock result = lockService.createOrUpdateLock(SubjectType.SERVICE_CENTRE, serviceCentreId, Page.GENERAL, userId); assertNotNull(result); + assertEquals(lockId, result.getId()); verify(serviceCentreService).getServiceCentreById(serviceCentreId); - verify(lockRepository).findBySubjectTypeAndSubjectIdAndPage( - SubjectType.SERVICE_CENTRE, serviceCentreId, Page.GENERAL); - verify(lockRepository).save(any(Lock.class)); - verify(lockRepository).deleteAllByUserIdAndIdIsNot(eq(userId), any(UUID.class)); + verify(lockRepository).upsertLockAndDeleteOtherUserLocks( + any(), eq(SubjectType.SERVICE_CENTRE.name()), eq(serviceCentreId), eq(Page.GENERAL.name()), eq(userId), + any(ZonedDateTime.class) + ); } @Test @@ -219,7 +239,7 @@ void deleteLockShouldDeleteLockForServiceCentre() { verify(serviceCentreService).getServiceCentreById(serviceCentreId); verify(lockRepository).deleteBySubjectTypeAndSubjectIdAndPage( - SubjectType.SERVICE_CENTRE, serviceCentreId, page); + SubjectType.SERVICE_CENTRE.name(), serviceCentreId, page.name()); } @Test From 916c518afd7303049f19b6d6903e12c609fd410c Mon Sep 17 00:00:00 2001 From: hurricanepilot Date: Thu, 9 Jul 2026 10:08:07 +0100 Subject: [PATCH 11/11] whitespace --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 67b8fd62..30494e45 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # 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)