Skip to content

Commit 115bc48

Browse files
Controller implemented
1 parent 28452cf commit 115bc48

File tree

5 files changed

+232
-0
lines changed

5 files changed

+232
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.RequestParam
8+
import org.springframework.web.bind.annotation.RestController
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
10+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.decodeUrlCharacters
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.DynamicRisk
12+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
13+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetDynamicRisksForPersonService
14+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
15+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.util.PaginatedResponse
16+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.util.paginateWith
17+
18+
@RestController
19+
@RequestMapping("/v1/persons")
20+
class DynamicRisksController (
21+
@Autowired val getDynamicRisksForPersonService : GetDynamicRisksForPersonService,
22+
@Autowired val auditService: AuditService,
23+
) {
24+
@GetMapping("{encodedHmppsId}/risks/dynamic")
25+
fun getDynamicRisks(
26+
@PathVariable encodedHmppsId: String,
27+
@RequestParam(required = false, defaultValue = "1", name = "page") page: Int,
28+
@RequestParam(required = false, defaultValue = "10", name = "perPage") perPage: Int,
29+
): PaginatedResponse<DynamicRisk> {
30+
val hmppsId = encodedHmppsId.decodeUrlCharacters()
31+
val response = getDynamicRisksForPersonService.execute(hmppsId)
32+
33+
if (response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
34+
throw EntityNotFoundException("Could not find person with id: $hmppsId")
35+
}
36+
auditService.createEvent("GET_DYNAMIC_RISKS", mapOf("hmppsId" to hmppsId))
37+
return response.data.paginateWith(page, perPage)
38+
}
39+
}

src/main/resources/application-local-docker.yml

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ authorisation:
4444
- "/v1/persons/.*/protected-characteristics"
4545
- "/v1/persons/.*/risks/mappadetail"
4646
- "/v1/persons/.*/risks/categories"
47+
- "/v1/persons/.*/risks/dynamic"
4748
- "/v1/persons/.*/case-notes"
4849
- "/v1/persons/.*/person-responsible-officer"
4950
- "/v1/persons/.*/risk-management-plan"

src/main/resources/application-local.yml

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ authorisation:
4545
- "/v1/persons/.*/risks/scores"
4646
- "/v1/persons/.*/needs"
4747
- "/v1/persons/.*/risks/serious-harm"
48+
- "/v1/persons/.*/risks/dynamic"
4849
- "/v1/persons/.*/reported-adjudications"
4950
- "/v1/persons/.*/adjudications"
5051
- "/v1/persons/.*/licences/conditions"

