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-667 - Thin slice for MAPPA Details #386

Merged
merged 10 commits into from
Mar 12, 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
42 changes: 42 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,48 @@ paths:
PersonNotFoundError:
$ref: "#/components/examples/PersonNotFoundError"

/v1/persons/{hmppsId}/risks/mappadetail:
get:
tags:
- persons
summary: Returns the mappa detail related to a person.
parameters:
- $ref: "#/components/parameters/HmppsId"
responses:
"200":
description: Successfully found mappa detail for a person with the provided HMPPS ID.
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
level:
type: string
levelDescription:
type: string
category:
type: string
categoryDescription:
type: string
startDate:
type: string
reviewDate:
type: string
notes:
type: string
"404":
description: Failed to find risk-related mappa detail a person with the provided HMPPS ID.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
PersonNotFoundError:
$ref: "#/components/examples/PersonNotFoundError"

/v1/persons/{hmppsId}/needs:
get:
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.exception.EntityNotFoundException
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.decodeUrlCharacters
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetMappaDetailForPersonService
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService

@RestController
@RequestMapping("/v1/persons")
class MappaDetailController(
@Autowired val getMappaDetailForPersonService: GetMappaDetailForPersonService,
@Autowired val auditService: AuditService,
) {

@GetMapping("{encodedHmppsId}/risks/mappadetail")
fun getMappaDetail(
@PathVariable encodedHmppsId: String,

): Map<String, MappaDetail?> {
val hmppsId = encodedHmppsId.decodeUrlCharacters()
val response = getMappaDetailForPersonService.execute(hmppsId)

if (response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
throw EntityNotFoundException("Could not find person with id: $hmppsId")
}
auditService.createEvent("GET_MAPPA_DETAIL", "Mappa detail for person with hmpps id: $hmppsId has been retrieved")
return mapOf("data" to response.data)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.http.HttpMethod
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper.WebClientWrapperResponse
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Offence
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Sentence
Expand Down Expand Up @@ -63,6 +64,28 @@ class NDeliusGateway(@Value("\${services.ndelius.base-url}") baseUrl: String) {
}
}

fun getMappaDetailForPerson(id: String): Response<MappaDetail?> {
val result = webClient.request<NDeliusSupervisions>(
HttpMethod.GET,
"/case/$id/supervisions",
authenticationHeader(),
UpstreamApi.NDELIUS,
)

return when (result) {
is WebClientWrapperResponse.Success -> {
Response(data = result.data.mappaDetail?.toMappaDetail())
}

is WebClientWrapperResponse.Error -> {
Response(
data = null,
errors = result.errors,
)
}
}
}

private fun authenticationHeader(): Map<String, String> {
val token = hmppsAuthGateway.getClientToken("nDelius")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps

data class MappaDetail(
val level: Int? = null,
val levelDescription: String? = null,
val category: Int? = null,
val categoryDescription: String? = null,
val startDate: String? = null,
val reviewDate: String? = null,
val notes: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.ndelius

import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail

data class NDeliusMappaDetail(
val level: Int? = null,
val levelDescription: String? = null,
val category: Int? = null,
val categoryDescription: String? = null,
val startDate: String? = null,
val reviewDate: String? = null,
val notes: String? = null,
) {
fun toMappaDetail(): MappaDetail = (
MappaDetail(
level = this.level,
levelDescription = this.levelDescription,
category = this.category,
categoryDescription = this.categoryDescription,
startDate = this.startDate,
reviewDate = this.reviewDate,
notes = this.notes,
)
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.ndelius

data class NDeliusSupervisions(
val mappaDetail: NDeliusMappaDetail ? = null,
val supervisions: List<NDeliusSupervision>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NDeliusGateway
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response

@Service
class GetMappaDetailForPersonService(
@Autowired val getPersonService: GetPersonService,
@Autowired val nDeliusGateway: NDeliusGateway,
) {
fun execute(hmppsId: String): Response<MappaDetail?> {
val personResponse = getPersonService.execute(hmppsId = hmppsId)

val deliusCrn = personResponse.data?.identifiers?.deliusCrn
var nDeliusMappaDetailResponse: Response<MappaDetail?> = Response(data = MappaDetail())

if (deliusCrn != null) {
nDeliusMappaDetailResponse = nDeliusGateway.getMappaDetailForPerson(id = deliusCrn)
}

return Response(
data = nDeliusMappaDetailResponse.data,
errors = nDeliusMappaDetailResponse.errors,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1.person

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import org.mockito.Mockito
import org.mockito.internal.verification.VerificationModeFactory
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.HttpStatus
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.removeWhitespaceAndNewlines
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.helpers.IntegrationAPIMockMvc
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.MappaDetail
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetMappaDetailForPersonService
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

@WebMvcTest(controllers = [MappaDetailController::class])
@ActiveProfiles("test")
internal class MappaDetailControllerTest(
@Autowired var springMockMvc: MockMvc,
@MockBean val getMappaDetailForPersonService: GetMappaDetailForPersonService,
@MockBean val auditService: AuditService,
) : DescribeSpec(
{
val hmppsId = "9999/11111A"
val encodedHmppsId = URLEncoder.encode(hmppsId, StandardCharsets.UTF_8)
val path = "/v1/persons/$encodedHmppsId/risks/mappadetail"
val mockMvc = IntegrationAPIMockMvc(springMockMvc)

describe("GET $path") {
beforeTest {
Mockito.reset(getMappaDetailForPersonService)
whenever(getMappaDetailForPersonService.execute(hmppsId)).thenReturn(
Response(
MappaDetail(
level = 2,
levelDescription = "A high level of risk",
category = 3,
categoryDescription = "Behaviour",
startDate = "2024-03-08",
reviewDate = "2024-10-08",
notes = "Review in a week",
),
),
)

Mockito.reset(auditService)
}

it("returns a 200 OK status code") {
val result = mockMvc.performAuthorised(path)

result.response.status.shouldBe(HttpStatus.OK.value())
}

it("gets the mappa detail for a person with the matching ID") {
mockMvc.performAuthorised(path)
verify(getMappaDetailForPersonService, VerificationModeFactory.times(1)).execute(hmppsId)
}

it("logs audit") {
mockMvc.performAuthorised(path)

verify(auditService, VerificationModeFactory.times(1)).createEvent("GET_MAPPA_DETAIL", "Mappa detail for person with hmpps id: $hmppsId has been retrieved")
}

it("returns the risk categories for a person with the matching ID") {
val result = mockMvc.performAuthorised(path)

result.response.contentAsString.shouldContain(
"""
"data": {
"level": 2,
"levelDescription": "A high level of risk",
"category": 3,
"categoryDescription": "Behaviour",
"startDate": "2024-03-08",
"reviewDate": "2024-10-08",
"notes": "Review in a week"
}
""".removeWhitespaceAndNewlines(),
)
}

it("returns a 404 NOT FOUND status code when person isn't found in the upstream API") {
whenever(getMappaDetailForPersonService.execute(hmppsId)).thenReturn(
Response(
data = MappaDetail(),
errors = listOf(
UpstreamApiError(
causedBy = UpstreamApi.PROBATION_OFFENDER_SEARCH,
type = UpstreamApiError.Type.ENTITY_NOT_FOUND,
),
),
),
)

val result = mockMvc.performAuthorised(path)

result.response.status.shouldBe(HttpStatus.NOT_FOUND.value())
}
}
},
)
Loading
Loading