Skip to content

Commit e980a36

Browse files
authored
HMAI-307 Add Prisons filter to /v1/persons/{hmppsId}/protected-characteristics so can be used by kilco (#745)
* remove encodedhmppsid from controller and add filters * add filtering to service layer * add /v1/persons/.*/protected-characteristics to private prison role
1 parent ae2e3be commit e980a36

File tree

6 files changed

+161
-32
lines changed

6 files changed

+161
-32
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/ProtectedCharacteristicsController.kt

+13-6
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import io.swagger.v3.oas.annotations.media.Content
66
import io.swagger.v3.oas.annotations.media.Schema
77
import io.swagger.v3.oas.annotations.responses.ApiResponse
88
import io.swagger.v3.oas.annotations.tags.Tag
9+
import jakarta.validation.ValidationException
910
import org.springframework.beans.factory.annotation.Autowired
1011
import org.springframework.web.bind.annotation.GetMapping
1112
import org.springframework.web.bind.annotation.PathVariable
13+
import org.springframework.web.bind.annotation.RequestAttribute
1214
import org.springframework.web.bind.annotation.RequestMapping
1315
import org.springframework.web.bind.annotation.RestController
1416
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
15-
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.decodeUrlCharacters
1617
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.DataResponse
1718
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonProtectedCharacteristics
1819
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
20+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.roleconfig.ConsumerFilters
1921
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetProtectedCharacteristicsService
2022
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
2123

@@ -26,24 +28,29 @@ class ProtectedCharacteristicsController(
2628
@Autowired val getProtectedCharacteristicsService: GetProtectedCharacteristicsService,
2729
@Autowired val auditService: AuditService,
2830
) {
29-
@GetMapping("{encodedHmppsId}/protected-characteristics")
31+
@GetMapping("{hmppsId}/protected-characteristics")
3032
@Operation(
3133
summary = "Returns protected characteristics of a person.",
34+
description = "<b>Applicable filters</b>: <ul><li>prisons</li></ul>",
3235
responses = [
3336
ApiResponse(responseCode = "200", useReturnTypeSchema = true),
37+
ApiResponse(responseCode = "400", content = [Content(schema = Schema(ref = "#/components/schemas/BadRequest"))]),
3438
ApiResponse(responseCode = "404", content = [Content(schema = Schema(ref = "#/components/schemas/PersonNotFound"))]),
3539
ApiResponse(responseCode = "500", content = [Content(schema = Schema(ref = "#/components/schemas/InternalServerError"))]),
3640
],
3741
)
38-
fun getPersonAddresses(
39-
@Parameter(description = "A URL-encoded HMPPS identifier", example = "2008%2F0545166T") @PathVariable encodedHmppsId: String,
42+
fun getProtectedCharacteristics(
43+
@Parameter(description = "The HMPPS ID of the person", example = "G2996UX") @PathVariable hmppsId: String,
44+
@RequestAttribute filters: ConsumerFilters?,
4045
): DataResponse<PersonProtectedCharacteristics?> {
41-
val hmppsId = encodedHmppsId.decodeUrlCharacters()
42-
val response = getProtectedCharacteristicsService.execute(hmppsId)
46+
val response = getProtectedCharacteristicsService.execute(hmppsId, filters)
4347

4448
if (response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
4549
throw EntityNotFoundException("Could not find person with id: $hmppsId")
4650
}
51+
if (response.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
52+
throw ValidationException("Invalid id: $hmppsId")
53+
}
4754
auditService.createEvent("GET_PERSON_PROTECTED_CHARACTERISTICS", mapOf("hmppsId" to hmppsId))
4855
return DataResponse(response.data)
4956
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/services/GetProtectedCharacteristicsService.kt

+27-3
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,51 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services
22

33
import org.springframework.beans.factory.annotation.Autowired
44
import org.springframework.stereotype.Service
5+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.common.ConsumerPrisonAccessService
56
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NomisGateway
67
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.PrisonerOffenderSearchGateway
78
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.ProbationOffenderSearchGateway
89
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonProtectedCharacteristics
910
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
12+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
13+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.roleconfig.ConsumerFilters
14+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPersonService.IdentifierType
1015

1116
@Service
1217
class GetProtectedCharacteristicsService(
1318
@Autowired val probationOffenderSearchGateway: ProbationOffenderSearchGateway,
1419
@Autowired val prisonerOffenderSearchGateway: PrisonerOffenderSearchGateway,
1520
@Autowired val nomisGateway: NomisGateway,
21+
@Autowired val consumerPrisonAccessService: ConsumerPrisonAccessService,
22+
@Autowired val getPersonService: GetPersonService,
1623
) {
17-
fun execute(hmppsId: String): Response<PersonProtectedCharacteristics?> {
24+
fun execute(
25+
hmppsId: String,
26+
filters: ConsumerFilters?,
27+
): Response<PersonProtectedCharacteristics?> {
28+
val hmppsIdType = getPersonService.identifyHmppsId(hmppsId)
29+
if (hmppsIdType == IdentifierType.UNKNOWN) {
30+
return Response(
31+
data = null,
32+
errors = listOf(UpstreamApiError(causedBy = UpstreamApi.NOMIS, type = UpstreamApiError.Type.BAD_REQUEST)),
33+
)
34+
}
35+
1836
val probationOffender = probationOffenderSearchGateway.getOffender(hmppsId)
1937

2038
if (probationOffender.data != null) {
21-
val result = probationOffender.data.toPersonProtectedCharacteristics()
2239
val prisonOffender =
2340
probationOffender.data.otherIds.nomsNumber
2441
?.let { prisonerOffenderSearchGateway.getPrisonOffender(it) }
42+
if (filters != null) {
43+
val consumerPrisonFilterCheck = consumerPrisonAccessService.checkConsumerHasPrisonAccess<PersonProtectedCharacteristics>(prisonOffender?.data?.prisonId, filters)
44+
if (consumerPrisonFilterCheck.errors.isNotEmpty()) {
45+
return consumerPrisonFilterCheck
46+
}
47+
}
48+
49+
val result = probationOffender.data.toPersonProtectedCharacteristics()
2550
if (prisonOffender?.data != null) {
2651
result.maritalStatus = prisonOffender.data.maritalStatus
2752

@@ -31,7 +56,6 @@ class GetProtectedCharacteristicsService(
3156
}
3257
return Response(data = result, errors = probationOffender.errors)
3358
}
34-
3559
return Response(data = null, errors = probationOffender.errors)
3660
}
3761
}

src/main/resources/globals.yml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ globals:
1818
- "/v1/persons/.*/cell-location"
1919
- "/v1/persons/.*/risks/categories"
2020
- "/v1/persons/.*/sentences"
21+
- "/v1/persons/.*/protected-characteristics"
2122
- "/v1/persons/.*/reported-adjudications"
2223
- "/v1/prison/prisoners"
2324
- "/v1/prison/prisoners/[^/]*$"

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/ProtectedCharacteristicsControllerTest.kt

+10-14
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
2323
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
2424
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetProtectedCharacteristicsService
2525
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
26-
import java.net.URLEncoder
27-
import java.nio.charset.StandardCharsets
2826

2927
@WebMvcTest(controllers = [ProtectedCharacteristicsController::class])
3028
@ActiveProfiles("test")
@@ -34,16 +32,16 @@ internal class ProtectedCharacteristicsControllerTest(
3432
@MockitoBean val auditService: AuditService,
3533
) : DescribeSpec(
3634
{
37-
val hmppsId = "9999/11111A"
38-
val encodedHmppsId = URLEncoder.encode(hmppsId, StandardCharsets.UTF_8)
39-
val path = "/v1/persons/$encodedHmppsId/protected-characteristics"
35+
val hmppsId = "A1234AA"
36+
val filters = null
37+
val path = "/v1/persons/$hmppsId/protected-characteristics"
4038
val mockMvc = IntegrationAPIMockMvc(springMockMvc)
4139

4240
describe("GET $path") {
4341
beforeTest {
4442
Mockito.reset(getProtectedCharacteristicsService)
4543
Mockito.reset(auditService)
46-
whenever(getProtectedCharacteristicsService.execute(hmppsId)).thenReturn(
44+
whenever(getProtectedCharacteristicsService.execute(hmppsId, filters)).thenReturn(
4745
Response(
4846
data =
4947
PersonProtectedCharacteristics(35, "Female", "Unknown", "British", "British", "None", emptyList()),
@@ -60,7 +58,7 @@ internal class ProtectedCharacteristicsControllerTest(
6058
it("gets the offences for a person with the matching ID") {
6159
mockMvc.performAuthorised(path)
6260

63-
verify(getProtectedCharacteristicsService, VerificationModeFactory.times(1)).execute(hmppsId)
61+
verify(getProtectedCharacteristicsService, VerificationModeFactory.times(1)).execute(hmppsId, filters)
6462
}
6563

6664
it("returns the offences for a person with the matching ID") {
@@ -95,12 +93,10 @@ internal class ProtectedCharacteristicsControllerTest(
9593
}
9694

9795
it("returns an empty list embedded in a JSON object when no offences are found") {
98-
val hmppsIdForPersonWithNoOffences = "0000/11111A"
99-
val encodedHmppsIdForPersonWithNoOffences =
100-
URLEncoder.encode(hmppsIdForPersonWithNoOffences, StandardCharsets.UTF_8)
101-
val offencesPath = "/v1/persons/$encodedHmppsIdForPersonWithNoOffences/protected-characteristics"
96+
val hmppsIdForPersonWithNoOffences = "A1234AA"
97+
val offencesPath = "/v1/persons/$hmppsIdForPersonWithNoOffences/protected-characteristics"
10298

103-
whenever(getProtectedCharacteristicsService.execute(hmppsIdForPersonWithNoOffences)).thenReturn(
99+
whenever(getProtectedCharacteristicsService.execute(hmppsIdForPersonWithNoOffences, filters)).thenReturn(
104100
Response(
105101
data = null,
106102
),
@@ -112,7 +108,7 @@ internal class ProtectedCharacteristicsControllerTest(
112108
}
113109

114110
it("returns a 404 NOT FOUND status code when person isn't found in the upstream API") {
115-
whenever(getProtectedCharacteristicsService.execute(hmppsId)).thenReturn(
111+
whenever(getProtectedCharacteristicsService.execute(hmppsId, filters)).thenReturn(
116112
Response(
117113
data = null,
118114
errors =
@@ -131,7 +127,7 @@ internal class ProtectedCharacteristicsControllerTest(
131127
}
132128

133129
it("fails with the appropriate error when an upstream service is down") {
134-
whenever(getProtectedCharacteristicsService.execute(hmppsId)).doThrow(
130+
whenever(getProtectedCharacteristicsService.execute(hmppsId, filters)).doThrow(
135131
WebClientResponseException(500, "MockError", null, null, null, null),
136132
)
137133

src/test/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/integration/person/ProtectedCharacteristicsIntegrationTest.kt

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,26 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.integration.IntegrationT
88
class ProtectedCharacteristicsIntegrationTest : IntegrationTestBase() {
99
@Test
1010
fun `returns protected characteristics for a person`() {
11-
callApi("$basePath/$pnc/protected-characteristics")
11+
callApi("$basePath/$nomsId/protected-characteristics")
1212
.andExpect(status().isOk)
1313
.andExpect(content().json(getExpectedResponse("person-protected-characteristics")))
1414
}
15+
16+
@Test
17+
fun `returns a 400 if the hmppsId is invalid`() {
18+
callApi("$basePath/$invalidNomsId/protected-characteristics")
19+
.andExpect(status().isBadRequest)
20+
}
21+
22+
@Test
23+
fun `returns a 404 for if consumer has empty list of prisons`() {
24+
callApiWithCN("$basePath/$nomsId/protected-characteristics", noPrisonsCn)
25+
.andExpect(status().isNotFound)
26+
}
27+
28+
@Test
29+
fun `returns a 404 for prisoner in wrong prison`() {
30+
callApiWithCN("$basePath/$nomsId/protected-characteristics", limitedPrisonsCn)
31+
.andExpect(status().isNotFound)
32+
}
1533
}

0 commit comments

Comments
 (0)