src/main/resources/application-test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ authorisation:
5353
- "/v1/persons/.*/risks/scores"
5454
- "/v1/persons/.*/needs"
5555
- "/v1/persons/.*/risks/serious-harm"
56+
- "/v1/persons/.*/risks/dynamic"
5657
- "/v1/persons/.*/reported-adjudications"
5758
- "/v1/epf/person-details/.*/\\.*+[^/]*$"
5859
- "/v1/persons/.*/adjudications"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person
2+
3+
import io.kotest.assertions.json.shouldContainJsonKeyValue
4+
import io.kotest.core.spec.style.DescribeSpec
5+
import io.kotest.matchers.shouldBe
6+
import io.kotest.matchers.string.shouldContain
7+
import org.mockito.Mockito
8+
import org.mockito.internal.verification.VerificationModeFactory
9+
import org.mockito.kotlin.doThrow
10+
import org.mockito.kotlin.verify
11+
import org.mockito.kotlin.whenever
12+
import org.springframework.beans.factory.annotation.Autowired
13+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
14+
import org.springframework.boot.test.mock.mockito.MockBean
15+
import org.springframework.http.HttpStatus
16+
import org.springframework.test.context.ActiveProfiles
17+
import org.springframework.test.web.servlet.MockMvc
18+
import org.springframework.web.reactive.function.client.WebClientResponseException
19+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.removeWhitespaceAndNewlines
20+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.helpers.IntegrationAPIMockMvc
21+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.DynamicRisk
22+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
23+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
24+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
25+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetDynamicRisksForPersonService
26+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
27+
import java.net.URLEncoder
28+
import java.nio.charset.StandardCharsets
29+
30+
@WebMvcTest(controllers = [DynamicRisksController::class])
31+
@ActiveProfiles("test")
32+
internal class DynamicRisksControllerTest(
33+
@Autowired var springMockMvc: MockMvc,
34+
@MockBean val getDynamicRisksForPersonService: GetDynamicRisksForPersonService,
35+
@MockBean val auditService: AuditService,
36+
) : DescribeSpec(
37+
{
38+
val hmppsId = "9999/11111A"
39+
val encodedHmppsId = URLEncoder.encode(hmppsId, StandardCharsets.UTF_8)
40+
val path = "/v1/persons/$encodedHmppsId/risks/dynamic"
41+
val mockMvc = IntegrationAPIMockMvc(springMockMvc)
42+
43+
describe("GET $path") {
44+
beforeTest {
45+
Mockito.reset(getDynamicRisksForPersonService)
46+
Mockito.reset(auditService)
47+
whenever(getDynamicRisksForPersonService.execute(hmppsId)).thenReturn(
48+
Response(
49+
data =
50+
listOf(
51+
DynamicRisk(
52+
code = "AVIS",
53+
description = "Subject has a ViSOR record",
54+
startDate = "2023-09-08",
55+
reviewDate = "2026-04-29",
56+
notes = "Nothing to say"
57+
),
58+
DynamicRisk(
59+
code = "RHRH",
60+
description = "High Risk of Harm",
61+
startDate = "2022-09-01",
62+
reviewDate = "2024-12-23",
63+
notes = "A lot of notes"
64+
),
65+
),
66+
),
67+
)
68+
}
69+
70+
it("returns a 200 OK status code") {
71+
val result = mockMvc.performAuthorised(path)
72+
73+
result.response.status.shouldBe(HttpStatus.OK.value())
74+
}
75+
76+
it("logs audit") {
77+
mockMvc.performAuthorised(path)
78+
79+
verify(
80+
auditService,
81+
VerificationModeFactory.times(1),
82+
).createEvent("GET_DYNAMIC_RISKS", mapOf("hmppsId" to hmppsId))
83+
}
84+
85+
it("gets the dynamic risks for a person with the matching ID") {
86+
mockMvc.performAuthorised(path)
87+
88+
verify(getDynamicRisksForPersonService, VerificationModeFactory.times(1)).execute(hmppsId)
89+
}
90+
91+
it("returns the dynamic risks for a person with the matching ID") {
92+
val result = mockMvc.performAuthorised(path)
93+
94+
result.response.contentAsString.shouldContain(
95+
"""
96+
"data": [
97+
{
98+
"code": "AVIS",
99+
"description": "Subject has a ViSOR record",
100+
"startDate": "2023-09-08",
101+
"reviewDate": "2026-04-29",
102+
"notes": "Nothing to say"
103+
},
104+
{
105+
"code": "RHRH",
106+
"description": "High Risk of Harm",
107+
"startDate": "2022-09-01",
108+
"reviewDate": "2024-12-23",
109+
"notes": "A lot of notes"
110+
}
111+
]
112+
""".removeWhitespaceAndNewlines(),
113+
)
114+
}
115+
116+
it("returns an empty list when no dynamic risks are found") {
117+
val hmppsIdForPersonWithNoDynamicRisks = "0123/12345B"
118+
val encodedHmppsIdForPersonWithNoDynamicRisks =
119+
URLEncoder.encode(hmppsIdForPersonWithNoDynamicRisks, StandardCharsets.UTF_8)
120+
val dynamicRisksPath = "/v1/persons/$encodedHmppsIdForPersonWithNoDynamicRisks/risks/dynamic"
121+
122+
whenever(getDynamicRisksForPersonService.execute(hmppsIdForPersonWithNoDynamicRisks)).thenReturn(
123+
Response(
124+
data = emptyList(),
125+
),
126+
)
127+
128+
val result = mockMvc.performAuthorised(dynamicRisksPath)
129+
130+
result.response.contentAsString.shouldContain("\"data\":[]".removeWhitespaceAndNewlines())
131+
}
132+
133+
it("returns a 404 NOT FOUND status code when person isn't found in the upstream API") {
134+
whenever(getDynamicRisksForPersonService.execute(hmppsId)).thenReturn(
135+
Response(
136+
data = emptyList(),
137+
errors =
138+
listOf(
139+
UpstreamApiError(
140+
causedBy = UpstreamApi.NOMIS,
141+
type = UpstreamApiError.Type.ENTITY_NOT_FOUND,
142+
),
143+
),
144+
),
145+
)
146+
147+
val result = mockMvc.performAuthorised(path)
148+
149+
result.response.status.shouldBe(HttpStatus.NOT_FOUND.value())
150+
}
151+
152+
it("returns paginated results") {
153+
whenever(getDynamicRisksForPersonService.execute(hmppsId)).thenReturn(
154+
Response(
155+
data =
156+
List(20) {
157+
DynamicRisk(
158+
code = "XNR",
159+
description = "Not For Release",
160+
startDate = "2022-08-01",
161+
reviewDate = "2025-08-01",
162+
notes = "Notes all written here"
163+
)
164+
},
165+
),
166+
)
167+
168+
val result = mockMvc.performAuthorised("$path?page=1&perPage=10")
169+
170+
result.response.contentAsString.shouldContainJsonKeyValue("$.pagination.page", 1)
171+
result.response.contentAsString.shouldContainJsonKeyValue("$.pagination.totalPages", 2)
172+
}
173+
174+
it("fails with the appropriate error when an upstream service is down") {
175+
whenever(getDynamicRisksForPersonService.execute(hmppsId)).doThrow(
176+
WebClientResponseException(500, "MockError", null, null, null, null),
177+
)
178+
179+
val response = mockMvc.performAuthorised("$path?page=1&perPage=10")
180+
181+
assert(response.response.status == 500)
182+
assert(
183+
response.response.contentAsString.equals(
184+
"{\"status\":500,\"errorCode\":null,\"userMessage\":\"500 MockError\",\"developerMessage\":\"Unable to complete request as an upstream service is not responding\",\"moreInfo\":null}",
185+
),
186+
)
187+
}
188+
}
189+
}
190+
)

0 commit comments

Comments
 (0)