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

Refactor risk endpoints to return empty data when upstream returns 403 #445

Merged
merged 10 commits into from
Jun 17, 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
6 changes: 3 additions & 3 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ paths:
get:
tags:
- risks
summary: Returns risk scores from the last year associated with a person, sorted by completedDate (newest first).
summary: Returns risk scores from the last year associated with a person, sorted by completedDate (newest first). This endpoint does not serve LAO (Limited Access Offender) data.
parameters:
- $ref: "#/components/parameters/hmppsId"
- $ref: "#/components/parameters/page"
Expand Down Expand Up @@ -649,7 +649,7 @@ paths:
tags:
- needs
summary: >
Returns criminogenic needs associated with a person.
Returns criminogenic needs associated with a person. This endpoint does not serve LAO (Limited Access Offender) data.

Note: Criminogenic needs are dynamic factors that are directly linked to criminal behaviour. Eight criminogenic needs are measured in OASys: Accommodation, Employability, Relationships, Lifestyle and Associates, Drug Misuse, Alcohol Misuse, Thinking & Behaviour and Attitudes. These are scored according to whether there is “no need”, “some need” or “severe need”, and a need is identified in a specific section based on calculations around these scores.
However, the process by which needs are assessed is changing as early as next year (2024), specifically moving to a strength-based model that seeks to identify and develop the strengths of people with convictions. As a consequence of this, the information provided by this endpoint will also change.
Expand Down Expand Up @@ -688,7 +688,7 @@ paths:
get:
tags:
- risks
summary: Returns Risk of Serious Harm (ROSH) risks associated with a person. Returns only assessments completed in the last year.
summary: Returns Risk of Serious Harm (ROSH) risks associated with a person. Returns only assessments completed in the last year. This endpoint does not serve LAO (Limited Access Offender) data.
parameters:
- $ref: "#/components/parameters/hmppsId"
responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class WebClientWrapper(
headers: Map<String, String>,
upstreamApi: UpstreamApi,
requestBody: Map<String, Any?>? = null,
forbiddenAsError: Boolean = false,
): WebClientWrapperResponse<T> {
return try {
val responseData =
Expand All @@ -47,7 +48,7 @@ class WebClientWrapper(

WebClientWrapperResponse.Success(responseData)
} catch (exception: WebClientResponseException) {
getErrorType(exception, upstreamApi)
getErrorType(exception, upstreamApi, forbiddenAsError)
}
}

Expand All @@ -57,6 +58,7 @@ class WebClientWrapper(
headers: Map<String, String>,
upstreamApi: UpstreamApi,
requestBody: Map<String, Any?>? = null,
forbiddenAsError: Boolean = false,
): WebClientWrapperResponse<List<T>> {
return try {
val responseData =
Expand All @@ -67,7 +69,7 @@ class WebClientWrapper(

WebClientWrapperResponse.Success(responseData)
} catch (exception: WebClientResponseException) {
getErrorType(exception, upstreamApi)
getErrorType(exception, upstreamApi, forbiddenAsError)
}
}

Expand All @@ -92,10 +94,12 @@ class WebClientWrapper(
fun getErrorType(
exception: WebClientResponseException,
upstreamApi: UpstreamApi,
forbiddenAsError: Boolean = false,
): WebClientWrapperResponse.Error {
val errorType =
when (exception.statusCode) {
HttpStatus.NOT_FOUND -> UpstreamApiError.Type.ENTITY_NOT_FOUND
HttpStatus.FORBIDDEN -> if (forbiddenAsError) UpstreamApiError.Type.FORBIDDEN else throw exception
else -> throw exception
}
return WebClientWrapperResponse.Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AssessRisksAndNeedsGateway(
"/risks/crn/$id/predictors/all",
authenticationHeader(),
UpstreamApi.ASSESS_RISKS_AND_NEEDS,
forbiddenAsError = true,
)

return when (result) {
Expand Down Expand Up @@ -59,6 +60,7 @@ class AssessRisksAndNeedsGateway(
"/risks/crn/$id",
authenticationHeader(),
UpstreamApi.ASSESS_RISKS_AND_NEEDS,
forbiddenAsError = true,
)

return when (result) {
Expand All @@ -82,6 +84,7 @@ class AssessRisksAndNeedsGateway(
"/needs/crn/$id",
authenticationHeader(),
UpstreamApi.ASSESS_RISKS_AND_NEEDS,
forbiddenAsError = true,
)

return when (result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,13 @@ class GetRiskPredictorScoresForPersonTest(

response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND).shouldBeTrue()
}

it("returns a 403 FORBIDDEN status code when forbidden") {
assessRisksAndNeedsApiMockServer.stubGetRiskPredictorScoresForPerson(deliusCrn, "", HttpStatus.FORBIDDEN)

val response = assessRisksAndNeedsGateway.getRiskPredictorScoresForPerson(deliusCrn)

response.hasError(UpstreamApiError.Type.FORBIDDEN).shouldBeTrue()
}
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,18 @@ internal class GetNeedsForPersonServiceTest(
)
}

it("records upstream API error for probation offender search") {
it("records upstream 404 API error for probation offender search") {
val response = getNeedsForPersonService.execute(hmppsId)

response.hasErrorCausedBy(UpstreamApiError.Type.ENTITY_NOT_FOUND, UpstreamApi.PROBATION_OFFENDER_SEARCH).shouldBe(true)
}

it("records upstream 403 API error for probation offender search") {
val response = getNeedsForPersonService.execute(hmppsId)

response.hasErrorCausedBy(UpstreamApiError.Type.FORBIDDEN, UpstreamApi.ASSESS_RISKS_AND_NEEDS).shouldBe(true)
}

it("does not get needs from ARN") {
getNeedsForPersonService.execute(hmppsId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,20 @@ internal class GetRiskPredictorScoresForPersonServiceTest(
)
}

it("records upstream API error for probation offender search") {
it("records upstream 404 API error for probation offender search") {
val response = getRiskPredictorScoresForPersonService.execute(hmppsId)

response.hasErrorCausedBy(UpstreamApiError.Type.ENTITY_NOT_FOUND, UpstreamApi.PROBATION_OFFENDER_SEARCH).shouldBe(true)
}

it("records upstream 403 API error for probation offender search") {
val response = getRiskPredictorScoresForPersonService.execute(hmppsId)

response.data.isEmpty()
response.errors.shouldHaveSize(1)
response.hasErrorCausedBy(UpstreamApiError.Type.FORBIDDEN, UpstreamApi.ASSESS_RISKS_AND_NEEDS).shouldBe(true)
}

it("does not get risk predictor scores from ARN") {
getRiskPredictorScoresForPersonService.execute(hmppsId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import org.mockito.Mockito
import org.mockito.internal.verification.VerificationModeFactory
Expand Down Expand Up @@ -90,12 +91,20 @@ GetRiskSeriousHarmForPersonServiceTest(
)
}

it("records upstream API error for probation offender search") {
it("records upstream 404 API error for probation offender search") {
val response = getRiskSeriousHarmForPersonService.execute(hmppsId)

response.hasErrorCausedBy(UpstreamApiError.Type.ENTITY_NOT_FOUND, UpstreamApi.PROBATION_OFFENDER_SEARCH).shouldBe(true)
}

it("records upstream 403 API error for probation offender search") {
val response = getRiskSeriousHarmForPersonService.execute(hmppsId)

response.data.shouldBeNull()
response.errors.shouldHaveSize(1)
response.hasErrorCausedBy(UpstreamApiError.Type.FORBIDDEN, UpstreamApi.ASSESS_RISKS_AND_NEEDS).shouldBe(true)
}

it("does not get risks from ARN") {
getRiskSeriousHarmForPersonService.execute(hmppsId)

Expand Down
Loading