Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hia 623 person search by pnc #358

Merged
merged 3 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Person?> {
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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ class ProbationOffenderSearchGateway(@Value("\${services.probation-offender-sear
}
}

fun getPersons(firstName: String?, surname: String?, searchWithinAliases: Boolean = false): Response<List<Person>> {
val requestBody = mapOf("firstName" to firstName, "surname" to surname, "includeAliases" to searchWithinAliases)
fun getPersons(firstName: String?, surname: String?, pncNumber: String?, searchWithinAliases: Boolean = false): Response<List<Person>> {
val requestBody = mapOf("firstName" to firstName, "surname" to surname, "pncNumber" to pncNumber, "includeAliases" to searchWithinAliases)
.filterValues { it != null }

val result = webClient.requestList<Offender>(HttpMethod.POST, "/search", authenticationHeader(), UpstreamApi.PROBATION_OFFENDER_SEARCH, requestBody)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ class GetPersonsService(
@Autowired val probationOffenderSearchGateway: ProbationOffenderSearchGateway,
) {

fun execute(firstName: String?, lastName: String?, searchWithinAliases: Boolean = false): Response<List<Person>> {
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<List<Person>> {
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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(
Expand All @@ -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") {
Expand Down Expand Up @@ -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 ->
Expand All @@ -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(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class PrisonerOffenderSearchGatewayTest(
describe("#getPersons") {
val firstName = "JAMES"
val lastName = "HOWLETT"
val hmppsId = "2003/13116A"
val hmppsId = "B9731BB"

beforeEach {
prisonerOffenderSearchApiMockServer.stubPostPrisonerSearch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ class ProbationOffenderSearchGatewayTest(
describe("#getPersons") {
val firstName = "Matt"
val surname = "Nolan"
val pncNumber = "2018/0123456X"

beforeEach {
probationOffenderSearchApiMockServer.stubPostOffenderSearch(
"""
{
"firstName": "$firstName",
"surname": "$surname",
"pncNumber": "$pncNumber",
"includeAliases": false
}
""".removeWhitespaceAndNewlines(),
Expand All @@ -63,19 +65,47 @@ 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)
response.data.first().lastName.shouldBe(surname)
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(
"""
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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())
}
Expand Down
Loading