diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonController.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonController.kt index 9f97924eb..fad9557fe 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonController.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonController.kt @@ -32,19 +32,20 @@ class PersonController( @GetMapping fun getPersons( + @RequestParam(required = false, name = "pnc_number") pncNumber: String?, @RequestParam(required = false, name = "first_name") firstName: String?, @RequestParam(required = false, name = "last_name") lastName: String?, @RequestParam(required = false, defaultValue = "false", name = "search_within_aliases") searchWithinAliases: Boolean, @RequestParam(required = false, defaultValue = "1", name = "page") page: Int, @RequestParam(required = false, defaultValue = "10", name = "perPage") perPage: Int, ): PaginatedResponse { - if (firstName == null && lastName == null) { + if (firstName == null && lastName == null && pncNumber == null) { throw ValidationException("No query parameters specified.") } - val response = getPersonsService.execute(firstName, lastName, searchWithinAliases) + val response = getPersonsService.execute(firstName, lastName, pncNumber, searchWithinAliases) - auditService.createEvent("SEARCH_PERSON", "Person searched with first name: $firstName, last name: $lastName and search within aliases: $searchWithinAliases") + auditService.createEvent("SEARCH_PERSON", "Person searched with first name: $firstName, last name: $lastName, search within aliases: $searchWithinAliases, pnc number: $pncNumber") return response.data.paginateWith(page, perPage) } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/ProbationOffenderSearchGateway.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/ProbationOffenderSearchGateway.kt index 97a3ea3da..7cdce5747 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/ProbationOffenderSearchGateway.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/ProbationOffenderSearchGateway.kt @@ -63,8 +63,8 @@ class ProbationOffenderSearchGateway(@Value("\${services.probation-offender-sear } } - fun getPersons(firstName: String?, surname: String?, searchWithinAliases: Boolean = false): Response> { - val requestBody = mapOf("firstName" to firstName, "surname" to surname, "includeAliases" to searchWithinAliases) + fun getPersons(firstName: String?, surname: String?, pncNumber: String?, searchWithinAliases: Boolean = false): Response> { + val requestBody = mapOf("firstName" to firstName, "surname" to surname, "pncNumber" to pncNumber, "includeAliases" to searchWithinAliases) .filterValues { it != null } val result = webClient.requestList(HttpMethod.POST, "/search", authenticationHeader(), UpstreamApi.PROBATION_OFFENDER_SEARCH, requestBody) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsService.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsService.kt index 45367d02a..66763919a 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsService.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsService.kt @@ -13,10 +13,16 @@ class GetPersonsService( @Autowired val probationOffenderSearchGateway: ProbationOffenderSearchGateway, ) { - fun execute(firstName: String?, lastName: String?, searchWithinAliases: Boolean = false): Response> { - val responseFromPrisonerOffenderSearch = prisonerOffenderSearchGateway.getPersons(firstName, lastName, searchWithinAliases = searchWithinAliases) - val personsFromProbationOffenderSearch = probationOffenderSearchGateway.getPersons(firstName, lastName, searchWithinAliases = searchWithinAliases) + fun execute(firstName: String?, lastName: String?, pncNumber: String?, searchWithinAliases: Boolean = false): Response> { + var hmppsId: String? = null + val personsFromProbationOffenderSearch = probationOffenderSearchGateway.getPersons(firstName, lastName, pncNumber, searchWithinAliases) + + if (pncNumber != null) { + hmppsId = personsFromProbationOffenderSearch.data.firstOrNull()?.identifiers?.deliusCrn + } + + val responseFromPrisonerOffenderSearch = prisonerOffenderSearchGateway.getPersons(firstName, lastName, hmppsId, searchWithinAliases) return Response(data = responseFromPrisonerOffenderSearch.data + personsFromProbationOffenderSearch.data) } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonControllerTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonControllerTest.kt index e9968a001..337b4a25d 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonControllerTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonControllerTest.kt @@ -43,6 +43,7 @@ internal class PersonControllerTest( ) : DescribeSpec( { val hmppsId = "2003/13116M" + val pncNumber = "2003/13116M" val encodedHmppsId = URLEncoder.encode(hmppsId, StandardCharsets.UTF_8) val basePath = "/v1/persons" val firstName = "Barry" @@ -53,7 +54,7 @@ internal class PersonControllerTest( beforeTest { Mockito.reset(getPersonsService) Mockito.reset(auditService) - whenever(getPersonsService.execute(firstName, lastName)).thenReturn( + whenever(getPersonsService.execute(firstName, lastName, null)).thenReturn( Response( data = listOf( @@ -75,33 +76,39 @@ internal class PersonControllerTest( } it("gets a person with matching search criteria") { - mockMvc.performAuthorised("$basePath?first_name=$firstName&last_name=$lastName") + mockMvc.performAuthorised("$basePath?first_name=$firstName&last_name=$lastName&pnc_number=$pncNumber") - verify(getPersonsService, times(1)).execute(firstName, lastName) + verify(getPersonsService, times(1)).execute(firstName, lastName, pncNumber) } it("gets a person with matching first name") { mockMvc.performAuthorised("$basePath?first_name=$firstName") - verify(getPersonsService, times(1)).execute(firstName, null) + verify(getPersonsService, times(1)).execute(firstName, null, null) } it("gets a person with matching last name") { mockMvc.performAuthorised("$basePath?last_name=$lastName") - verify(getPersonsService, times(1)).execute(null, lastName) + verify(getPersonsService, times(1)).execute(null, lastName, null) } it("gets a person with matching alias") { mockMvc.performAuthorised("$basePath?first_name=$firstName&search_within_aliases=true") - verify(getPersonsService, times(1)).execute(firstName, null, searchWithinAliases = true) + verify(getPersonsService, times(1)).execute(firstName, null, null, searchWithinAliases = true) + } + + it("gets a person with matching pncNumber") { + mockMvc.performAuthorised("$basePath?pnc_number=$pncNumber") + + verify(getPersonsService, times(1)).execute(null, null, pncNumber) } it("defaults to not searching within aliases") { mockMvc.performAuthorised("$basePath?first_name=$firstName") - verify(getPersonsService, times(1)).execute(firstName, null, searchWithinAliases = false) + verify(getPersonsService, times(1)).execute(firstName, null, null) } it("returns a person with matching first and last name") { @@ -147,12 +154,12 @@ internal class PersonControllerTest( } it("logs audit") { - mockMvc.performAuthorised("$basePath?first_name=$firstName&last_name=$lastName") - verify(auditService, times(1)).createEvent("SEARCH_PERSON", "Person searched with first name: $firstName, last name: $lastName and search within aliases: false") + mockMvc.performAuthorised("$basePath?first_name=$firstName&last_name=$lastName&pnc_number=$pncNumber") + verify(auditService, times(1)).createEvent("SEARCH_PERSON", "Person searched with first name: $firstName, last name: $lastName, search within aliases: false, pnc number: $pncNumber") } it("returns paginated results") { - whenever(getPersonsService.execute(firstName, lastName)).thenReturn( + whenever(getPersonsService.execute(firstName, lastName, null)).thenReturn( Response( data = List(20) { i -> @@ -175,7 +182,7 @@ internal class PersonControllerTest( val firstNameThatDoesNotExist = "Bob21345" val lastNameThatDoesNotExist = "Gun36773" - whenever(getPersonsService.execute(firstNameThatDoesNotExist, lastNameThatDoesNotExist)).thenReturn( + whenever(getPersonsService.execute(firstNameThatDoesNotExist, lastNameThatDoesNotExist, null)).thenReturn( Response( data = emptyList(), ), diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/prisoneroffendersearch/PrisonerOffenderSearchGatewayTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/prisoneroffendersearch/PrisonerOffenderSearchGatewayTest.kt index 60de7cfa9..178961245 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/prisoneroffendersearch/PrisonerOffenderSearchGatewayTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/prisoneroffendersearch/PrisonerOffenderSearchGatewayTest.kt @@ -44,7 +44,7 @@ class PrisonerOffenderSearchGatewayTest( describe("#getPersons") { val firstName = "JAMES" val lastName = "HOWLETT" - val hmppsId = "2003/13116A" + val hmppsId = "B9731BB" beforeEach { prisonerOffenderSearchApiMockServer.stubPostPrisonerSearch( diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/probationoffendersearch/ProbationOffenderSearchGatewayTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/probationoffendersearch/ProbationOffenderSearchGatewayTest.kt index c5534ddf3..930331db4 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/probationoffendersearch/ProbationOffenderSearchGatewayTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/probationoffendersearch/ProbationOffenderSearchGatewayTest.kt @@ -48,6 +48,7 @@ class ProbationOffenderSearchGatewayTest( describe("#getPersons") { val firstName = "Matt" val surname = "Nolan" + val pncNumber = "2018/0123456X" beforeEach { probationOffenderSearchApiMockServer.stubPostOffenderSearch( @@ -55,6 +56,7 @@ class ProbationOffenderSearchGatewayTest( { "firstName": "$firstName", "surname": "$surname", + "pncNumber": "$pncNumber", "includeAliases": false } """.removeWhitespaceAndNewlines(), @@ -63,12 +65,12 @@ class ProbationOffenderSearchGatewayTest( } it("authenticates using HMPPS Auth with credentials") { - probationOffenderSearchGateway.getPersons(firstName, surname) + probationOffenderSearchGateway.getPersons(firstName, surname, pncNumber) verify(hmppsAuthGateway, VerificationModeFactory.times(1)).getClientToken("Probation Offender Search") } - it("returns person(s) when searching on first and last name") { - val response = probationOffenderSearchGateway.getPersons(firstName, surname) + it("returns person(s) when searching on first name, last name and pnc number") { + val response = probationOffenderSearchGateway.getPersons(firstName, surname, pncNumber) response.data.count().shouldBe(1) response.data.first().firstName.shouldBe(firstName) @@ -76,6 +78,34 @@ class ProbationOffenderSearchGatewayTest( response.data.first().pncId.shouldBe("2018/0123456X") } + it("returns person(s) when searching on first name and last name") { + probationOffenderSearchApiMockServer.stubPostOffenderSearch( + """ + { + "firstName": "Ahsoka", + "surname": "Tano", + "includeAliases": false + } + """.removeWhitespaceAndNewlines(), + """ + [ + { + "firstName": "Ahsoka", + "surname": "Tano" + } + ] + """.trimIndent(), + ) + + val response = probationOffenderSearchGateway.getPersons("Ahsoka", "Tano", null) + + println(response) + + response.data.count().shouldBe(1) + response.data.first().firstName.shouldBe("Ahsoka") + response.data.first().lastName.shouldBe("Tano") + } + it("returns person(s) when searching on first name only") { probationOffenderSearchApiMockServer.stubPostOffenderSearch( """ @@ -94,7 +124,7 @@ class ProbationOffenderSearchGatewayTest( """.trimIndent(), ) - val response = probationOffenderSearchGateway.getPersons("Ahsoka", null) + val response = probationOffenderSearchGateway.getPersons("Ahsoka", null, null) response.data.count().shouldBe(1) response.data.first().firstName.shouldBe("Ahsoka") @@ -118,7 +148,32 @@ class ProbationOffenderSearchGatewayTest( ] """.trimIndent(), ) - val response = probationOffenderSearchGateway.getPersons(null, "Tano") + val response = probationOffenderSearchGateway.getPersons(null, "Tano", null) + + response.data.count().shouldBe(1) + response.data.first().firstName.shouldBe("Ahsoka") + response.data.first().lastName.shouldBe("Tano") + } + + it("returns person(s) when searching on pnc number only") { + probationOffenderSearchApiMockServer.stubPostOffenderSearch( + """ + { + "pncNumber": "2018/0123456X", + "includeAliases": false + } + """.removeWhitespaceAndNewlines(), + """ + [ + { + "firstName": "Ahsoka", + "surname": "Tano" + } + ] + """.trimIndent(), + ) + + val response = probationOffenderSearchGateway.getPersons(null, null, pncNumber) response.data.count().shouldBe(1) response.data.first().firstName.shouldBe("Ahsoka") @@ -149,7 +204,7 @@ class ProbationOffenderSearchGatewayTest( """.trimIndent(), ) - val response = probationOffenderSearchGateway.getPersons("Fulcrum", null, searchWithinAliases = true) + val response = probationOffenderSearchGateway.getPersons("Fulcrum", null, null, searchWithinAliases = true) response.data.count().shouldBe(1) response.data.first().aliases.first().firstName.shouldBe("Fulcrum") diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsServiceTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsServiceTest.kt index 24fb9cae0..06a9ee612 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsServiceTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetPersonsServiceTest.kt @@ -11,6 +11,7 @@ import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.ContextConfiguration import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.PrisonerOffenderSearchGateway import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.ProbationOffenderSearchGateway +import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Identifiers import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Person import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response @@ -25,61 +26,84 @@ internal class GetPersonsServiceTest( ) : DescribeSpec({ val firstName = "Bruce" val lastName = "Wayne" + val pncNumber = "2003/13116M" beforeEach { Mockito.reset(prisonerOffenderSearchGateway) Mockito.reset(probationOffenderSearchGateway) - whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(Response(data = emptyList())) - whenever(probationOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(Response(data = emptyList())) + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, null)).thenReturn(Response(data = emptyList())) + whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, null, false)).thenReturn(Response(data = emptyList())) } it("gets person(s) from Prisoner Offender Search") { - getPersonsService.execute(firstName, lastName) + getPersonsService.execute(firstName, lastName, null) verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName) } it("gets person(s) from Probation Offender Search") { - getPersonsService.execute(firstName, lastName) + getPersonsService.execute(firstName, lastName, null) - verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName) + verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null) } it("defaults to not searching within aliases") { - getPersonsService.execute(firstName, lastName) + getPersonsService.execute(firstName, lastName, null) - verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, searchWithinAliases = false) - verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, searchWithinAliases = false) + verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null, searchWithinAliases = false) + verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null, searchWithinAliases = false) } it("allows searching within aliases") { - whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, searchWithinAliases = true)).thenReturn(Response(data = emptyList())) - whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, searchWithinAliases = true)).thenReturn(Response(data = emptyList())) + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, null, searchWithinAliases = true)).thenReturn(Response(data = emptyList())) + whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, null, searchWithinAliases = true)).thenReturn(Response(data = emptyList())) - getPersonsService.execute(firstName, lastName, searchWithinAliases = true) + getPersonsService.execute(firstName, lastName, null, true) - verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, searchWithinAliases = true) - verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, searchWithinAliases = true) + verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null, true) + verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null, searchWithinAliases = true) + } + + it("allows prisonerOffenderSearchGateway to search with a hmppsId if a pncNumber is passed in") { + val responseFromProbationOffenderSearch = Response(data = listOf(Person(firstName, lastName, middleName = "John", identifiers = Identifiers(deliusCrn = "A1234AA")))) + + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, pncNumber)).thenReturn(responseFromProbationOffenderSearch) + whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, "A1234AA")).thenReturn(Response(data = emptyList())) + + getPersonsService.execute(firstName, lastName, pncNumber) + + verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, pncNumber) + verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, "A1234AA") + } + + it("allows prisonerOffenderSearchGateway to not search with a hmppsId if a pncNumber is not passed in") { + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, null)).thenReturn(Response(data = emptyList())) + whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, null)).thenReturn(Response(data = emptyList())) + + getPersonsService.execute(firstName, lastName, null) + + verify(probationOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null) + verify(prisonerOffenderSearchGateway, times(1)).getPersons(firstName, lastName, null) } it("returns person(s)") { - val responseFromPrisonerOffenderSearch = Response(data = listOf(Person(firstName, lastName, middleName = "Gary"))) val responseFromProbationOffenderSearch = Response(data = listOf(Person(firstName, lastName, middleName = "John"))) + val responseFromPrisonerOffenderSearch = Response(data = listOf(Person(firstName, lastName, middleName = "Gary"))) - whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(responseFromPrisonerOffenderSearch) - whenever(probationOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(responseFromProbationOffenderSearch) + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, pncNumber)).thenReturn(responseFromProbationOffenderSearch) + whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName, null)).thenReturn(responseFromPrisonerOffenderSearch) - val response = getPersonsService.execute(firstName, lastName) + val response = getPersonsService.execute(firstName, lastName, pncNumber) response.data.shouldBe(responseFromPrisonerOffenderSearch.data + responseFromProbationOffenderSearch.data) } it("returns an empty list when no person(s) are found") { + whenever(probationOffenderSearchGateway.getPersons(firstName, lastName, null)).thenReturn(Response(emptyList())) whenever(prisonerOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(Response(emptyList())) - whenever(probationOffenderSearchGateway.getPersons(firstName, lastName)).thenReturn(Response(emptyList())) - val response = getPersonsService.execute(firstName, lastName) + val response = getPersonsService.execute(firstName, lastName, null) response.data.shouldBe(emptyList()) }