Skip to content

Commit bd7520c

Browse files
Merge pull request #386 from ministryofjustice/HIA-667
HIA-667 - Thin slice for MAPPA Details
2 parents db7f8d0 + 9d0d704 commit bd7520c

File tree

15 files changed

+526
-2
lines changed

15 files changed

+526
-2
lines changed

openapi.yml

+42
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,48 @@ paths:
412412
PersonNotFoundError:
413413
$ref: "#/components/examples/PersonNotFoundError"
414414

415+
/v1/persons/{hmppsId}/risks/mappadetail:
416+
get:
417+
tags:
418+
- persons
419+
summary: Returns the mappa detail related to a person.
420+
parameters:
421+
- $ref: "#/components/parameters/HmppsId"
422+
responses:
423+
"200":
424+
description: Successfully found mappa detail for a person with the provided HMPPS ID.
425+
content:
426+
application/json:
427+
schema:
428+
type: object
429+
properties:
430+
data:
431+
type: object
432+
properties:
433+
level:
434+
type: string
435+
levelDescription:
436+
type: string
437+
category:
438+
type: string
439+
categoryDescription:
440+
type: string
441+
startDate:
442+
type: string
443+
reviewDate:
444+
type: string
445+
notes:
446+
type: string
447+
"404":
448+
description: Failed to find risk-related mappa detail a person with the provided HMPPS ID.
449+
content:
450+
application/json:
451+
schema:
452+
$ref: "#/components/schemas/Error"
453+
examples:
454+
PersonNotFoundError:
455+
$ref: "#/components/examples/PersonNotFoundError"
456+
415457
/v1/persons/{hmppsId}/needs:
416458
get:
417459
tags:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person
2+
3+
import org.springframework.beans.factory.annotation.Autowired
4+
import org.springframework.web.bind.annotation.GetMapping
5+
import org.springframework.web.bind.annotation.PathVariable
6+
import org.springframework.web.bind.annotation.RequestMapping
7+
import org.springframework.web.bind.annotation.RestController
8+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.decodeUrlCharacters
10+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
12+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetMappaDetailForPersonService
13+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
14+
15+
@RestController
16+
@RequestMapping("/v1/persons")
17+
class MappaDetailController(
18+
@Autowired val getMappaDetailForPersonService: GetMappaDetailForPersonService,
19+
@Autowired val auditService: AuditService,
20+
) {
21+
22+
@GetMapping("{encodedHmppsId}/risks/mappadetail")
23+
fun getMappaDetail(
24+
@PathVariable encodedHmppsId: String,
25+
26+
): Map<String, MappaDetail?> {
27+
val hmppsId = encodedHmppsId.decodeUrlCharacters()
28+
val response = getMappaDetailForPersonService.execute(hmppsId)
29+
30+
if (response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
31+
throw EntityNotFoundException("Could not find person with id: $hmppsId")
32+
}
33+
auditService.createEvent("GET_MAPPA_DETAIL", "Mappa detail for person with hmpps id: $hmppsId has been retrieved")
34+
return mapOf("data" to response.data)
35+
}
36+
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/NDeliusGateway.kt

+23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.springframework.http.HttpMethod
66
import org.springframework.stereotype.Component
77
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper
88
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper.WebClientWrapperResponse
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
910
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Offence
1011
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
1112
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Sentence
@@ -63,6 +64,28 @@ class NDeliusGateway(@Value("\${services.ndelius.base-url}") baseUrl: String) {
6364
}
6465
}
6566

