From 634ae4bbda210342e227adb97b783081e0b4bfd1 Mon Sep 17 00:00:00 2001 From: Jamie Briggs Date: Tue, 25 Feb 2025 13:57:24 +0000 Subject: [PATCH] CCMSPUI-379 Implement GetClientDetails (#151) * CCMSPUI-529 Made provider-id mandatory for notification search * CCMSPUI-529 Made provider case reference and surname case-insensitive for notification search * CCMSPUI-529 Modified Notification and Refactored into NotificationInfo * CCMSPUI-529 Fixes to tests * CCMSPUI-529 Fixes to tests and removed XML mappings * CCMSPUI-529 Fixed NotificationSearchRepository filter SQL method * CCMSPUI-529 Removed dead code * CCMSPUI-529 Refactored NotificationSearchRepositoryIntegrationTest to use Oracle-Free instead of embedded H2 DB * CCMSPUI-529 Added BooleanStringConverterTest * CCMSPUI-530 Updated open-api-specification to include new get notification endpoint and added Notification schema for holding more data * CCMSPUI-530 Added new entities for notes, documents, attachments and actions * CCMSPUI-530 Started on NotificationRepositoryIntegrationTest * CCMSPUI-530 Completed NotificationRepositoryIntegrationTest * CCMSPUI-530 Fixes to integration tests * CCMSPUI-530 Created various mappers for mapping document entity for presentation layer * CCMSPUI-530 Finished mapper tests * CCMSPUI-530 Finished controller and service tests * CCMSPUI-530 Completed JavaDocs * CCMSPUI-530 Added title to DocumentMapper when mapping title * CCMSPUI-530 Added provider id to request params to secure get notification endpoint * CCMSPUI-530 Added evidence allowed to notification info response * CCMSPUI-530 Moved evidence allowed to correct schema in open-api-specification.yml * CCMSPUI-530 Update to method names * CCMSPUI-530 updated Notification Note to return time along with date * CCMSPUI-530 fixed NotificationRepositoryIntegrationTest * CCMSPUI-379 Added to open-api-specification.yml * CCMSPUI-379 Added ClientDetail. Excluded some columns pending investigation. * CCMSPUI-379 Started on ClientDetailRepositoryIntegrationTest * CCMSPUI-379 Adding missing sort statement to NotificationSearchRepository * CCMSPUI-379 Implemented filters for NotificationSearchRepository * CCMSPUI-379 Client search implemented. Address LOB still needs adding and checkstyle still needs rectifying. * CCMSPUI-379 Updated ClientDetailRepository to filter by surname at birth instead of current surname to replicate SOA API * CCMSPUI-379 Fixed issues with inputs which contain apostrophes * CCMSPUI-379 Fixed checkstyle issues * CCMSPUI-379 Fixed broken tests in ClientsControllerTest and ClientDetailsMapperImplTest * CCMSPUI-379 Fixed broken tests in ClientsControllerTest and ClientDetailsMapperImplTest * CCMSPUI-379 Fixed ClientDetailRepository and checkstyle issue * CCMSPUI-379 Updated BaseEntityManagerRepository to include abstracted "addCondition" methods to clean up inherited classes * CCMSPUI-379 Updates to BaseEntityManagerRepository to abstract away the search functionality and utilize the JPA Specification class * CCMSPUI-379 Made request params in client search mandatory * CCMSPUI-379 Refactored BaseEntityManagerRepository to simplify CriteriaBuilder implementation * CCMSPUI-379: Update count query to be more efficient * CCMSPUI-379: Updated BaseEntityManagerRepository to use completable features * CCMSPUI-379: Added helper method to count entities by their ID name * CCMSPUI-379: Abstracted away getEntityClazz method, enabled virtual threads, and added completable futures on BaseEntityManagerRepository * CCMSPUI-379: Fixed checkstyle issues Signed-off-by: Jamie Briggs --- data-api/open-api-specification.yml | 94 ++++ .../CaseSearchRepositoryIntegerationTest.java | 411 ------------------ .../CaseSearchRepositoryIntegrationTest.java | 351 +++++++++++++++ ...ClientDetailRepositoryIntegrationTest.java | 288 ++++++++++++ ...NotificationRepositoryIntegrationTest.java | 24 - ...cationSearchRepositoryIntegrationTest.java | 403 +++++++++-------- .../sql/case_search_create_schema.sql | 88 ++++ .../resources/sql/case_search_drop_schema.sql | 1 + .../sql/get_client_details_create_schema.sql | 49 +++ .../sql/get_client_details_drop_schema.sql | 1 + .../data/controller/ClientsController.java | 16 + .../laa/ccms/data/entity/ClientDetail.java | 86 ++++ .../ccms/data/entity/NotificationInfo.java | 2 + .../ccms/data/mapper/ClientDetailsMapper.java | 50 +++ .../ccms/data/mapper/NotificationMapper.java | 2 +- .../BaseEntityManagerRepository.java | 137 ++++++ .../data/repository/CaseSearchRepository.java | 123 +----- .../repository/ClientDetailRepository.java | 37 ++ .../NotificationSearchRepository.java | 159 +------ .../CaseSearchSpecification.java | 81 ++++ .../ClientDetailSpecification.java | 95 ++++ .../NotificationInfoSpecification.java | 105 +++++ .../ccms/data/service/CaseSearchService.java | 6 +- .../laa/ccms/data/service/ClientService.java | 36 ++ .../data/service/NotificationService.java | 6 +- .../src/main/resources/application-local.yml | 2 + .../src/main/resources/application.yml | 3 + .../controller/ClientsControllerTest.java | 69 ++- .../mapper/ClientDetailsMapperImplTest.java | 114 +++++ .../mapper/NotificationMapperImplTest.java | 1 + .../mapper/NotificationsMapperImplTest.java | 1 - .../data/service/CaseSearchServiceTest.java | 7 +- .../ccms/data/service/ClientServiceTest.java | 234 ++++++---- .../data/service/NotificationServiceTest.java | 3 +- 34 files changed, 2107 insertions(+), 978 deletions(-) delete mode 100644 data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegerationTest.java create mode 100644 data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegrationTest.java create mode 100644 data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/ClientDetailRepositoryIntegrationTest.java create mode 100644 data-service/src/integrationTest/resources/sql/case_search_create_schema.sql create mode 100644 data-service/src/integrationTest/resources/sql/case_search_drop_schema.sql create mode 100644 data-service/src/integrationTest/resources/sql/get_client_details_create_schema.sql create mode 100644 data-service/src/integrationTest/resources/sql/get_client_details_drop_schema.sql create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/entity/ClientDetail.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapper.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/repository/BaseEntityManagerRepository.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/repository/ClientDetailRepository.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/CaseSearchSpecification.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/ClientDetailSpecification.java create mode 100644 data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/NotificationInfoSpecification.java create mode 100644 data-service/src/test/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapperImplTest.java diff --git a/data-api/open-api-specification.yml b/data-api/open-api-specification.yml index 02064aa9..caa43ad7 100644 --- a/data-api/open-api-specification.yml +++ b/data-api/open-api-specification.yml @@ -1068,6 +1068,70 @@ paths: description: 'Forbidden' '500': description: 'Internal server error' + /clients: + get: + tags: + - clients + summary: 'Get Clients' + operationId: 'getClients' + x-spring-paginated: true + parameters: + - name: 'first-name' + in: 'query' + required: true + schema: + type: 'string' + example: 'john' + - name: 'surname' + in: 'query' + required: true + schema: + type: 'string' + example: 'smith' + - name: 'date-of-birth' + in: 'query' + required: true + schema: + type: 'string' + example: "2017-01-01" + format: date + - name: 'gender' + in: 'query' + schema: + type: 'string' + example: 'Male' + - name: 'client-reference-number' + in: 'query' + schema: + type: 'string' + example: '1234567890' + - name: 'home-office-reference' + in: 'query' + schema: + type: 'string' + example: '1234567890' + - name: 'national-insurance-number' + in: 'query' + schema: + type: 'string' + example: 'AB123456C' + responses: + '200': + description: 'Successful operation' + content: + application/json: + schema: + $ref: "#/components/schemas/clientDetails" + '400': + description: 'Bad request' + '401': + description: 'Unauthorized' + '403': + description: 'Forbidden' + '404': + description: 'Not found' + '500': + description: 'Internal server error' /clients/status/{transaction-request-id}: get: tags: @@ -1546,6 +1610,36 @@ components: type: 'string' default_code: type: 'string' + clientDetails: + allOf: + - $ref: "#/components/schemas/page" + type: 'object' + properties: + content: + type: 'array' + default: [ ] + items: + $ref: "#/components/schemas/clientSummary" + clientSummary: + allOf: + - $ref: '#/components/schemas/baseClient' + type: 'object' + properties: + surname_at_birth: + type: 'string' + full_name: + type: 'string' + date_of_birth: + type: 'string' + format: 'date' + gender: + type: 'string' + postal_code: + type: 'string' + home_office_reference: + type: 'string' + national_insurance_number: + type: 'string' baseClient: type: 'object' properties: diff --git a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegerationTest.java b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegerationTest.java deleted file mode 100644 index 46e97bf3..00000000 --- a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegerationTest.java +++ /dev/null @@ -1,411 +0,0 @@ -package uk.gov.laa.ccms.data.repository; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.test.context.ActiveProfiles; -import uk.gov.laa.ccms.data.entity.CaseSearch; - -@DataJpaTest -@ActiveProfiles("h2-test") -@DisplayName("Case Search Repository Integration Test") -class CaseSearchRepositoryIntegerationTest { - - private CaseSearchRepository caseSearchRepository; - - @PersistenceContext - private EntityManager entityManager; - - private CaseSearch s1; - private CaseSearch s2; - - @BeforeEach - void setUp() { - caseSearchRepository = new CaseSearchRepository(entityManager); - // Insert some test case search rows - s1 = CaseSearch.builder() - .providerFirmPartyId(100L) - .casePartyId(1001L) - .appOrCertSrId(2001L) - .lscCaseReference("3001") - .cisCaseReference("4001") - .clientPartyId(5001L) - .personFirstName("First") - .personLastName("Last") - .providerCaseReference("6001") - .providerOfficePartyId(8001L) - .feeEarnerPartyId(9001L) - .feeEarner("Fee One") - .categoryOfLaw("Category One") - .categoryOfLawDescription("Category One Description") - .actualCaseStatus("ONE") - .displayCaseStatus("Display Status One") - .build(); - s2 = CaseSearch.builder() - .providerFirmPartyId(100L) - .casePartyId(1002L) - .appOrCertSrId(2002L) - .lscCaseReference("3002") - .cisCaseReference("4002") - .clientPartyId(5002L) - .personFirstName("First Two") - .personLastName("Last Two") - .providerCaseReference("6002") - .providerOfficePartyId(8002L) - .feeEarnerPartyId(9002L) - .feeEarner("Fee Two") - .categoryOfLaw("Category Two") - .categoryOfLawDescription("Category Two Description") - .actualCaseStatus("TWO") - .displayCaseStatus("Display Status Two") - .build(); - // Persist both searchs - entityManager.persist(s1); - entityManager.persist(s2); - } - - @Test - @DisplayName("Should get all case search using provider ID") - void shouldGetAllCaseSearchRowsUsingProviderId() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(2, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should contain correct pagination information") - void shouldContainCorrectPaginationInformation() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - null, - null, - Pageable.ofSize(1).withPage(0)); - // Then - assertTrue(result.getContent().contains(s1)); - assertEquals(2, result.getTotalElements()); - assertEquals(2, result.getTotalPages()); - assertEquals(1, result.getNumberOfElements()); - assertEquals(0, result.getNumber()); - } - - @Test - @DisplayName("Should find no cases when provider ID does not match") - void shouldFindNoCaseSearchRowsWhenProviderIdDoesNotMatch() { - // Given - // When - Page result = caseSearchRepository.findAll(200L, - "3001", - null, - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(0, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter by case reference number") - void shouldFilterByCaseReferenceNumber() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - "3001", - null, - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter like case reference number") - void shouldFilterLikeCaseReferenceNumber() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - "300", - null, - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(2, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter by provider case reference number") - void shouldFilterByProviderCaseReferenceNumber() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - "6001", - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter like provider case reference number") - void shouldFilterLikeProviderCaseReferenceNumber() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - "600", - null, - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(2, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter exactly status") - void shouldFilterExactlyStatus() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - "ONE", - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter exactly status two") - void shouldFilterExactlyStatusTwo() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - "TWO", - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter exactly status none") - void shouldFilterExactlyStatusNone() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - "ON", - null, - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(0, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter by client surname") - void shouldFilterByClientSurname() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - "Last Two", - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter like client surname") - void shouldFilterLikeClientSurname() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - "Last", - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(2, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter like client surname ignore case") - void shouldFilterLikeClientSurnameIgnoreCase() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - "LaSt twO", - null, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertTrue(result.getContent().contains(s2)); - } - - @Test - @DisplayName("Should filter equal fee earner ID") - void shouldFilterEqualFeeEarnerId() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - 9001L, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - - @Test - @DisplayName("Should filter equal fee earner ID return none") - void shouldFilterEqualFeeEarnerIdReturnNone() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - 900L, - null, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(0, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - - @Test - @DisplayName("Should filter equal fee earner ID") - void shouldFilterEqualOfficeId() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - null, - 8001L, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(1, result.getTotalElements()); - assertTrue(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - - @Test - @DisplayName("Should filter equal fee earner ID return none") - void shouldFilterEqualOfficeIdReturnNone() { - // Given - // When - Page result = caseSearchRepository.findAll(100L, - null, - null, - null, - null, - null, - 800L, - Pageable.ofSize(10).withPage(0)); - // Then - assertEquals(0, result.getTotalElements()); - assertFalse(result.getContent().contains(s1)); - assertFalse(result.getContent().contains(s2)); - } - - - @AfterEach - void afterEach() { - entityManager.remove(s1); - entityManager.remove(s2); - } -} diff --git a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegrationTest.java b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegrationTest.java new file mode 100644 index 00000000..fde7031f --- /dev/null +++ b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/CaseSearchRepositoryIntegrationTest.java @@ -0,0 +1,351 @@ +package uk.gov.laa.ccms.data.repository; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import org.springframework.test.context.jdbc.SqlMergeMode; +import uk.gov.laa.ccms.data.OracleIntegrationTestInterface; +import uk.gov.laa.ccms.data.entity.CaseSearch; +import uk.gov.laa.ccms.data.repository.specification.CaseSearchSpecification; + +@SpringBootTest +@SqlMergeMode(SqlMergeMode.MergeMode.MERGE) +@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_CLASS, scripts = { + "/sql/case_search_create_schema.sql" +}) +@Sql(executionPhase = ExecutionPhase.AFTER_TEST_CLASS, scripts = { + "/sql/case_search_drop_schema.sql" +}) +@DisplayName("Case Search Repository Integration Test") +class CaseSearchRepositoryIntegrationTest implements OracleIntegrationTestInterface { + + @Autowired + private CaseSearchRepository caseSearchRepository; + + @Test + @DisplayName("Should get all case search using provider ID") + void shouldGetAllCaseSearchRowsUsingProviderId() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(2, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + assertEquals(result.getContent().get(1).getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should contain correct pagination information") + void shouldContainCorrectPaginationInformation() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + null, + null), + Pageable.ofSize(1).withPage(0)); + // Then + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + assertEquals(2, result.getTotalElements()); + assertEquals(2, result.getTotalPages()); + assertEquals(1, result.getNumberOfElements()); + assertEquals(0, result.getNumber()); + } + + @Test + @DisplayName("Should find no cases when provider ID does not match") + void shouldFindNoCaseSearchRowsWhenProviderIdDoesNotMatch() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(200L, + "3001", + null, + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(0, result.getTotalElements()); + } + + @Test + @DisplayName("Should filter by case reference number") + void shouldFilterByCaseReferenceNumber() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + "3001", + null, + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + } + + @Test + @DisplayName("Should not filter like case reference number") + void shouldNotFilterLikeCaseReferenceNumber() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + "300", + null, + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(0, result.getTotalElements()); + } + + @Test + @DisplayName("Should filter by provider case reference number") + void shouldFilterByProviderCaseReferenceNumber() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + "6001", + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + + } + + @Test + @DisplayName("Should filter like provider case reference number") + void shouldFilterLikeProviderCaseReferenceNumber() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + "600", + null, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(2, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + assertEquals(result.getContent().get(1).getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should filter exactly status") + void shouldFilterExactlyStatus() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + "ONE", + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + } + + @Test + @DisplayName("Should filter exactly status two") + void shouldFilterExactlyStatusTwo() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + "TWO", + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should filter exactly status none") + void shouldFilterExactlyStatusNone() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + "ON", + null, + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(0, result.getTotalElements()); + } + + @Test + @DisplayName("Should filter by client surname") + void shouldFilterByClientSurname() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + "Last Two", + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should filter like client surname") + void shouldFilterLikeClientSurname() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + "Last", + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(2, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + assertEquals(result.getContent().get(1).getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should filter like client surname ignore case") + void shouldFilterLikeClientSurnameIgnoreCase() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + "LaSt twO", + null, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3002"); + } + + @Test + @DisplayName("Should filter equal fee earner ID") + void shouldFilterEqualFeeEarnerId() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + 9001L, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + + } + + + @Test + @DisplayName("Should filter equal fee earner ID return none") + void shouldFilterEqualFeeEarnerIdReturnNone() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + 900L, + null), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(0, result.getTotalElements()); + + + } + + + @Test + @DisplayName("Should filter equal fee earner ID") + void shouldFilterEqualOfficeId() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + null, + 8001L), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(1, result.getTotalElements()); + assertEquals(result.getContent().getFirst().getLscCaseReference(), "3001"); + + } + + + @Test + @DisplayName("Should filter equal fee earner ID return none") + void shouldFilterEqualOfficeIdReturnNone() { + // Given + // When + Page result = caseSearchRepository.findAll(CaseSearchSpecification.filterBy(100L, + null, + null, + null, + null, + null, + 800L), + Pageable.ofSize(10).withPage(0)); + // Then + assertEquals(0, result.getTotalElements()); + + + } + +} diff --git a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/ClientDetailRepositoryIntegrationTest.java b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/ClientDetailRepositoryIntegrationTest.java new file mode 100644 index 00000000..ade5225d --- /dev/null +++ b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/ClientDetailRepositoryIntegrationTest.java @@ -0,0 +1,288 @@ +package uk.gov.laa.ccms.data.repository; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import org.springframework.test.context.jdbc.SqlMergeMode; +import uk.gov.laa.ccms.data.OracleIntegrationTestInterface; +import uk.gov.laa.ccms.data.entity.ClientDetail; +import uk.gov.laa.ccms.data.repository.specification.ClientDetailSpecification; + +@SpringBootTest +@SqlMergeMode(SqlMergeMode.MergeMode.MERGE) +@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_CLASS, scripts = { + "/sql/get_client_details_create_schema.sql" +}) +@Sql(executionPhase = ExecutionPhase.AFTER_TEST_CLASS, scripts = { + "/sql/get_client_details_drop_schema.sql" +}) +@DisplayName("Client search repository integration tests") +public class ClientDetailRepositoryIntegrationTest implements OracleIntegrationTestInterface { + + @Autowired + private ClientDetailRepository repository; + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("Should return two client details") + void shouldReturnTwoClientDetails(String emptyStringInput) { + // Given + // When + Page result = repository.findAll( + ClientDetailSpecification.filterBy(emptyStringInput, emptyStringInput, + null, + emptyStringInput, emptyStringInput, emptyStringInput, emptyStringInput), + PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getTotalElements()); + } + + @Test + @DisplayName("Should return single client detail") + void shouldReturnSingleClientDetail() { + // Given + // When + Page result = repository.findAll( + ClientDetailSpecification.filterBy(null, null, null, + null, null, null, null), + PageRequest.of(0, 1)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals(2, result.getTotalPages()); + assertEquals(2L, result.getTotalElements()); + } + + @Test + @DisplayName("Should return single client filter equals first name") + void shouldReturnSingleClientFilterEqualsFirstName() { + // Given + String firstName = "john"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(firstName, + null, null, null, null, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("John", result.getContent().getFirst().getFirstName()); + } + + @Test + @DisplayName("Should return multiple clients filter like first name") + void shouldReturnMultipleClientFilterLikeFirstName() { + // Given + String firstName = "j"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(firstName, + null, null, null, null, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals("John", result.getContent().getFirst().getFirstName()); + assertEquals("Jane", result.getContent().get(1).getFirstName()); + } + + @Test + @DisplayName("Should return single client filter equals surname at birth") + void shouldReturnSingleClientFilterEqualsSurnameAtBirth() { + // Given + String surname = "smithson"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, + surname, null, null, null, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("Doe", result.getContent().getFirst().getSurname()); + assertEquals("Smithson", result.getContent().getFirst().getSurnameAtBirth()); + } + + @Test + @DisplayName("Should return mulitple clients filter like surname at birth") + void shouldReturnMultipleClientsFilterLikeSurnameAtBirth() { + // Given + String surname = "smith"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, + surname, null, null, null, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals("Smithson", result.getContent().getFirst().getSurnameAtBirth()); + assertEquals("Smith", result.getContent().get(1).getSurnameAtBirth()); + } + + @Test + @DisplayName("Should return single client filer equals date of birth") + void shouldReturnSingleClientFilterEqualsDateOfBirth() { + // Given + LocalDate dateOfBirth = LocalDate.of(1985, 06, 15); + // When + Page result = repository.findAll( + ClientDetailSpecification.filterBy(null, null, dateOfBirth, + null, null, null, null), + PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals(dateOfBirth, result.getContent().getFirst().getDateOfBirth()); + } + + @Test + @DisplayName("Should return single client equals gender") + void shouldReturnSingleClientFilterEqualsGender() { + // Given + String gender = "male"; + // When + Page result = repository.findAll( + ClientDetailSpecification.filterBy(null, null, null, + gender, null, null, null), + PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("Male", result.getContent().getFirst().getGender()); + } + + @Test + @DisplayName("Should return single client filter equals gender alt") + void shouldReturnSingleClientFilterEqualsGenderAlt() { + // Given + String gender = "FEMALE"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, gender, null, null, + null), PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("Female", result.getContent().getFirst().getGender()); + } + + @Test + @DisplayName("Should return single client filter equals client reference number") + void shouldReturnSingleClientFilterEqualsClientReferenceNumber() { + // Given + String clientReferenceNumber = "100000000000001"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, clientReferenceNumber, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals(100000000000001L, result.getContent().getFirst() + .getClientReferenceNumber()); + } + + @Test + @DisplayName("Should return multiple client filter like client reference number") + void shouldReturnMultipleClientFilterLikeClientReferenceNumber() { + // Given + String clientReferenceNumber = "10000000000"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, clientReferenceNumber, + null, null), PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals(100000000000001L, result.getContent().getFirst() + .getClientReferenceNumber()); + assertEquals(100000000000002L, result.getContent().get(1) + .getClientReferenceNumber()); + } + + @Test + @DisplayName("Should return single client filter equals home office reference number") + void shouldReturnSingleClientFilterEqualsHomeOfficeReferenceNumber() { + // Given + String homeOfficeNumber = "HO123456"; + // When + Page result = repository.findAll( + ClientDetailSpecification.filterBy(null, null, + null, null, null, + homeOfficeNumber, null), PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("HO123456", result.getContent().getFirst() + .getHomeOfficeNumber()); + } + + @Test + @DisplayName("Should return multiple clients filter like home office reference number") + void shouldReturnMultipleClientsFilterLikeHomeOfficeReferenceNumber() { + // Given + String homeOfficeNumber = "HO"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, null, + homeOfficeNumber, null), PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals("HO123456", result.getContent().getFirst() + .getHomeOfficeNumber()); + assertEquals("HO987654", result.getContent().get(1) + .getHomeOfficeNumber()); + } + + @Test + @DisplayName("Should return single client filter equals national insurance number") + void shouldReturnSingleClientFilterEqualsNationalInsuranceNumber() { + // Given + String nationalInsuranceNumber = "AB123456C"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, null, + null, nationalInsuranceNumber), + PageRequest.of(0, 10)); + // Then + assertEquals(1L, result.getContent().size()); + assertEquals("AB123456C", result.getContent().getFirst() + .getNationalInsuranceNumber()); + } + + @Test + @DisplayName("Should return multiple clients filter like national insurance number") + void shouldReturnMultipleClientsFilterLikeNationalInsuranceNumber() { + // Given + String nationalInsuranceNumber = "123"; + // When + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, null, + null, nationalInsuranceNumber), + PageRequest.of(0, 10)); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals("AB123456C", result.getContent().getFirst() + .getNationalInsuranceNumber()); + assertEquals("CD123654E", result.getContent().get(1) + .getNationalInsuranceNumber()); + } + + @Test + @DisplayName("Should sort by first name") + void shouldSortByFirstName() { + // Given + // When + PageRequest pageable = PageRequest.of(0, 10, + Sort.by(Sort.Order.asc("FirstName"))); + Page result = repository.findAll(ClientDetailSpecification.filterBy(null, null, + null, null, null, + null, null), + pageable); + // Then + assertEquals(2L, result.getContent().size()); + assertEquals("Jane", result.getContent().getFirst() + .getFirstName()); + assertEquals("John", result.getContent().get(1) + .getFirstName()); + } + +} diff --git a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationRepositoryIntegrationTest.java b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationRepositoryIntegrationTest.java index 63179f16..9b48ed8c 100644 --- a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationRepositoryIntegrationTest.java +++ b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationRepositoryIntegrationTest.java @@ -36,30 +36,6 @@ public class NotificationRepositoryIntegrationTest implements OracleIntegrationT @Autowired private NotificationRepository repository; - private NotificationInfo notification; - - @BeforeEach - void setup(){ - notification = NotificationInfo.builder().notificationId(1L) - .userId("test_user") - .userLoginId("test_login") - .providerFirmId(10L) - .dateAssigned(LocalDate.of(2025, 1, 1)) - .subject("Subject") - .dueDate(LocalDate.of(2027, 1, 1)) - .assignedTo("JBriggs") - .status("open") - .lscCaseRefReference("1001") - .providerCaseReference("First Case Reference") - .clientName("Jamie Briggs") - .feeEarner("Fee") - .personLastName("Briggs") - .feeEarnerPartyId(3001L) - .actionNotificationInd("N") - .isOpen(true) - .build(); - } - @Test @DisplayName("Should return notification") void shouldReturnNotification(){ diff --git a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepositoryIntegrationTest.java b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepositoryIntegrationTest.java index 16d5561f..f573c79c 100644 --- a/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepositoryIntegrationTest.java +++ b/data-service/src/integrationTest/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepositoryIntegrationTest.java @@ -20,16 +20,17 @@ import org.springframework.test.context.jdbc.SqlMergeMode; import uk.gov.laa.ccms.data.OracleIntegrationTestInterface; import uk.gov.laa.ccms.data.entity.NotificationInfo; +import uk.gov.laa.ccms.data.repository.specification.NotificationInfoSpecification; @SpringBootTest @SqlMergeMode(MERGE) -@Sql(executionPhase=BEFORE_TEST_CLASS,scripts= "/sql/get_notif_info_create_schema.sql") -@Sql(executionPhase=AFTER_TEST_CLASS,scripts= "/sql/get_notif_info_drop_schema.sql") +@Sql(executionPhase = BEFORE_TEST_CLASS, scripts = "/sql/get_notif_info_create_schema.sql") +@Sql(executionPhase = AFTER_TEST_CLASS, scripts = "/sql/get_notif_info_drop_schema.sql") @DisplayName("Notification Info Repository Integration Test") class NotificationSearchRepositoryIntegrationTest implements OracleIntegrationTestInterface { - + private NotificationSearchRepository notificationRepository; - + @PersistenceContext private EntityManager entityManager; @@ -81,59 +82,63 @@ void setUp() { @Test @DisplayName("Should not get any Notifications") - void shouldNotGetAnyNotifications(){ + void shouldNotGetAnyNotifications() { // Given // When - Page result = notificationRepository.findAll(100L, - null, - null, - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(100L, + null, + null, + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertTrue(result.getContent().isEmpty()); } @Test @DisplayName("Should get all Notifications by provider ID") - void shouldGetAllNotifications(){ + void shouldGetAllNotifications() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, + null, + null, + null), + Pageable.ofSize(10).withPage(0)); // Then assertEquals(2, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); assertTrue(result.getContent().contains(n2)); } - + @Test @DisplayName("Should filter by case reference number") - void shouldFilterByCaseReferenceNumber(){ + void shouldFilterByCaseReferenceNumber() { // Given // When - Page result = notificationRepository.findAll(10L, - "1001", - null, - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + "1001", + null, + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -141,19 +146,20 @@ void shouldFilterByCaseReferenceNumber(){ @Test @DisplayName("Should filter by similar case reference number") - void shouldFilterBySimilarCaseReferenceNumber(){ + void shouldFilterBySimilarCaseReferenceNumber() { // Given // When - Page result = notificationRepository.findAll(10L, - "100", - null, - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + "100", + null, + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertTrue(result.getContent().contains(n1)); assertTrue(result.getContent().contains(n2)); @@ -162,19 +168,20 @@ void shouldFilterBySimilarCaseReferenceNumber(){ @Test @DisplayName("Should filter by provider case reference number") - void shouldFilterByProviderCaseReferenceNumber(){ + void shouldFilterByProviderCaseReferenceNumber() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - "First", - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + "First", + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -182,19 +189,20 @@ void shouldFilterByProviderCaseReferenceNumber(){ @Test @DisplayName("Should filter by provider case reference number case insensitive") - void shouldFilterByProviderCaseReferenceNumberCaseInsensitive(){ + void shouldFilterByProviderCaseReferenceNumberCaseInsensitive() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - "FIRST case REF", - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + "FIRST case REF", + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -202,19 +210,20 @@ void shouldFilterByProviderCaseReferenceNumberCaseInsensitive(){ @Test @DisplayName("Should filter by similar provider case reference number") - void shouldFilterBySimilarProviderCaseReferenceNumber(){ + void shouldFilterBySimilarProviderCaseReferenceNumber() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - "Case Reference", - null, - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + "Case Reference", + null, + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(2, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -223,19 +232,20 @@ void shouldFilterBySimilarProviderCaseReferenceNumber(){ @Test @DisplayName("Should filter by assigned to user ID") - void shouldFilterByAssignedToUserID(){ + void shouldFilterByAssignedToUserID() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - "JBriggs", - null, - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + "JBriggs", + null, + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -243,19 +253,20 @@ void shouldFilterByAssignedToUserID(){ @Test @DisplayName("Should filter by user surname") - void shouldFilterByUserSurname(){ + void shouldFilterByUserSurname() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - "Briggs", - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + "Briggs", + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -263,19 +274,20 @@ void shouldFilterByUserSurname(){ @Test @DisplayName("Should filter by like user surname") - void shouldFilterByLikeUserSurname(){ + void shouldFilterByLikeUserSurname() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - "Bri", - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + "Bri", + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(2, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -284,19 +296,20 @@ void shouldFilterByLikeUserSurname(){ @Test @DisplayName("Should filter by like user surname case insensitive") - void shouldFilterByLikeUserSurnameCaseInsensitive(){ + void shouldFilterByLikeUserSurnameCaseInsensitive() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - "bri-MONDAY", - null, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + "bri-MONDAY", + null, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertFalse(result.getContent().contains(n1)); @@ -305,19 +318,20 @@ void shouldFilterByLikeUserSurnameCaseInsensitive(){ @Test @DisplayName("Should filter by fee earner ID") - void shouldFilterByFeeEarnerID(){ + void shouldFilterByFeeEarnerID() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - 3001, - true, - null, - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + 3001, + true, + null, + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -325,18 +339,19 @@ void shouldFilterByFeeEarnerID(){ @Test @DisplayName("Should filter by notification type") - void shouldFilterByNotificationType(){ + void shouldFilterByNotificationType() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, "N", - null, - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, "N", + null, + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -344,19 +359,20 @@ void shouldFilterByNotificationType(){ @Test @DisplayName("Should filter by date from") - void shouldFilterByDateFrom(){ + void shouldFilterByDateFrom() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, - null, - LocalDate.of(2025, 2, 1), - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, + null, + LocalDate.of(2025, 2, 1), + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n2)); @@ -364,19 +380,20 @@ void shouldFilterByDateFrom(){ @Test @DisplayName("Should filter by date from inclusive") - void shouldFilterByDateFromInclusive(){ + void shouldFilterByDateFromInclusive() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, - null, - LocalDate.of(2024, 1, 1), - null, Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, + null, + LocalDate.of(2024, 1, 1), + null), Pageable.ofSize(10).withPage(0)); // Then assertEquals(2, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -385,19 +402,21 @@ void shouldFilterByDateFromInclusive(){ @Test @DisplayName("Should filter by date to") - void shouldFilterByDateTo(){ + void shouldFilterByDateTo() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, - null, - null, - LocalDate.of(2025, 12, 1), Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, + null, + null, + LocalDate.of(2025, 12, 1)), + Pageable.ofSize(10).withPage(0)); // Then assertEquals(1, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); @@ -405,19 +424,21 @@ void shouldFilterByDateTo(){ @Test @DisplayName("Should filter by date to inclusive") - void shouldFilterByDateToInclusive(){ + void shouldFilterByDateToInclusive() { // Given // When - Page result = notificationRepository.findAll(10L, - null, - null, - null, - null, - null, - true, - null, - null, - LocalDate.of(2027, 1, 1), Pageable.ofSize(10).withPage(0)); + Page result = notificationRepository.findAll( + NotificationInfoSpecification.filterBy(10L, + null, + null, + null, + null, + null, + true, + null, + null, + LocalDate.of(2027, 1, 1)), + Pageable.ofSize(10).withPage(0)); // Then assertEquals(2, result.getTotalElements()); assertTrue(result.getContent().contains(n1)); diff --git a/data-service/src/integrationTest/resources/sql/case_search_create_schema.sql b/data-service/src/integrationTest/resources/sql/case_search_create_schema.sql new file mode 100644 index 00000000..b97ed1e2 --- /dev/null +++ b/data-service/src/integrationTest/resources/sql/case_search_create_schema.sql @@ -0,0 +1,88 @@ +CREATE TABLE XXCCMS.XXCCMS_CASE_SEARCH_V ( + CASE_PARTY_ID NUMBER(15,0) NOT NULL PRIMARY KEY, + APP_OR_CERT_SR_ID NUMBER, + LSC_CASE_REFERENCE VARCHAR2(360) NOT NULL, + CIS_CASE_REFERENCE VARCHAR2(150), + CLIENT_PARTY_ID NUMBER (15, 0), + PERSON_FIRST_NAME VARCHAR2(150), + PERSON_LAST_NAME VARCHAR2(150), + PROVIDER_CASE_REFERENCE VARCHAR2(150), + PROVIDER_FIRM_PARTY_ID NUMBER(15, 0), + PROVIDER_OFFICE_PARTY_ID NUMBER(15, 0), + FEE_EARNER_PARTY_ID NUMBER(15, 0), + FEE_EARNER VARCHAR2(360), + CATEGORY_OF_LAW VARCHAR2(150), + CATEGORY_OF_LAW_DESC VARCHAR(80), + ACTUAL_CASE_STATUS VARCHAR2(30), + DISPLAY_CASE_STATUS VARCHAR2(150) +); + + +INSERT INTO XXCCMS.XXCCMS_CASE_SEARCH_V (CASE_PARTY_ID, + APP_OR_CERT_SR_ID, + LSC_CASE_REFERENCE, + CIS_CASE_REFERENCE, + CLIENT_PARTY_ID, + PERSON_FIRST_NAME, + PERSON_LAST_NAME, + PROVIDER_CASE_REFERENCE, + PROVIDER_FIRM_PARTY_ID, + PROVIDER_OFFICE_PARTY_ID, + FEE_EARNER_PARTY_ID, + FEE_EARNER, + CATEGORY_OF_LAW, + CATEGORY_OF_LAW_DESC, + ACTUAL_CASE_STATUS, + DISPLAY_CASE_STATUS) +VALUES (1001, -- CASE_PARTY_ID + 2001, -- APP_OR_CERT_SR_ID + '3001', -- LSC_CASE_REFERENCE + '4001', -- CIS_CASE_REFERENCE + 5001, -- CLIENT_PARTY_ID + 'First', -- PERSON_FIRST_NAME + 'Last', -- PERSON_LAST_NAME + '6001', -- PROVIDER_CASE_REFERENCE + 100, -- PROVIDER_FIRM_PARTY_ID + 8001, -- PROVIDER_OFFICE_PARTY_ID + 9001, -- FEE_EARNER_PARTY_ID + 'Fee One', -- FEE_EARNER + 'Category One', -- CATEGORY_OF_LAW + 'Category One Description', -- CATEGORY_OF_LAW_DESC + 'ONE', -- ACTUAL_CASE_STATUS + 'Display Status One' -- DISPLAY_CASE_STATUS + ); + + +INSERT INTO XXCCMS.XXCCMS_CASE_SEARCH_V (CASE_PARTY_ID, + APP_OR_CERT_SR_ID, + LSC_CASE_REFERENCE, + CIS_CASE_REFERENCE, + CLIENT_PARTY_ID, + PERSON_FIRST_NAME, + PERSON_LAST_NAME, + PROVIDER_CASE_REFERENCE, + PROVIDER_FIRM_PARTY_ID, + PROVIDER_OFFICE_PARTY_ID, + FEE_EARNER_PARTY_ID, + FEE_EARNER, + CATEGORY_OF_LAW, + CATEGORY_OF_LAW_DESC, + ACTUAL_CASE_STATUS, + DISPLAY_CASE_STATUS) +VALUES (1002, -- CASE_PARTY_ID + 2002, -- APP_OR_CERT_SR_ID + '3002', -- LSC_CASE_REFERENCE + '4002', -- CIS_CASE_REFERENCE + 5002, -- CLIENT_PARTY_ID + 'First Two', -- PERSON_FIRST_NAME + 'Last Two', -- PERSON_LAST_NAME + '6002', -- PROVIDER_CASE_REFERENCE + 100, -- PROVIDER_FIRM_PARTY_ID + 8002, -- PROVIDER_OFFICE_PARTY_ID + 9002, -- FEE_EARNER_PARTY_ID + 'Fee Two', -- FEE_EARNER + 'Category Two', -- CATEGORY_OF_LAW + 'Category Two Description', -- CATEGORY_OF_LAW_DESC + 'TWO', -- ACTUAL_CASE_STATUS + 'Display Status Two' -- DISPLAY_CASE_STATUS + ); diff --git a/data-service/src/integrationTest/resources/sql/case_search_drop_schema.sql b/data-service/src/integrationTest/resources/sql/case_search_drop_schema.sql new file mode 100644 index 00000000..fb477bf8 --- /dev/null +++ b/data-service/src/integrationTest/resources/sql/case_search_drop_schema.sql @@ -0,0 +1 @@ +DROP TABLE XXCCMS.XXCCMS_CASE_SEARCH_V; \ No newline at end of file diff --git a/data-service/src/integrationTest/resources/sql/get_client_details_create_schema.sql b/data-service/src/integrationTest/resources/sql/get_client_details_create_schema.sql new file mode 100644 index 00000000..bd7bac14 --- /dev/null +++ b/data-service/src/integrationTest/resources/sql/get_client_details_create_schema.sql @@ -0,0 +1,49 @@ +CREATE TABLE XXCCMS.XXCCMS_GET_CLIENT_DETAILS_V ( + CLIENT_REFERENCE_NUMBER NUMBER(15, 0) NOT NULL PRIMARY KEY, + TITLE VARCHAR2(30), + FIRSTNAME VARCHAR2(150), + SURNAME VARCHAR2(150), + SURNAME_AT_BIRTH VARCHAR2(150), + DATE_OF_BIRTH DATE, + GENDER VARCHAR2(30), + -- CONTACT_TYPE SYS.XMLTYPE(64) -- + COUNTRY_OF_ORIGIN VARCHAR2(3), + MARITAL_STATUS VARCHAR2(30), + -- CONTACTS XML SYS.XMLTYPE(64) -- + ADDRESS CLOB, + CORRESPONDENCE_METHOD VARCHAR2(150), + DISABILITY_TYPE VARCHAR2(150), + CORRESPONDENCE_LANGUAGE VARCHAR2(150), + ETHNIC_MONITORING VARCHAR2(150), + NO_FIX_ABODE VARCHAR2(150), + NI_NUMBER VARCHAR2(60), + HOME_OFFICE_NUMBER VARCHAR2(150) + -- CASE_REFERENCE XML SYS.XMLTYPE(64) +); + + +INSERT INTO XXCCMS.XXCCMS_GET_CLIENT_DETAILS_V (CLIENT_REFERENCE_NUMBER, TITLE, FIRSTNAME, SURNAME, + SURNAME_AT_BIRTH, + DATE_OF_BIRTH, GENDER, COUNTRY_OF_ORIGIN, + MARITAL_STATUS, + CORRESPONDENCE_METHOD, DISABILITY_TYPE, + CORRESPONDENCE_LANGUAGE, + ETHNIC_MONITORING, NO_FIX_ABODE, NI_NUMBER, + HOME_OFFICE_NUMBER) +VALUES (100000000000001, 'Mr.', 'John', 'Doe', 'Smithson', + TO_DATE('1985-06-15', 'YYYY-MM-DD'), 'Male', 'USA', 'Single', + 'Email', 'None', 'English', + 'Not Recorded', 'No', 'AB123456C', 'HO123456'); + +INSERT INTO XXCCMS.XXCCMS_GET_CLIENT_DETAILS_V (CLIENT_REFERENCE_NUMBER, TITLE, FIRSTNAME, SURNAME, + SURNAME_AT_BIRTH, + DATE_OF_BIRTH, GENDER, COUNTRY_OF_ORIGIN, + MARITAL_STATUS, + CORRESPONDENCE_METHOD, DISABILITY_TYPE, + CORRESPONDENCE_LANGUAGE, + ETHNIC_MONITORING, NO_FIX_ABODE, NI_NUMBER, + HOME_OFFICE_NUMBER) +VALUES (100000000000002, 'Ms.', 'Jane', 'Roe', 'Smith', + TO_DATE('1990-03-21', 'YYYY-MM-DD'), 'Female', 'CAN', 'Married', + 'Phone', 'Visual Impairment', 'French', + 'Caucasian', 'No', 'CD123654E', 'HO987654'); \ No newline at end of file diff --git a/data-service/src/integrationTest/resources/sql/get_client_details_drop_schema.sql b/data-service/src/integrationTest/resources/sql/get_client_details_drop_schema.sql new file mode 100644 index 00000000..270d0893 --- /dev/null +++ b/data-service/src/integrationTest/resources/sql/get_client_details_drop_schema.sql @@ -0,0 +1 @@ +DROP TABLE XXCCMS.XXCCMS_GET_CLIENT_DETAILS_V; \ No newline at end of file diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/controller/ClientsController.java b/data-service/src/main/java/uk/gov/laa/ccms/data/controller/ClientsController.java index d7cb8a7d..4ffbc780 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/controller/ClientsController.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/controller/ClientsController.java @@ -1,8 +1,12 @@ package uk.gov.laa.ccms.data.controller; +import java.time.LocalDate; +import java.util.Optional; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; import uk.gov.laa.ccms.data.api.ClientsApi; +import uk.gov.laa.ccms.data.model.ClientDetails; import uk.gov.laa.ccms.data.model.TransactionStatus; import uk.gov.laa.ccms.data.service.ClientService; import uk.gov.laa.ccms.data.service.ClientServiceException; @@ -44,4 +48,16 @@ public ResponseEntity getClientTransactionStatus(String trans return ResponseEntity.internalServerError().build(); } } + + @Override + public ResponseEntity getClients(String firstName, String surname, + LocalDate dateOfBirth, String gender, String clientReferenceNumber, + String homeOfficeReference, String nationalInsuranceNumber, + Pageable pageable) { + Optional clients = clientService.getClients(firstName, surname, dateOfBirth, + gender, + clientReferenceNumber, homeOfficeReference, nationalInsuranceNumber, + pageable); + return clients.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build()); + } } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/entity/ClientDetail.java b/data-service/src/main/java/uk/gov/laa/ccms/data/entity/ClientDetail.java new file mode 100644 index 00000000..bb847585 --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/entity/ClientDetail.java @@ -0,0 +1,86 @@ +package uk.gov.laa.ccms.data.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.hibernate.annotations.Immutable; + +/** + * Represents a client detail entity from the XXCCMS_GET_CLIENT_DETAILS_V database view. + * + *

This entity captures personal information about a client.

+ * + *

The class is immutable, and its instances can be created using the builder pattern.

+ * + * @author Jamie Briggs + */ +@Entity +@Table(name = "XXCCMS_GET_CLIENT_DETAILS_V", schema = "XXCCMS") +@Getter +@Builder +@Immutable +@AllArgsConstructor +@RequiredArgsConstructor +public class ClientDetail { + + @Id + @Column(name = "CLIENT_REFERENCE_NUMBER", nullable = false, precision = 15) + private long clientReferenceNumber; + + @Column(name = "TITLE", length = 30) + private String title; + + @Column(name = "FIRSTNAME", length = 150) + private String firstName; + + @Column(name = "SURNAME", length = 150) + private String surname; + + @Column(name = "SURNAME_AT_BIRTH", length = 150) + private String surnameAtBirth; + + @Column(name = "DATE_OF_BIRTH") + private LocalDate dateOfBirth; + + @Column(name = "GENDER", length = 30) + private String gender; + + @Column(name = "COUNTRY_OF_ORIGIN", length = 3) + private String countryOfOrigin; + + @Column(name = "MARITAL_STATUS", length = 30) + private String maritalStatus; + + @Lob + @Column(name = "ADDRESS", columnDefinition = "CLOB") + private String address; + + @Column(name = "CORRESPONDENCE_METHOD", length = 150) + private String correspondenceMethod; + + @Column(name = "DISABILITY_TYPE", length = 150) + private String disabilityType; + + @Column(name = "CORRESPONDENCE_LANGUAGE", length = 150) + private String correspondenceLanguage; + + @Column(name = "ETHNIC_MONITORING", length = 150) + private String ethnicMonitoring; + + @Column(name = "NO_FIX_ABODE", length = 150) + private String noFixAbode; + + @Column(name = "NI_NUMBER", length = 60) + private String nationalInsuranceNumber; + + @Column(name = "HOME_OFFICE_NUMBER", length = 150) + private String homeOfficeNumber; + +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/entity/NotificationInfo.java b/data-service/src/main/java/uk/gov/laa/ccms/data/entity/NotificationInfo.java index 31937c6e..4800a9b4 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/entity/NotificationInfo.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/entity/NotificationInfo.java @@ -119,4 +119,6 @@ public class NotificationInfo { @Convert(converter = BooleanStringConverter.class) private Boolean evidenceAllowedIndicator; + + } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapper.java b/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapper.java new file mode 100644 index 00000000..47529ac2 --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapper.java @@ -0,0 +1,50 @@ +package uk.gov.laa.ccms.data.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.data.domain.Page; +import uk.gov.laa.ccms.data.entity.ClientDetail; +import uk.gov.laa.ccms.data.model.ClientDetails; +import uk.gov.laa.ccms.data.model.ClientSummary; + +/** + * Interface responsible for mapping objects from {@link ClientDetail} to {@link ClientSummary} + * and {@link ClientDetails} objects. This interface utilizes MapStruct + * or mapping properties. + * + * @see ClientDetail + * @see ClientSummary + * @see ClientDetails + * + * @author Jamie Briggs + */ +@Mapper(componentModel = "spring") +public interface ClientDetailsMapper { + + + /** + * Maps a {@link ClientDetail} object to a {@link ClientSummary} object. + * + * @param clientDetail the {@link ClientDetail} instance to be mapped + * @return a {@link ClientSummary} object with mapped properties + */ + @Mapping(target = "postalCode", + expression = "java(clientDetail.getAddress() != null ? " + + "clientDetail.getAddress().replaceAll(\".*(.*?)" + + ".*\", \"$1\") : \"\")") + @Mapping(target = "homeOfficeReference", source = "homeOfficeNumber") + @Mapping(target = "fullName", + expression = "java(clientDetail.getFirstName() + ' ' + clientDetail.getSurname())") + ClientSummary mapToClientSummary(ClientDetail clientDetail); + + /** + * Maps the given pageable search results of {@link ClientDetail} entities + * into a {@link ClientDetails} object. + * + * @param searchResults the paginated list of {@link ClientDetail} instances to be mapped + * @return a {@link ClientDetails} object containing transformed search results + * along with pagination metadata. + */ + ClientDetails mapToClientDetails(Page searchResults); + +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/NotificationMapper.java b/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/NotificationMapper.java index ed034111..7ebed3ad 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/NotificationMapper.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/mapper/NotificationMapper.java @@ -54,7 +54,7 @@ public interface NotificationMapper { * the {@link NotificationAction} object. */ @Named("action") - public static String actionToString(NotificationAction action) { + static String actionToString(NotificationAction action) { return action.getNextAction(); } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/BaseEntityManagerRepository.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/BaseEntityManagerRepository.java new file mode 100644 index 00000000..0fb2d485 --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/BaseEntityManagerRepository.java @@ -0,0 +1,137 @@ +package uk.gov.laa.ccms.data.repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.transaction.annotation.Transactional; + +/** + * Abstract base class for implementing repositories using an EntityManager to interact + * with the database. This class provides a method for allowing search queries using + * an associated {@link Specification} to filter and {@link Pageable} for paginating results. + * + * + * @param the entity type managed by this repository + * @author Jamie Briggs + */ +public abstract class BaseEntityManagerRepository { + + protected final EntityManager entityManager; + private final Class entityClazz; + + /** + * Constructor for BaseEntityManagerRepository. + * + * @param entityManager the EntityManager instance used to interact with the persistence context. + */ + protected BaseEntityManagerRepository(EntityManager entityManager) { + this.entityManager = entityManager; + this.entityClazz = (Class) ((ParameterizedType) getClass() + .getGenericSuperclass()) + .getActualTypeArguments()[0]; + + } + + /** + * Finds all entities of type {@code T} that match the provided {@link Specification} + * and are paginated according to the given pageable parameter. + * + *

This method executes two asynchronous database queries: one for fetching the results + * that match the specification, and another for retrieving the total count of records + * matching the same specification. The results are then combined into + * a {@link Page} object.

+ * + * @param specification the specification to filter the entities. Represents the predicate + * for filtering results, which can include conditions on entity attributes. + * @param pageable the pagination and sorting information. Contains information + * about page size, current page, and sorting. + * @return a {@link Page} object containing the results that match the + * {@link Specification} within the bounds of the pagination, and the + * total count of matching records. + */ + @Transactional(readOnly = true) + public Page findAll(final Specification specification, final Pageable pageable) { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + + // Get all results + CompletableFuture> resultListFuture = + CompletableFuture.supplyAsync(() -> getResultList(specification, pageable, + criteriaBuilder)); + + // Build the count query + CompletableFuture countFuture = + CompletableFuture.supplyAsync(() -> getCount(specification, criteriaBuilder)); + + // Wait for both tasks to complete + try { + List resultList = resultListFuture.get(); // Main query results + long totalCount = countFuture.get(); // Count query results + return new PageImpl<>(resultList, pageable, totalCount); + } catch (Exception e) { + throw new RuntimeException("Error executing queries", e); + } + } + + private List getResultList(Specification specification, Pageable pageable, + CriteriaBuilder criteriaBuilder) { + CriteriaQuery mainQuery = criteriaBuilder.createQuery(entityClazz); + Root mainQueryRoot = mainQuery.from(entityClazz); + applyWhereClause(mainQuery, specification, criteriaBuilder, mainQueryRoot); + applySortingClause(mainQuery, pageable, criteriaBuilder, mainQueryRoot); + + Query query = entityManager.createQuery(mainQuery); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + List resultList = query.getResultList(); + return resultList; + } + + private void applyWhereClause(CriteriaQuery query, Specification specification, + CriteriaBuilder criteriaBuilder, Root root) { + if (specification != null) { + Predicate predicate = specification.toPredicate(root, query, criteriaBuilder); + if (predicate != null) { + query.where(predicate); + } + } + } + + private void applySortingClause(CriteriaQuery query, Pageable pageable, + CriteriaBuilder criteriaBuilder, Root root) { + if (pageable.getSort().isSorted()) { + query.orderBy(QueryUtils.toOrders(pageable.getSort(), root, criteriaBuilder)); + } + } + + private long getCount(Specification specification, CriteriaBuilder criteriaBuilder) { + CriteriaQuery countQuery = criteriaBuilder.createQuery(Long.class); + Root countRoot = countQuery.from(entityClazz); + countQuery.select(criteriaBuilder.count(countRoot.get(getIdPropertyName()))); + applyWhereClause(countQuery, specification, criteriaBuilder, countRoot); + return entityManager.createQuery(countQuery).getSingleResult(); + } + + private String getIdPropertyName() { + return java.util.Arrays.stream(entityClazz.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(jakarta.persistence.Id.class)) + .findFirst() + .map(java.lang.reflect.Field::getName) + .orElseThrow(() -> new RuntimeException( + "No @Id annotation found in class: " + entityClazz.getName())); + } + + + + +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/CaseSearchRepository.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/CaseSearchRepository.java index 8580385a..71013fd3 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/CaseSearchRepository.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/CaseSearchRepository.java @@ -1,124 +1,37 @@ package uk.gov.laa.ccms.data.repository; import jakarta.persistence.EntityManager; -import jakarta.persistence.Query; -import java.util.List; -import java.util.Objects; -import java.util.StringJoiner; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.stereotype.Repository; import uk.gov.laa.ccms.data.entity.CaseSearch; /** - * A repository class for performing search queries on {@link CaseSearch} entities. + * Repository for performing operations on the {@link CaseSearch} entity. This repository + * is implemented using an {@link EntityManager} for database interaction. It extends + * the {@link BaseEntityManagerRepository}, which provides custom methods + * for database interaction. * - *

This class utilizes a native SQL query to fetch filtered and paginated results - * from the XXCCMS_CASE_SEARCH_V database view. It combines filtering criteria such - * as provider firm party ID, case references, case status, client surname, fee earner ID, and - * provider office party ID to dynamically construct the query based on the passed parameters.

+ *

The {@link CaseSearchRepository} is designed to handle {@link CaseSearch} entities, which + * represents case data from the XXCCMS_CASE_SEARCH_V view in the database.

* - *

It relies on {@link EntityManager} to execute native SQL queries and doesn't use standard - * Spring Data repositories. All queries are read-only and do not modify the database state.

- * - * @see Page * @see CaseSearch * @see EntityManager - * + * @see BaseEntityManagerRepository + * @see org.springframework.data.jpa.domain.Specification + * @see org.springframework.data.domain.Pageable * @author Jamie Briggs */ -@Component -@RequiredArgsConstructor -public class CaseSearchRepository { - - private final EntityManager entityManager; +@Repository +public class CaseSearchRepository extends BaseEntityManagerRepository { /** - * Retrieves a paginated and filtered list of case search records based on the given parameters. + * Constructs a new instance of {@link CaseSearchRepository}, which is responsible for + * performing database operations on {@link CaseSearch} entities using the provided + * {@link EntityManager}. * - * @param providerFirmPartyId the unique identifier of the provider firm (mandatory) - * @param caseReferenceNumber the case reference number for filtering, can be partially matched - * @param providerCaseReference the reference number specific to the provider for filtering - * @param caseStatus the status of the case to filter by - * @param clientSurname the surname of the client to filter by; case-insensitive partial - * matches are allowed - * @param feeEarnerId the unique identifier of the associated fee earner to filter by - * @param officeId the unique identifier of the provider's office to filter by - * @param pageable the pagination and sorting information - * @return a paginated list of {@code CaseSearch} entities matching the filtering criteria + * @param entityManager the {@link EntityManager} instance used to interact with the database. */ - @Transactional(readOnly = true) - public Page findAll(final long providerFirmPartyId, final String caseReferenceNumber, - final String providerCaseReference, final String caseStatus, final String clientSurname, - final Long feeEarnerId, final Long officeId, final Pageable pageable) { - - final String searchCaseQuery = - """ - SELECT * FROM XXCCMS.XXCCMS_CASE_SEARCH_V - """ - + - getFilterSql(providerFirmPartyId, caseReferenceNumber, providerCaseReference, caseStatus, - clientSurname, feeEarnerId, officeId) - + - """ - OFFSET :offset ROWS FETCH NEXT :size ROWS ONLY - """; - - Query query = entityManager.createNativeQuery(searchCaseQuery, CaseSearch.class); - query.setHint("org.hibernate.readOnly", true); - query.setParameter("offset", pageable.getOffset()); - query.setParameter("size", pageable.getPageSize()); - - final String countCaseQuery = - """ - SELECT COUNT(*) FROM XXCCMS.XXCCMS_CASE_SEARCH_V - """ - + getFilterSql(providerFirmPartyId, caseReferenceNumber, providerCaseReference, - caseStatus, clientSurname, feeEarnerId, officeId); - Query countQuery = entityManager.createNativeQuery(countCaseQuery); - - countQuery.setHint("org.hibernate.readOnly", true); - long total = ((Number) countQuery.getSingleResult()).longValue(); - - List resultList = query.getResultList(); - - return new PageImpl<>(resultList, pageable, total); - } - - private static String getFilterSql(long providerFirmPartyId, String caseReferenceNumber, - String providerCaseReference, String caseStatus, String clientSurname, Long feeEarnerId, - Long officeId) { - StringJoiner sj = new StringJoiner(" AND "); - // Provider firm party id - sj.add("WHERE PROVIDER_FIRM_PARTY_ID = " + providerFirmPartyId); - // Case reference number - if (!Objects.isNull(caseReferenceNumber) && !caseReferenceNumber.isBlank()) { - sj.add("LSC_CASE_REFERENCE LIKE '%" + caseReferenceNumber + "%'"); - } - // Provider case reference - if (!Objects.isNull(providerCaseReference) && !providerCaseReference.isBlank()) { - sj.add("UPPER(PROVIDER_CASE_REFERENCE) LIKE '%" + providerCaseReference.toUpperCase() + "%'"); - } - // Case status - if (!Objects.isNull(caseStatus) && !caseStatus.isBlank()) { - sj.add("ACTUAL_CASE_STATUS = '" + caseStatus + "'"); - } - // Surname - if (!Objects.isNull(clientSurname) && !clientSurname.isBlank()) { - sj.add("UPPER(PERSON_LAST_NAME) LIKE '%" + clientSurname.toUpperCase() + "%'"); - } - // Fee earner party ID - if (!Objects.isNull(feeEarnerId)) { - sj.add("FEE_EARNER_PARTY_ID = " + feeEarnerId); - } - // Provider office party ID - if (!Objects.isNull(officeId)) { - sj.add("PROVIDER_OFFICE_PARTY_ID = " + officeId); - } - return sj + " "; + public CaseSearchRepository(EntityManager entityManager) { + super(entityManager); } } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/ClientDetailRepository.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/ClientDetailRepository.java new file mode 100644 index 00000000..33ceca6a --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/ClientDetailRepository.java @@ -0,0 +1,37 @@ +package uk.gov.laa.ccms.data.repository; + +import jakarta.persistence.EntityManager; +import org.springframework.stereotype.Repository; +import uk.gov.laa.ccms.data.entity.ClientDetail; + +/** + * Repository for performing operations on the {@link ClientDetail} entity. This repository + * is implemented using an {@link EntityManager} for database interaction. It extends + * the {@link BaseEntityManagerRepository}, which provides custom methods + * for database interaction. + * + *

The {@link ClientDetailRepository} is designed to handle {@link ClientDetail} entities, which + * represents client data from the XXCCMS_GET_CLIENT_DETAILS_V view in the database.

+ * + * @see ClientDetail + * @see EntityManager + * @see BaseEntityManagerRepository + * @see org.springframework.data.jpa.domain.Specification + * @see org.springframework.data.domain.Pageable + * @author Jamie Briggs + */ +@Repository +public class ClientDetailRepository extends BaseEntityManagerRepository { + + /** + * Constructs a new instance of {@link ClientDetailRepository}, which is responsible for + * performing database operations on {@link ClientDetail} entities using the provided + * {@link EntityManager}. + * + * @param entityManager the {@link EntityManager} instance used to interact with the database. + */ + public ClientDetailRepository(EntityManager entityManager) { + super(entityManager); + } + +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepository.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepository.java index 2aabc349..b8c0e7ba 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepository.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/NotificationSearchRepository.java @@ -1,159 +1,38 @@ package uk.gov.laa.ccms.data.repository; import jakarta.persistence.EntityManager; -import jakarta.persistence.Query; -import java.time.LocalDate; -import java.util.List; -import java.util.Objects; -import java.util.StringJoiner; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import uk.gov.laa.ccms.data.entity.NotificationInfo; /** - * Repository for searching and retrieving notification records - * using dynamic filters and pagination. + * Repository for performing operations on the {@link NotificationInfo} entity. This repository + * is implemented using an {@link EntityManager} for database interaction. It extends + * the {@link BaseEntityManagerRepository}, which provides custom methods + * for database interaction. * - *

This class interacts directly with the database view `XXCCMS_GET_NOTIF_INFO_V` - * to fetch records related to notifications, applying dynamic filters and paginated results. - * It provides an implementation using native SQL queries to support complex filter conditions.

+ *

The {@link NotificationSearchRepository} is designed to handle {@link NotificationInfo} + * entities, which represents notification data from the XXCCMS_GET_NOTIF_INFO_V view + * in the database.

* - * @author Jamie Briggs * @see NotificationInfo - * @see Pageable + * @see EntityManager + * @see BaseEntityManagerRepository + * @see org.springframework.data.jpa.domain.Specification + * @see org.springframework.data.domain.Pageable + * @author Jamie Briggs */ @Component -@RequiredArgsConstructor -public final class NotificationSearchRepository { - - private final EntityManager entityManager; +public class NotificationSearchRepository extends BaseEntityManagerRepository { /** - * Retrieves a paginated list of NotificationInfo entities from the database, applying the - * specified filters to narrow down the results. + * Constructs a new instance of {@link NotificationSearchRepository}, which is responsible for + * performing database operations on {@link NotificationInfo} entities using the provided + * {@link EntityManager}. * - * @param providerId the ID of the provider firm to filter notifications - * by (mandatory) - * @param caseReferenceNumber the case reference number to search for - * @param providerCaseReference the provider-specific case reference to search for - * @param assignedToUserId the user ID of the person assigned to the notification - * @param clientSurname the surname of the client associated with the notification - * @param feeEarnerId the ID of the fee earner to filter by - * @param includeClosed whether to include closed notifications in the results - * @param notificationType the type of notification to filter results by - * @param assignedDateFrom the start date for filtering notifications by - * assignment date - * @param assignedDateTo the end date for filtering notifications by - * assignment date - * @param pageable the pagination and sorting information - * @return a paginated list of NotificationInfo entities matching the specified filters + * @param entityManager the {@link EntityManager} instance used to interact with the database. */ - public Page findAll(final long providerId, - final String caseReferenceNumber, final String providerCaseReference, - final String assignedToUserId, final String clientSurname, final Integer feeEarnerId, - final Boolean includeClosed, final String notificationType, - final LocalDate assignedDateFrom, final LocalDate assignedDateTo, - final Pageable pageable) { - - final String searchNotificationQuery = - """ - SELECT * FROM XXCCMS.XXCCMS_GET_NOTIF_INFO_V - """ - + - getFilterSql(providerId, caseReferenceNumber, providerCaseReference, - assignedToUserId, clientSurname, feeEarnerId, includeClosed, - notificationType, assignedDateFrom, assignedDateTo) - + - getSortSql(pageable) - + - """ - OFFSET :offset ROWS FETCH NEXT :size ROWS ONLY - """; - - Query query = entityManager.createNativeQuery(searchNotificationQuery, NotificationInfo.class); - query.setHint("org.hibernate.readOnly", true); - query.setParameter("offset", pageable.getOffset()); - query.setParameter("size", pageable.getPageSize()); - - final String notificationCount = - """ - SELECT COUNT(*) FROM XXCCMS.XXCCMS_GET_NOTIF_INFO_V - """ - + - getFilterSql(providerId, caseReferenceNumber, providerCaseReference, - assignedToUserId, clientSurname, feeEarnerId, includeClosed, - notificationType, assignedDateFrom, assignedDateTo); - - Query countQuery = entityManager.createNativeQuery(notificationCount); - - countQuery.setHint("org.hibernate.readOnly", true); - long total = ((Number) countQuery.getSingleResult()).longValue(); - - List resultList = query.getResultList(); - return new PageImpl<>(resultList, pageable, total); - } - - private static String getFilterSql(Long providerId, - String caseReferenceNumber, String providerCaseReference, String assignedToUser, - String clientSurname, Integer feeEarnerId, Boolean includeClosed, - String notificationType, LocalDate assignedDateFrom, LocalDate assignedDateTo) { - - StringJoiner sj = new StringJoiner(" AND "); - // Provider firm party id - sj.add("WHERE PROVIDERFIRM_ID = " + providerId); - // Case reference number - if (stringNotEmpty(caseReferenceNumber)) { - sj.add("LSC_CASE_REF_REFERENCE LIKE '%" + caseReferenceNumber + "%'"); - } - // Provider case reference - if (stringNotEmpty(providerCaseReference)) { - sj.add("UPPER(PROVIDER_CASE_REFERENCE) LIKE '%" + providerCaseReference.toUpperCase() + "%'"); - } - // Assigned to user ID - if (stringNotEmpty(assignedToUser)) { - sj.add("UPPER(ASSIGNED_TO) LIKE '%" + assignedToUser.toUpperCase() + "%'"); - } - // Client Surname - if (stringNotEmpty(clientSurname)) { - sj.add("UPPER(PERSON_LAST_NAME) LIKE '%" + clientSurname.toUpperCase() + "%'"); - } - // Fee Earner ID - if (!Objects.isNull(feeEarnerId)) { - sj.add("UPPER(FEE_EARNER_PARTY_ID) = " + feeEarnerId); - } - // Include closed (If true, include all) - if (Boolean.FALSE.equals(includeClosed)) { - sj.add("IS_OPEN = 'true'"); - } - if (stringNotEmpty(notificationType)) { - sj.add("ACTION_NOTIFICATION_IND = '" + notificationType + "'"); - } - if (Objects.nonNull(assignedDateFrom)) { - sj.add("DATE_ASSIGNED >= TO_DATE('" + assignedDateFrom + "', 'YYYY-MM-DD')"); - } - if (Objects.nonNull(assignedDateTo)) { - sj.add("DATE_ASSIGNED <= TO_DATE('" + assignedDateTo + "', 'YYYY-MM-DD')"); - } - return sj + " "; - } - - - private static boolean stringNotEmpty(String value) { - return value != null && !value.isEmpty(); - } - - private static String getSortSql(Pageable pageable) { - if (pageable.getSort().isEmpty()) { - return " "; - } - - StringJoiner sortJoiner = new StringJoiner(", ", " ORDER BY ", " "); - pageable.getSort().forEach(order -> - sortJoiner.add(order.getProperty() + " " + order.getDirection().name())); - return sortJoiner.toString(); + public NotificationSearchRepository(EntityManager entityManager) { + super(entityManager); } } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/CaseSearchSpecification.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/CaseSearchSpecification.java new file mode 100644 index 00000000..3a0876cb --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/CaseSearchSpecification.java @@ -0,0 +1,81 @@ +package uk.gov.laa.ccms.data.repository.specification; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.util.Objects; +import org.springframework.data.jpa.domain.Specification; +import uk.gov.laa.ccms.data.entity.CaseSearch; + +/** + * Utility class for creating a JPA {@link Specification} to query {@link CaseSearch} entities + * using various filter criteria. + * + *

The primary method {@code filterBy} allows dynamic construction of predicates + * for filtering cases based on optional parameters such as case references, + * provider details, client information, and case statuses.

+ * + *

The resulting specification can be used with Spring Data JPA repositories + * to fetch filtered results from the database.

+ * + * @see Specification + * @see CaseSearch + * @author Jamie Briggs + */ +public final class CaseSearchSpecification { + + /** + * Builds a specification to filter {@link CaseSearch} entities based on + * multiple optional criteria. + * + *

Constructs predicates based on the provided parameters to match corresponding + * fields in the entity.

+ * + * @param providerFirmPartyId the identifier for the provider firm party (mandatory). + * @param caseReferenceNumber the case reference number (matches exact value). + * @param providerCaseReference the provider case reference (matches partially). + * @param caseStatus the case status (matches exact value). + * @param clientSurname the client surname (matches partially and case-insensitive). + * @param feeEarnerId the fee earner identifier (matches exact value). + * @param officeId the provider office identifier (matches exact value). + * @return a {@link Specification} of type {@link CaseSearch} that can be used to filter results. + */ + public static Specification filterBy(final long providerFirmPartyId, + final String caseReferenceNumber, + final String providerCaseReference, final String caseStatus, final String clientSurname, + final Long feeEarnerId, final Long officeId) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { + Predicate predicate = cb.conjunction(); + + predicate = cb.and(predicate, cb.equal(root.get("providerFirmPartyId"), providerFirmPartyId)); + if (isNotEmpty(caseReferenceNumber)) { + predicate = cb.and(predicate, cb.equal(root.get("lscCaseReference"), + caseReferenceNumber)); + } + if (isNotEmpty(providerCaseReference)) { + predicate = cb.and(predicate, cb.like(root.get("providerCaseReference"), + "%" + providerCaseReference + "%")); + } + if (isNotEmpty(caseStatus)) { + predicate = cb.and(predicate, cb.equal(root.get("actualCaseStatus"), caseStatus)); + } + if (isNotEmpty(clientSurname)) { + predicate = cb.and(predicate, + cb.like(cb.lower(root.get("personLastName")), + "%" + clientSurname.toLowerCase() + "%")); + } + if (Objects.nonNull(feeEarnerId)) { + predicate = cb.and(predicate, cb.equal(root.get("feeEarnerPartyId"), feeEarnerId)); + } + if (Objects.nonNull(officeId)) { + predicate = cb.and(predicate, cb.equal(root.get("providerOfficePartyId"), officeId)); + } + + return predicate; + }; + + } +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/ClientDetailSpecification.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/ClientDetailSpecification.java new file mode 100644 index 00000000..8c93cea7 --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/ClientDetailSpecification.java @@ -0,0 +1,95 @@ +package uk.gov.laa.ccms.data.repository.specification; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.time.LocalDate; +import java.util.Objects; +import org.springframework.data.jpa.domain.Specification; +import uk.gov.laa.ccms.data.entity.ClientDetail; + +/** + * Utility class for creating a JPA {@link Specification} to query {@link ClientDetail} entities + * using various filter criteria. + * + *

The primary method {@code filterBy} allows dynamic construction of predicates + * for filtering client details based on optional parameters such as first name, the clients + * surname at birth, and date of birth.

+ * + *

The resulting specification can be used with Spring Data JPA repositories + * to fetch filtered results from the database.

+ * + * @see Specification + * @see ClientDetail + * @author Jamie Briggs + */ +public final class ClientDetailSpecification { + + /** + * Builds a specification to filter {@link ClientDetail} entities based on + * multiple optional criteria. + * + *

Constructs predicates based on the provided parameters to match corresponding + * fields in the entity.

+ * + * + * @param firstName the first name of the client to (matches partially, case-insensitive) + * @param surnameAtBirth the surname at birth of the client to (matches partially, + * case-insensitive) + * @param dateOfBirth the date of birth of the client to filter by + * @param gender the gender of the client to (case-insensitive) + * @param clientReferenceNumber the client reference number to (matches partially, + * case-insensitive) + * @param homeOfficeReference the home office reference to (matches partially, case-insensitive) + * @param nationalInsuranceNumber the national insurance number to (matches partially, + * case-insensitive) + * + * @return a {@link Specification} of type {@link ClientDetail} that can be used to filter + * results. + */ + public static Specification filterBy(final String firstName, + final String surnameAtBirth, + final LocalDate dateOfBirth, final String gender, final String clientReferenceNumber, + final String homeOfficeReference, final String nationalInsuranceNumber) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { + Predicate predicate = cb.conjunction(); + + if (isNotEmpty(firstName)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("firstName")), "%" + firstName.toUpperCase() + "%")); + } + if (isNotEmpty(surnameAtBirth)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("surnameAtBirth")), + "%" + surnameAtBirth.toUpperCase() + "%")); + } + if (Objects.nonNull(dateOfBirth)) { + predicate = cb.and(predicate, cb.equal(root.get("dateOfBirth"), dateOfBirth)); + } + if (isNotEmpty(gender)) { + predicate = cb.and(predicate, + cb.equal(cb.upper(root.get("gender")), gender.toUpperCase())); + } + if (isNotEmpty(clientReferenceNumber)) { + predicate = cb.and(predicate, + cb.like(cb.upper(cb.toString(root.get("clientReferenceNumber"))), + "%" + clientReferenceNumber.toUpperCase() + "%")); + } + if (isNotEmpty(homeOfficeReference)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("homeOfficeNumber")), + "%" + homeOfficeReference.toUpperCase() + "%")); + } + if (isNotEmpty(nationalInsuranceNumber)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("nationalInsuranceNumber")), + "%" + nationalInsuranceNumber.toUpperCase() + "%")); + } + + return predicate; + }; + } +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/NotificationInfoSpecification.java b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/NotificationInfoSpecification.java new file mode 100644 index 00000000..bd1246b7 --- /dev/null +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/repository/specification/NotificationInfoSpecification.java @@ -0,0 +1,105 @@ +package uk.gov.laa.ccms.data.repository.specification; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.time.LocalDate; +import java.util.Objects; +import org.springframework.data.jpa.domain.Specification; +import uk.gov.laa.ccms.data.entity.NotificationInfo; + +/** + * Utility class for creating a JPA {@link Specification} to query {@NotificationInfo} entities + * using various filter criteria. + * + *

The primary method @{code filterBy} allows dynamic construction of predicates for filtering + * notifications based on optional parameters such as case reference number, provider case + * reference and assigned user ID.

+ * + *

The resulting specification can be used with Spring Data JPA repositories to fetch + * filtered results from the database.

+ */ +public final class NotificationInfoSpecification { + + /** + * Constructs a JPA {@link Specification} for filtering {@link NotificationInfo} records based on + * various optional parameters. The resulting specification can be used to dynamically generate + * predicates for querying notifications. + * + * @param providerId a mandatory identifier of the provider firm to filter notifications by + * @param caseReferenceNumber an optional case reference number to filter by (matches partially, + * case-insensitive) + * @param providerCaseReference an optional provider case reference to filter by (matches + * partially, case-insensitive) + * @param assignedToUserId an optional user ID to filter assigned notifications (matches + * partially, case-insensitive) + * @param clientSurname an optional client surname to filter notifications (matches partially, + * case-insensitive) + * @param feeEarnerId an optional fee earner identifier to filter notifications + * @param includeClosed an optional flag to determine whether to include closed notifications; + * if false, only open notifications are included + * @param notificationType an optional notification type identifier to filter by (exact match, + * case-insensitive) + * @param assignedDateFrom an optional start date to filter notifications assigned on or after + * this date + * @param assignedDateTo an optional end date to filter notifications assigned on or before + * this date + * @return a {@link Specification} of {@link NotificationInfo} containing the constructed + * filtering predicates + */ + public static Specification filterBy(final long providerId, + final String caseReferenceNumber, final String providerCaseReference, + final String assignedToUserId, final String clientSurname, final Integer feeEarnerId, + final Boolean includeClosed, final String notificationType, + final LocalDate assignedDateFrom, final LocalDate assignedDateTo) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { + Predicate predicate = cb.conjunction(); + + predicate = cb.and(predicate, cb.equal(root.get("providerFirmId"), providerId)); + if (isNotEmpty(caseReferenceNumber)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("lscCaseRefReference")), + "%" + caseReferenceNumber.toUpperCase() + "%")); + } + if (isNotEmpty(providerCaseReference)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("providerCaseReference")), + "%" + providerCaseReference.toUpperCase() + "%")); + } + if (isNotEmpty(assignedToUserId)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("assignedTo")), + "%" + assignedToUserId.toUpperCase() + "%")); + } + if (isNotEmpty(clientSurname)) { + predicate = cb.and(predicate, + cb.like(cb.upper(root.get("personLastName")), + "%" + clientSurname.toUpperCase() + "%")); + } + if (Objects.nonNull(feeEarnerId)) { + predicate = cb.and(predicate, cb.equal(root.get("feeEarnerPartyId"), feeEarnerId)); + } + if (Boolean.FALSE.equals(includeClosed)) { + predicate = cb.and(predicate, cb.equal(root.get("isOpen"), true)); + } + if (isNotEmpty(notificationType)) { + predicate = cb.and(predicate, + cb.equal(cb.upper(root.get("actionNotificationInd")), + notificationType.toUpperCase())); + } + if (Objects.nonNull(assignedDateFrom)) { + predicate = cb.and(predicate, + cb.greaterThanOrEqualTo(root.get("dateAssigned"), assignedDateFrom)); + } + if (Objects.nonNull(assignedDateTo)) { + predicate = cb.and(predicate, + cb.lessThanOrEqualTo(root.get("dateAssigned"), assignedDateTo)); + } + + return predicate; + }; + } +} diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/service/CaseSearchService.java b/data-service/src/main/java/uk/gov/laa/ccms/data/service/CaseSearchService.java index 1e735eca..e2a45b52 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/service/CaseSearchService.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/service/CaseSearchService.java @@ -9,6 +9,7 @@ import uk.gov.laa.ccms.data.mapper.CaseSearchMapper; import uk.gov.laa.ccms.data.model.CaseDetails; import uk.gov.laa.ccms.data.repository.CaseSearchRepository; +import uk.gov.laa.ccms.data.repository.specification.CaseSearchSpecification; /** * Service for performing search operations on case entities. @@ -40,8 +41,9 @@ public class CaseSearchService { public Optional getCases(long providerFirmPartyId, String caseReferenceNumber, String providerCaseReference, String caseStatus, String clientSurname, Long feeEarnerId, Long officeId, Pageable pageable) { - Page cases = caseSearchRepository.findAll(providerFirmPartyId, caseReferenceNumber, - providerCaseReference, caseStatus, clientSurname, feeEarnerId, officeId, + Page cases = caseSearchRepository.findAll( + CaseSearchSpecification.filterBy(providerFirmPartyId, caseReferenceNumber, + providerCaseReference, caseStatus, clientSurname, feeEarnerId, officeId), pageable); CaseDetails caseDetails = caseSearchMapper.toCaseDetails(cases); return Optional.ofNullable(caseDetails); diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/service/ClientService.java b/data-service/src/main/java/uk/gov/laa/ccms/data/service/ClientService.java index c0cc7ab5..bedf9494 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/service/ClientService.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/service/ClientService.java @@ -1,12 +1,20 @@ package uk.gov.laa.ccms.data.service; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import uk.gov.laa.ccms.data.entity.ClientDetail; +import uk.gov.laa.ccms.data.mapper.ClientDetailsMapper; import uk.gov.laa.ccms.data.mapper.TransactionStatusMapper; +import uk.gov.laa.ccms.data.model.ClientDetails; import uk.gov.laa.ccms.data.model.TransactionStatus; +import uk.gov.laa.ccms.data.repository.ClientDetailRepository; import uk.gov.laa.ccms.data.repository.TransactionStatusRepository; +import uk.gov.laa.ccms.data.repository.specification.ClientDetailSpecification; /** * Service class responsible for handling client-related operations. @@ -23,7 +31,9 @@ @RequiredArgsConstructor public class ClientService { + private final ClientDetailRepository clientDetailRepository; private final TransactionStatusRepository transactionStatusRepository; + private final ClientDetailsMapper clientDetailsMapper; private final TransactionStatusMapper transactionStatusMapper; /** @@ -49,5 +59,31 @@ public Optional getTransactionStatus(String transactionId) .map(transactionStatusMapper::toTransactionStatus); } + /** + * Retrieves client details based on the given filtering criteria and pagination settings. + * + * @param firstName the first name of the client + * @param surname the surname of the client + * @param dateOfBirth the date of birth of the client + * @param gender the gender of the client + * @param clientReferenceNumber the client reference number + * @param homeOfficeReference the home office reference number of the client + * @param nationalInsuranceNumber the national insurance number of the client + * @param pageable the pagination information + * @return an {@code Optional} containing {@link ClientDetails} if found, or an empty + * {@code Optional} if no matching data is found + */ + public Optional getClients(String firstName, String surname, + LocalDate dateOfBirth, String gender, String clientReferenceNumber, + String homeOfficeReference, String nationalInsuranceNumber, Pageable pageable) { + Page pagedClients = clientDetailRepository.findAll( + ClientDetailSpecification.filterBy(firstName, surname, + dateOfBirth, gender, clientReferenceNumber, homeOfficeReference, + nationalInsuranceNumber), pageable); + ClientDetails clientDetails = clientDetailsMapper.mapToClientDetails( + pagedClients); + return Optional.ofNullable(clientDetails); + } + } diff --git a/data-service/src/main/java/uk/gov/laa/ccms/data/service/NotificationService.java b/data-service/src/main/java/uk/gov/laa/ccms/data/service/NotificationService.java index 00504a06..238df973 100644 --- a/data-service/src/main/java/uk/gov/laa/ccms/data/service/NotificationService.java +++ b/data-service/src/main/java/uk/gov/laa/ccms/data/service/NotificationService.java @@ -23,6 +23,7 @@ import uk.gov.laa.ccms.data.repository.NotificationCountRepository; import uk.gov.laa.ccms.data.repository.NotificationRepository; import uk.gov.laa.ccms.data.repository.NotificationSearchRepository; +import uk.gov.laa.ccms.data.repository.specification.NotificationInfoSpecification; /** * Service class responsible for handling notification-related operations. @@ -85,7 +86,8 @@ public Optional getNotifications(final long providerId, String providerCaseReference, String assignedToUserId, String clientSurname, Integer feeEarnerId, boolean includeClosed, String notificationType, LocalDate dateFrom, LocalDate dateTo, Pageable pageable) { - Page byAssignedTo = notificationSearchRepository.findAll(providerId, + Page byAssignedTo = notificationSearchRepository.findAll( + NotificationInfoSpecification.filterBy(providerId, caseReferenceNumber, providerCaseReference, assignedToUserId, @@ -94,7 +96,7 @@ public Optional getNotifications(final long providerId, includeClosed, notificationType, dateFrom, - dateTo, + dateTo), pageable); Notifications notifications = notificationsMapper.mapToNotificationsList(byAssignedTo); return Optional.ofNullable(notifications); diff --git a/data-service/src/main/resources/application-local.yml b/data-service/src/main/resources/application-local.yml index a2793231..dfdfeca9 100644 --- a/data-service/src/main/resources/application-local.yml +++ b/data-service/src/main/resources/application-local.yml @@ -4,6 +4,8 @@ spring: driver-class-name: oracle.jdbc.OracleDriver username: XXCCMS_PUI password: XXCCMS_PUI + hikari: + maximum-pool-size: 20 # Default is 10 jpa: database-platform: org.hibernate.dialect.OracleDialect diff --git a/data-service/src/main/resources/application.yml b/data-service/src/main/resources/application.yml index ad4c9b8b..b5dc5d0c 100644 --- a/data-service/src/main/resources/application.yml +++ b/data-service/src/main/resources/application.yml @@ -10,6 +10,9 @@ spring: database-platform: org.hibernate.dialect.OracleDialect hibernate: ddl-auto: none + threads: + virtual: + enabled: true laa.ccms.springboot.starter.auth: authentication-header: "Authorization" diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/controller/ClientsControllerTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/controller/ClientsControllerTest.java index 376f0ab5..f11cc190 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/controller/ClientsControllerTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/controller/ClientsControllerTest.java @@ -1,5 +1,7 @@ package uk.gov.laa.ccms.data.controller; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -17,11 +19,14 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; +import uk.gov.laa.ccms.data.model.ClientDetails; +import uk.gov.laa.ccms.data.model.ClientSummary; import uk.gov.laa.ccms.data.model.TransactionStatus; import uk.gov.laa.ccms.data.service.ClientService; import uk.gov.laa.ccms.data.service.ClientServiceException; @@ -46,7 +51,9 @@ class ClientsControllerTest { @BeforeEach public void setup(){ - mockMvc = standaloneSetup(clientsController).build(); + mockMvc = standaloneSetup(clientsController) + .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) + .build(); objectMapper = new ObjectMapper(); } @@ -91,4 +98,64 @@ void shouldReturn500Error() throws Exception { } } + + @Nested + @DisplayName("getClients() Tests") + class GetClientsTests{ + + @Test + @DisplayName("Should return data") + void shouldReturnData() throws Exception { + // Given + ClientDetails details = new ClientDetails().addContentItem(new ClientSummary() + .clientReferenceNumber("123")); + doReturn(Optional.of(details)).when(clientService).getClients( + any(), any(), any(), any(), + any(), any(), any(), any()); + + // Then + String jsonContent = objectMapper.writeValueAsString(details); + mockMvc.perform(get("/clients?first-name=first&surname=last&date-of-birth=2010-01-01")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().json(jsonContent)); + } + + @Test + @DisplayName("Should return not found") + void shouldReturnNotFound() throws Exception { + // Then + mockMvc.perform(get("/clients?first-name=first&surname=last&date-of-birth=2010-01-01")) + .andDo(print()) + .andExpect(status().isNotFound()); + } + + + @Test + @DisplayName("Should return bad request when missing first name") + void shouldReturnBadRequestWhenMissingFirstName() throws Exception { + // Then + mockMvc.perform(get("/clients?surname=last&date-of-birth=2010-01-01")) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Should return bad request when missing surname") + void shouldReturnBadRequestWhenMissingSurname() throws Exception { + // Then + mockMvc.perform(get("/clients?first-name=first&date-of-birth=2010-01-01")) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Should return bad request when missing date of birth") + void shouldReturnBadRequestWhenMissingDateOfBirth() throws Exception { + // Then + mockMvc.perform(get("/clients?first-name=firstsurname=last")) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + } } diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapperImplTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapperImplTest.java new file mode 100644 index 00000000..f25689c5 --- /dev/null +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/ClientDetailsMapperImplTest.java @@ -0,0 +1,114 @@ +package uk.gov.laa.ccms.data.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.LocalDate; +import java.util.Arrays; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import uk.gov.laa.ccms.data.entity.ClientDetail; +import uk.gov.laa.ccms.data.model.ClientDetails; +import uk.gov.laa.ccms.data.model.ClientSummary; + +@DisplayName("Client details mapper implementation test") +class ClientDetailsMapperImplTest { + + ClientDetailsMapper mapper = new ClientDetailsMapperImpl(); + + @Test + @DisplayName("Should map empty client summary") + void shouldMapEmptyClientSummary(){ + // Given + ClientDetail entity = new ClientDetail(); + // When + ClientSummary result = mapper.mapToClientSummary(entity); + // Then + assertNotNull(result); + assertEquals("", result.getPostalCode()); + } + + @Test + @DisplayName("Should map client summary") + void shouldMapToClientSummary(){ + // Given + ClientDetail entity = getTestClientDetail(); + // When + ClientSummary result = mapper.mapToClientSummary(entity); + // Then + assertEquals("Surname At Birth", result.getSurnameAtBirth()); + assertEquals("First Surname", result.getFullName()); + assertEquals(LocalDate.of(2000, 1, 1), result.getDateOfBirth()); + assertEquals("Gender", result.getGender()); + assertEquals("Home Office Number", result.getHomeOfficeReference()); + assertEquals("First", result.getFirstName()); + assertEquals("Surname", result.getSurname()); + } + + @Test + @DisplayName("Should map postcode from address") + void shouldMapPostcodeFromAddress(){ + // Given + ClientDetail entity = getTestClientDetail(); + // When + ClientSummary result = mapper.mapToClientSummary(entity); + // Then + // Then + assertEquals("TS23 1JH", result.getPostalCode()); + } + + @Test + @DisplayName("Should map multiple clients") + void shouldMapMultipleClients(){ + // Given + ClientDetail entity = getTestClientDetail(); + ClientDetail entityTwo = getTestClientDetail(); + Page page = new PageImpl<>(Arrays.asList(entity, entityTwo)); + // When + ClientDetails result = mapper.mapToClientDetails(page); + // Then + assertEquals(2, result.getSize()); + } + + @Test + @DisplayName("Should map pageable properties") + void shouldMapPageableProperties(){ + // Given + ClientDetail entity = getTestClientDetail(); + ClientDetail entityTwo = getTestClientDetail(); + Pageable pageable = Pageable.ofSize(2).withPage(5); + Page page = new PageImpl<>(Arrays.asList(entity, entityTwo), pageable, 20); + // When + ClientDetails result = mapper.mapToClientDetails(page); + // Then + assertEquals(2, result.getSize()); + assertEquals(5, result.getNumber()); + assertEquals(20, result.getTotalElements()); + assertEquals(10, result.getTotalPages()); + } + + private static ClientDetail getTestClientDetail() { + return ClientDetail.builder() + .clientReferenceNumber(123L) + .title("Title") + .firstName("First") + .surname("Surname") + .surnameAtBirth("Surname At Birth") + .dateOfBirth(LocalDate.of(2000, 1, 1)) + .gender("Gender") + .countryOfOrigin("Country") + .maritalStatus("Marital Status") + .correspondenceMethod("Method") + .disabilityType("Disability Type") + .correspondenceLanguage("Language") + .ethnicMonitoring("Monitoring") + .noFixAbode("false") + .nationalInsuranceNumber("NINO") + .homeOfficeNumber("Home Office Number") + .address("
527312626, Grange AvenueBILLINGHAMGBRTS23 1JH
") + .build(); + } +} diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationMapperImplTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationMapperImplTest.java index 16f24154..5a30e1b7 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationMapperImplTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationMapperImplTest.java @@ -131,6 +131,7 @@ void shouldMapDocuments(){ assertEquals("Status", resultTwo.getStatus()); } + // TODO Come back to this one @Test @DisplayName("Should map two attachments") void shouldMapAttachments(){ diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationsMapperImplTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationsMapperImplTest.java index 6e728a33..88c4d9e5 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationsMapperImplTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/mapper/NotificationsMapperImplTest.java @@ -76,7 +76,6 @@ void shouldMapSingleNotification(){ assertEquals(true, notificationResult.getNotificationOpenIndicator()); assertEquals("Provider Case Ref",notificationResult.getProviderCaseReferenceNumber()); assertEquals("LSC Case Ref", notificationResult.getCaseReferenceNumber()); - assertEquals(true, notificationResult.getEvidenceAllowed()); } } diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/service/CaseSearchServiceTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/service/CaseSearchServiceTest.java index 58cccc97..15173f54 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/service/CaseSearchServiceTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/service/CaseSearchServiceTest.java @@ -41,12 +41,7 @@ void setup(){ void shouldReturnCaseSummaryObject(){ // Given new CaseSearch(); - when(caseSearchRepository.findAll(anyLong(), - any(), - any(), - any(), - any(), - any(), + when(caseSearchRepository.findAll( any(), any(Pageable.class))).thenReturn(new PageImpl<>(List.of( CaseSearch.builder().lscCaseReference("123").build()))); diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/service/ClientServiceTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/service/ClientServiceTest.java index 098ee43f..7f984adc 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/service/ClientServiceTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/service/ClientServiceTest.java @@ -4,130 +4,180 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.LocalDate; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import uk.gov.laa.ccms.data.entity.ClientDetail; +import uk.gov.laa.ccms.data.mapper.ClientDetailsMapper; import uk.gov.laa.ccms.data.mapper.TransactionStatusMapper; +import uk.gov.laa.ccms.data.model.ClientDetails; import uk.gov.laa.ccms.data.model.TransactionStatus; +import uk.gov.laa.ccms.data.repository.ClientDetailRepository; import uk.gov.laa.ccms.data.repository.TransactionStatusRepository; @ExtendWith(MockitoExtension.class) @DisplayName("Client Service Test") class ClientServiceTest { + @Mock + TransactionStatusRepository transactionStatusRepository; + @Mock + ClientDetailRepository clientDetailRepository; @Mock TransactionStatusMapper transactionStatusMapper; @Mock - TransactionStatusRepository transactionStatusRepository; + ClientDetailsMapper clientDetailsMapper; ClientService clientService; @BeforeEach void beforeEach() { clientService = - new ClientService(transactionStatusRepository, transactionStatusMapper); + new ClientService(clientDetailRepository, transactionStatusRepository, + clientDetailsMapper, transactionStatusMapper); } - @Test - @DisplayName("Should return empty client transaction status") - void shouldReturnEmptyClientTransactionStatus() { - // Given - String transactionId = "123"; - // When - Optional transactionStatus = clientService.getTransactionStatus( - transactionId); - // Then - assertTrue(transactionStatus.isEmpty()); - } + @Nested + @DisplayName("GetTransactionStatus() Tests") + class GetTransactionStatusTest { - @Test - @DisplayName("Should return client transaction status") - void shouldReturnClientTransactionStatus() { - // Given - String transactionId = "123"; - uk.gov.laa.ccms.data.entity.TransactionStatus entity = - uk.gov.laa.ccms.data.entity.TransactionStatus.builder() - .requestId(transactionId) - .processName("CreateClient").status("Success").build(); - when(transactionStatusRepository.findClientTransactionByTransactionId(transactionId)) - .thenReturn(Optional.of(entity)); - TransactionStatus result = new TransactionStatus().submissionStatus("Success") - .referenceNumber("123"); - when(transactionStatusMapper.toTransactionStatus(entity)).thenReturn( - result); - // When - Optional transactionStatus = clientService.getTransactionStatus( - transactionId); - // Then - verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( - transactionId); - verify(transactionStatusRepository, times(1)).findClientTransactionByTransactionId( - transactionId); - assertTrue(transactionStatus.isPresent()); - assertEquals(result, transactionStatus.get()); - } - @Test - @DisplayName("Should return client transaction status even with success transaction") - void shouldReturnClientTransactionStatusEvenWithSuccessTransactions() { - // Given - String transactionId = "123"; - uk.gov.laa.ccms.data.entity.TransactionStatus successTransaction = - uk.gov.laa.ccms.data.entity.TransactionStatus.builder() - .requestId(transactionId) - .processName("XXCCMS_COMMON_UTIL.USER_FUNC_AUTH").status("Success").build(); - when(transactionStatusRepository.findAllUserFunctionTransactionsByTransactionId(transactionId)) - .thenReturn(singletonList(successTransaction)); - uk.gov.laa.ccms.data.entity.TransactionStatus entity = - uk.gov.laa.ccms.data.entity.TransactionStatus.builder() - .requestId(transactionId) - .processName("CreateClient").status("Success").build(); - when(transactionStatusRepository.findClientTransactionByTransactionId(transactionId)) - .thenReturn(Optional.of(entity)); - TransactionStatus result = new TransactionStatus().submissionStatus("Success") - .referenceNumber("123"); - when(transactionStatusMapper.toTransactionStatus(entity)).thenReturn( - result); - - // When - Optional transactionStatus = clientService.getTransactionStatus( - transactionId); - - // Then - verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( - transactionId); - verify(transactionStatusRepository, times(1)).findClientTransactionByTransactionId( - transactionId); - assertTrue(transactionStatus.isPresent()); - assertEquals(result, transactionStatus.get()); - } + @Test + @DisplayName("Should return empty client transaction status") + void shouldReturnEmptyClientTransactionStatus() { + // Given + String transactionId = "123"; + // When + Optional transactionStatus = clientService.getTransactionStatus( + transactionId); + // Then + assertTrue(transactionStatus.isEmpty()); + } + + @Test + @DisplayName("Should return client transaction status") + void shouldReturnClientTransactionStatus() { + // Given + String transactionId = "123"; + uk.gov.laa.ccms.data.entity.TransactionStatus entity = + uk.gov.laa.ccms.data.entity.TransactionStatus.builder() + .requestId(transactionId) + .processName("CreateClient").status("Success").build(); + when(transactionStatusRepository.findClientTransactionByTransactionId(transactionId)) + .thenReturn(Optional.of(entity)); + TransactionStatus result = new TransactionStatus().submissionStatus("Success") + .referenceNumber("123"); + when(transactionStatusMapper.toTransactionStatus(entity)).thenReturn( + result); + // When + Optional transactionStatus = clientService.getTransactionStatus( + transactionId); + // Then + verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( + transactionId); + verify(transactionStatusRepository, times(1)).findClientTransactionByTransactionId( + transactionId); + assertTrue(transactionStatus.isPresent()); + assertEquals(result, transactionStatus.get()); + } - @Test - @DisplayName("Should throw exception when user function transaction is error") - void shouldThrowExceptionWhenUserFunctionTransactionIsError() { - // Given - String transactionId = "123"; - uk.gov.laa.ccms.data.entity.TransactionStatus successTransaction = - uk.gov.laa.ccms.data.entity.TransactionStatus.builder() - .requestId(transactionId) - .processName("XXCCMS_COMMON_UTIL.USER_FUNC_AUTH").status("Error").build(); - when(transactionStatusRepository.findAllUserFunctionTransactionsByTransactionId(transactionId)) - .thenReturn(singletonList(successTransaction)); - - // When & Then - assertThrows(ClientServiceException.class, () -> clientService.getTransactionStatus(transactionId)); - verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( - transactionId); - verify(transactionStatusRepository, times(0)).findClientTransactionByTransactionId( - transactionId); + @Test + @DisplayName("Should return client transaction status even with success transaction") + void shouldReturnClientTransactionStatusEvenWithSuccessTransactions() { + // Given + String transactionId = "123"; + uk.gov.laa.ccms.data.entity.TransactionStatus successTransaction = + uk.gov.laa.ccms.data.entity.TransactionStatus.builder() + .requestId(transactionId) + .processName("XXCCMS_COMMON_UTIL.USER_FUNC_AUTH").status("Success").build(); + when( + transactionStatusRepository.findAllUserFunctionTransactionsByTransactionId(transactionId)) + .thenReturn(singletonList(successTransaction)); + uk.gov.laa.ccms.data.entity.TransactionStatus entity = + uk.gov.laa.ccms.data.entity.TransactionStatus.builder() + .requestId(transactionId) + .processName("CreateClient").status("Success").build(); + when(transactionStatusRepository.findClientTransactionByTransactionId(transactionId)) + .thenReturn(Optional.of(entity)); + TransactionStatus result = new TransactionStatus().submissionStatus("Success") + .referenceNumber("123"); + when(transactionStatusMapper.toTransactionStatus(entity)).thenReturn( + result); + + // When + Optional transactionStatus = clientService.getTransactionStatus( + transactionId); + + // Then + verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( + transactionId); + verify(transactionStatusRepository, times(1)).findClientTransactionByTransactionId( + transactionId); + assertTrue(transactionStatus.isPresent()); + assertEquals(result, transactionStatus.get()); + } + + @Test + @DisplayName("Should throw exception when user function transaction is error") + void shouldThrowExceptionWhenUserFunctionTransactionIsError() { + // Given + String transactionId = "123"; + uk.gov.laa.ccms.data.entity.TransactionStatus successTransaction = + uk.gov.laa.ccms.data.entity.TransactionStatus.builder() + .requestId(transactionId) + .processName("XXCCMS_COMMON_UTIL.USER_FUNC_AUTH").status("Error").build(); + when( + transactionStatusRepository.findAllUserFunctionTransactionsByTransactionId(transactionId)) + .thenReturn(singletonList(successTransaction)); + + // When & Then + assertThrows(ClientServiceException.class, + () -> clientService.getTransactionStatus(transactionId)); + verify(transactionStatusRepository, times(1)).findAllUserFunctionTransactionsByTransactionId( + transactionId); + verify(transactionStatusRepository, times(0)).findClientTransactionByTransactionId( + transactionId); + } } -} \ No newline at end of file + @Nested + @DisplayName("GetClientDetails() Tests") + class GetClientDetailsTest { + + @Test + @DisplayName("Should return client details") + void shouldReturnClientDetails(){ + // Given + ClientDetail clientDetail = new ClientDetail(); + PageImpl pagedResult = new PageImpl<>(singletonList( + clientDetail + )); + when(clientDetailRepository.findAll(any(), any())).thenReturn(pagedResult); + when(clientDetailsMapper.mapToClientDetails(pagedResult)).thenReturn(new ClientDetails() + .size(1).totalPages(20)); + // When + Optional result = clientService.getClients("First", "Last", + LocalDate.of(2000, 1, 1), "", "", "", + "", Pageable.unpaged()); + // Then + assertTrue(result.isPresent()); + assertEquals(1, result.get().getSize()); + assertEquals(20, result.get().getTotalPages()); + } + + } +} diff --git a/data-service/src/test/java/uk/gov/laa/ccms/data/service/NotificationServiceTest.java b/data-service/src/test/java/uk/gov/laa/ccms/data/service/NotificationServiceTest.java index c7e230a6..5fec2cf6 100644 --- a/data-service/src/test/java/uk/gov/laa/ccms/data/service/NotificationServiceTest.java +++ b/data-service/src/test/java/uk/gov/laa/ccms/data/service/NotificationServiceTest.java @@ -113,8 +113,7 @@ void getNotifications_returnsData() { // Given PageImpl repositoryResult = new PageImpl<>( Collections.singletonList(new NotificationInfo())); - when(notificationSearchRepository.findAll(eq(10L), any(), any(), any(), any(), any(), any(), - any(), any(), any(), any(Pageable.class))) + when(notificationSearchRepository.findAll(any(), any(Pageable.class))) .thenReturn( repositoryResult); Notifications expected = new Notifications().size(1);