Skip to content

Commit ce53735

Browse files
authored
HMAI-223 Gateway and Service Layer - Get prisoner visits restrictions - /v1/persons/{hmppsId}/visit-restrictions (#653)
* implement gateway getOffenderVisitRestrictions * Added GetVisitRestrictionsForPersonService
1 parent 51c6300 commit ce53735

File tree

6 files changed

+434
-0
lines changed

6 files changed

+434
-0
lines changed

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

+25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Address
1010
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Alert
1111
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ImageMetadata
1212
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Offence
13+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonVisitRestriction
1314
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ReasonableAdjustment
1415
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
1516
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.RiskCategory
@@ -29,6 +30,7 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisImageD
2930
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisInmateDetail
3031
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisOffenceHistoryDetail
3132
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisOffenderSentence
33+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisOffenderVisitRestrictions
3234
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisReasonableAdjustments
3335
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisReferenceCode
3436
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisSentence
@@ -450,6 +452,29 @@ class NomisGateway(
450452
}
451453
}
452454

455+
fun getOffenderVisitRestrictions(offenderNumber: String): Response<List<PersonVisitRestriction>?> {
456+
val result =
457+
webClient.request<NomisOffenderVisitRestrictions>(
458+
HttpMethod.GET,
459+
"/api/offenders/$offenderNumber/offender-restrictions",
460+
authenticationHeader(),
461+
UpstreamApi.NOMIS,
462+
badRequestAsError = true,
463+
)
464+
return when (result) {
465+
is WebClientWrapperResponse.Success -> {
466+
Response(data = result.data.offenderRestrictions.map { it.toPersonVisitRestriction() })
467+
}
468+
469+
is WebClientWrapperResponse.Error -> {
470+
Response(
471+
data = null,
472+
errors = result.errors,
473+
)
474+
}
475+
}
476+
}
477+
453478
private fun authenticationHeader(): Map<String, String> {
454479
val token = hmppsAuthGateway.getClientToken("NOMIS")
455480

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 PersonVisitRestriction(
4+
val restrictionId: Long,
5+
val comment: String,
6+
val restrictionType: String,
7+
val restrictionTypeDescription: String,
8+
val startDate: String,
9+
val expiryDate: String,
10+
val active: Boolean,
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis
2+
3+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonVisitRestriction
4+
5+
data class NomisOffenderVisitRestrictions(
6+
val bookingId: Long,
7+
val offenderRestrictions: List<OffenderRestriction>,
8+
)
9+
10+
data class OffenderRestriction(
11+
val restrictionId: Long,
12+
val comment: String,
13+
val restrictionType: String,
14+
val restrictionTypeDescription: String,
15+
val startDate: String,
16+
val expiryDate: String,
17+
val active: Boolean,
18+
) {
19+
fun toPersonVisitRestriction() =
20+
PersonVisitRestriction(
21+
restrictionId = this.restrictionId,
22+
comment = this.comment,
23+
restrictionType = this.restrictionType,
24+
restrictionTypeDescription = this.restrictionTypeDescription,
25+
startDate = this.startDate,
26+
expiryDate = this.expiryDate,
27+
active = this.active,
28+
)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.common.ConsumerPrisonAccessService
6+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NomisGateway
7+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.PrisonerOffenderSearchGateway
8+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonVisitRestriction
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
10+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
12+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.roleconfig.ConsumerFilters
13+
14+
@Service
15+
class GetVisitRestrictionsForPersonService(
16+
@Autowired val nomisGateway: NomisGateway,
17+
@Autowired val prisonerOffenderSearchGateway: PrisonerOffenderSearchGateway,
18+
@Autowired val consumerPrisonAccessService: ConsumerPrisonAccessService,
19+
@Autowired val getPersonService: GetPersonService,
20+
) {
21+
fun execute(
22+
hmppsId: String,
23+
filters: ConsumerFilters? = null,
24+
): Response<List<PersonVisitRestriction>?> {
25+
val personResponse = getPersonService.getNomisNumber(hmppsId)
26+
if (personResponse.errors.isNotEmpty()) {
27+
return Response(data = null, errors = personResponse.errors)
28+
}
29+
val nomisNumber = personResponse.data?.nomisNumber
30+
if (nomisNumber == null) {
31+
return Response(
32+
data = null,
33+
errors = listOf(UpstreamApiError(causedBy = UpstreamApi.NOMIS, type = UpstreamApiError.Type.ENTITY_NOT_FOUND)),
34+
)
35+
}
36+
37+
val (prisonerOffender, prisonOffenderErrors) = prisonerOffenderSearchGateway.getPrisonOffender(nomisNumber)
38+
if (prisonOffenderErrors.isNotEmpty()) {
39+
return Response(data = null, prisonOffenderErrors)
40+
}
41+
val prisonId = prisonerOffender?.prisonId
42+
43+
val consumerPrisonFilterCheck = consumerPrisonAccessService.checkConsumerHasPrisonAccess<List<PersonVisitRestriction>>(prisonId, filters)
44+
if (consumerPrisonFilterCheck.errors.isNotEmpty()) {
45+
return consumerPrisonFilterCheck
46+
}
47+
48+
val visitRestrictionResponse = nomisGateway.getOffenderVisitRestrictions(nomisNumber)
49+
return visitRestrictionResponse
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.nomis
2+
3+
import io.kotest.core.spec.style.DescribeSpec
4+
import io.kotest.matchers.collections.shouldBeEmpty
5+
import io.kotest.matchers.collections.shouldHaveSize
6+
import io.kotest.matchers.ints.shouldBeGreaterThan
7+
import io.kotest.matchers.nulls.shouldNotBeNull
8+
import io.kotest.matchers.shouldBe
9+
import org.mockito.Mockito
10+
import org.mockito.internal.verification.VerificationModeFactory
11+
import org.mockito.kotlin.verify
12+
import org.mockito.kotlin.whenever
13+
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
14+
import org.springframework.http.HttpStatus
15+
import org.springframework.test.context.ActiveProfiles
16+
import org.springframework.test.context.ContextConfiguration
17+
import org.springframework.test.context.bean.override.mockito.MockitoBean
18+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.removeWhitespaceAndNewlines
19+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.HmppsAuthGateway
20+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.NomisGateway
21+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.mockservers.HmppsAuthMockServer
22+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.mockservers.NomisApiMockServer
23+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
24+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
25+
26+
@ActiveProfiles("test")
27+
@ContextConfiguration(
28+
initializers = [ConfigDataApplicationContextInitializer::class],
29+
classes = [NomisGateway::class],
30+
)
31+
class GetOffenderVisitRestrictionsTest(
32+
@MockitoBean val hmppsAuthGateway: HmppsAuthGateway,
33+
val nomisGateway: NomisGateway,
34+
) : DescribeSpec(
35+
{
36+
val nomisApiMockServer = NomisApiMockServer()
37+
val offenderNo = "zyx987"
38+
val offenderRestrictionsPath = "/api/offenders/$offenderNo/offender-restrictions"
39+
40+
beforeEach {
41+
nomisApiMockServer.start()
42+
nomisApiMockServer.stubNomisApiResponse(
43+
offenderRestrictionsPath,
44+
"""
45+
{
46+
"bookingId": 9007199254740991,
47+
"offenderRestrictions": [
48+
{
49+
"restrictionId": 9007199254740991,
50+
"comment": "string",
51+
"restrictionType": "string",
52+
"restrictionTypeDescription": "string",
53+
"startDate": "1980-01-01",
54+
"expiryDate": "1980-01-01",
55+
"active": true
56+
}
57+
]
58+
}
59+
""".removeWhitespaceAndNewlines(),
60+
)
61+
62+
Mockito.reset(hmppsAuthGateway)
63+
whenever(hmppsAuthGateway.getClientToken("NOMIS")).thenReturn(HmppsAuthMockServer.TOKEN)
64+
}
65+
66+
afterTest {
67+
nomisApiMockServer.stop()
68+
}
69+
70+
it("authenticates using HMPPS Auth with credentials") {
71+
nomisGateway.getOffenderVisitRestrictions(offenderNo)
72+
73+
verify(hmppsAuthGateway, VerificationModeFactory.times(1)).getClientToken("NOMIS")
74+
}
75+
76+
it("returns offender visit restrictions for the matching person ID") {
77+
val response = nomisGateway.getOffenderVisitRestrictions(offenderNo)
78+
79+
response.data.shouldNotBeNull()
80+
response.data!!.count().shouldBeGreaterThan(0)
81+
}
82+
83+
it("returns a person with an empty list of restrictions when no restrictions are found") {
84+
nomisApiMockServer.stubNomisApiResponse(
85+
offenderRestrictionsPath,
86+
"""
87+
{
88+
"bookingId": 9007199254740991,
89+
"offenderRestrictions": []
90+
}
91+
""".removeWhitespaceAndNewlines(),
92+
)
93+
94+
val response = nomisGateway.getOffenderVisitRestrictions(offenderNo)
95+
96+
response.data.shouldBeEmpty()
97+
}
98+
99+
it("returns an error when 404 Not Found is returned because no person is found") {
100+
nomisApiMockServer.stubNomisApiResponse(offenderRestrictionsPath, "", HttpStatus.NOT_FOUND)
101+
102+
val response = nomisGateway.getOffenderVisitRestrictions(offenderNo)
103+
104+
response.errors.shouldHaveSize(1)
105+
response.errors
106+
.first()
107+
.causedBy
108+
.shouldBe(UpstreamApi.NOMIS)
109+
response.errors
110+
.first()
111+
.type
112+
.shouldBe(UpstreamApiError.Type.ENTITY_NOT_FOUND)
113+
}
114+
115+
it("returns an error when 400 Bad Request is returned because of an invalid request") {
116+
nomisApiMockServer.stubNomisApiResponse(offenderRestrictionsPath, "", HttpStatus.BAD_REQUEST)
117+
118+
val response = nomisGateway.getOffenderVisitRestrictions(offenderNo)
119+
120+
response.errors.shouldHaveSize(1)
121+
response.errors
122+
.first()
123+
.causedBy
124+
.shouldBe(UpstreamApi.NOMIS)
125+
response.errors
126+
.first()
127+
.type
128+
.shouldBe(UpstreamApiError.Type.BAD_REQUEST)
129+
}
130+
},
131+
)

0 commit comments

Comments
 (0)