67+
fun getMappaDetailForPerson(id: String): Response<MappaDetail?> {
68+
val result = webClient.request<NDeliusSupervisions>(
69+
HttpMethod.GET,
70+
"/case/$id/supervisions",
71+
authenticationHeader(),
72+
UpstreamApi.NDELIUS,
73+
)
74+
75+
return when (result) {
76+
is WebClientWrapperResponse.Success -> {
77+
Response(data = result.data.mappaDetail?.toMappaDetail())
78+
}
79+
80+
is WebClientWrapperResponse.Error -> {
81+
Response(
82+
data = null,
83+
errors = result.errors,
84+
)
85+
}
86+
}
87+
}
88+
6689
private fun authenticationHeader(): Map<String, String> {
6790
val token = hmppsAuthGateway.getClientToken("nDelius")
6891

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
2+
3+
data class MappaDetail(
4+
val level: Int? = null,
5+
val levelDescription: String? = null,
6+
val category: Int? = null,
7+
val categoryDescription: String? = null,
8+
val startDate: String? = null,
9+
val reviewDate: String? = null,
10+
val notes: String? = null,
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.ndelius
2+
3+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
4+
5+
data class NDeliusMappaDetail(
6+
val level: Int? = null,
7+
val levelDescription: String? = null,
8+
val category: Int? = null,
9+
val categoryDescription: String? = null,
10+
val startDate: String? = null,
11+
val reviewDate: String? = null,
12+
val notes: String? = null,
13+
) {
14+
fun toMappaDetail(): MappaDetail = (
15+
MappaDetail(
16+
level = this.level,
17+
levelDescription = this.levelDescription,
18+
category = this.category,
19+
categoryDescription = this.categoryDescription,
20+
startDate = this.startDate,
21+
reviewDate = this.reviewDate,
22+
notes = this.notes,
23+
)
24+
)
25+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.ndelius
22

33
data class NDeliusSupervisions(
4+
val mappaDetail: NDeliusMappaDetail ? = null,
45
val supervisions: List<NDeliusSupervision>,
56
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services
2+
3+
import org.springframework.beans.factory.annotation.Autowired
4+
import org.springframework.stereotype.Service
5+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NDeliusGateway
6+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
7+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
8+
9+
@Service
10+
class GetMappaDetailForPersonService(
11+
@Autowired val getPersonService: GetPersonService,
12+
@Autowired val nDeliusGateway: NDeliusGateway,
13+
) {
14+
fun execute(hmppsId: String): Response<MappaDetail?> {
15+
val personResponse = getPersonService.execute(hmppsId = hmppsId)
16+
17+
val deliusCrn = personResponse.data?.identifiers?.deliusCrn
18+
var nDeliusMappaDetailResponse: Response<MappaDetail?> = Response(data = MappaDetail())
19+
20+
if (deliusCrn != null) {
21+
nDeliusMappaDetailResponse = nDeliusGateway.getMappaDetailForPerson(id = deliusCrn)
22+
}
23+
24+
return Response(
25+
data = nDeliusMappaDetailResponse.data,
26+
errors = nDeliusMappaDetailResponse.errors,
27+
)
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person
2+
3+
import io.kotest.core.spec.style.DescribeSpec
4+
import io.kotest.matchers.shouldBe
5+
import io.kotest.matchers.string.shouldContain
6+
import org.mockito.Mockito
7+
import org.mockito.internal.verification.VerificationModeFactory
8+
import org.mockito.kotlin.verify
9+
import org.mockito.kotlin.whenever
10+
import org.springframework.beans.factory.annotation.Autowired
11+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
12+
import org.springframework.boot.test.mock.mockito.MockBean
13+
import org.springframework.http.HttpStatus
14+
import org.springframework.test.context.ActiveProfiles
15+
import org.springframework.test.web.servlet.MockMvc
16+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.removeWhitespaceAndNewlines
17+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.helpers.IntegrationAPIMockMvc
18+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
19+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
20+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
21+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
22+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetMappaDetailForPersonService
23+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
24+
import java.net.URLEncoder
25+
import java.nio.charset.StandardCharsets
26+
27+
@WebMvcTest(controllers = [MappaDetailController::class])
28+
@ActiveProfiles("test")
29+
internal class MappaDetailControllerTest(
30+
@Autowired var springMockMvc: MockMvc,
31+
@MockBean val getMappaDetailForPersonService: GetMappaDetailForPersonService,
32+
@MockBean val auditService: AuditService,
33+
) : DescribeSpec(
34+
{
35+
val hmppsId = "9999/11111A"
36+
val encodedHmppsId = URLEncoder.encode(hmppsId, StandardCharsets.UTF_8)
37+
val path = "/v1/persons/$encodedHmppsId/risks/mappadetail"
38+
val mockMvc = IntegrationAPIMockMvc(springMockMvc)
39+
40+
describe("GET $path") {
41+
beforeTest {
42+
Mockito.reset(getMappaDetailForPersonService)
43+
whenever(getMappaDetailForPersonService.execute(hmppsId)).thenReturn(
44+
Response(
45+
MappaDetail(
46+
level = 2,
47+
levelDescription = "A high level of risk",
48+
category = 3,
49+
categoryDescription = "Behaviour",
50+
startDate = "2024-03-08",
51+
reviewDate = "2024-10-08",
52+
notes = "Review in a week",
53+
),
54+
),
55+
)
56+
57+
Mockito.reset(auditService)
58+
}
59+
60+
it("returns a 200 OK status code") {
61+
val result = mockMvc.performAuthorised(path)
62+
63+
result.response.status.shouldBe(HttpStatus.OK.value())
64+
}
65+
66+
it("gets the mappa detail for a person with the matching ID") {
67+
mockMvc.performAuthorised(path)
68+
verify(getMappaDetailForPersonService, VerificationModeFactory.times(1)).execute(hmppsId)
69+
}
70+
71+
it("logs audit") {
72+
mockMvc.performAuthorised(path)
73+
74+
verify(auditService, VerificationModeFactory.times(1)).createEvent("GET_MAPPA_DETAIL", "Mappa detail for person with hmpps id: $hmppsId has been retrieved")
75+
}
76+
77+
it("returns the risk categories for a person with the matching ID") {
78+
val result = mockMvc.performAuthorised(path)
79+
80+
result.response.contentAsString.shouldContain(
81+
"""
82+
"data": {
83+
"level": 2,
84+
"levelDescription": "A high level of risk",
85+
"category": 3,
86+
"categoryDescription": "Behaviour",
87+
"startDate": "2024-03-08",
88+
"reviewDate": "2024-10-08",
89+
"notes": "Review in a week"
90+
}
91+
""".removeWhitespaceAndNewlines(),
92+
)
93+
}
94+
95+
it("returns a 404 NOT FOUND status code when person isn't found in the upstream API") {
96+
whenever(getMappaDetailForPersonService.execute(hmppsId)).thenReturn(
97+
Response(
98+
data = MappaDetail(),
99+
errors = listOf(
100+
UpstreamApiError(
101+
causedBy = UpstreamApi.PROBATION_OFFENDER_SEARCH,
102+
type = UpstreamApiError.Type.ENTITY_NOT_FOUND,
103+
),
104+
),
105+
),
106+
)
107+
108+
val result = mockMvc.performAuthorised(path)
109+
110+
result.response.status.shouldBe(HttpStatus.NOT_FOUND.value())
111+
}
112+
}
113+
},
114+
)

0 commit comments

Comments
 (0)