Skip to content

Commit

Permalink
CDPS-1075 Get reference data codes from Prison API (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottrowley authored Dec 18, 2024
1 parent f1f6d00 commit a30f7c1
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.service.annotation.HttpExchange
import org.springframework.web.service.annotation.PutExchange
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateNationality
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality

@HttpExchange("/api/offenders")
interface PrisonApiClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.service.annotation.GetExchange
import org.springframework.web.service.annotation.HttpExchange
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response.ReferenceDataCode

@HttpExchange("/api/reference-domains")
interface ReferenceDataClient {
@GetExchange("/domains/{domain}/all-codes")
fun getReferenceDataByDomain(
@PathVariable domain: String,
): ResponseEntity<List<ReferenceDataCode>>
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request

import io.swagger.v3.oas.annotations.media.Schema

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request

import io.swagger.v3.oas.annotations.media.Schema

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response

data class ReferenceDataCode(
val domain: String,
val code: String,
val description: String,
val activeFlag: String,
val listSeq: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.springframework.web.reactive.function.client.support.WebClientAdapter
import org.springframework.web.service.invoker.HttpServiceProxyFactory
import reactor.netty.http.client.HttpClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.PrisonApiClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.ReferenceDataClient
import uk.gov.justice.digital.hmpps.personintegrationapi.config.UserEnhancedOAuth2ClientCredentialGrantRequestConverter
import uk.gov.justice.hmpps.kotlin.auth.healthWebClient
import java.time.Duration
Expand Down Expand Up @@ -67,6 +68,16 @@ class WebClientConfiguration(
return client
}

@Bean
@DependsOn("prisonApiWebClient")
fun referenceDataClient(prisonApiWebClient: WebClient): ReferenceDataClient {
val factory =
HttpServiceProxyFactory.builderFor(WebClientAdapter.create(prisonApiWebClient)).build()
val client = factory.createClient(ReferenceDataClient::class.java)

return client
}

private fun authorizedClientManagerUserEnhanced(clients: ClientRegistrationRepository?): OAuth2AuthorizedClientManager {
val service: OAuth2AuthorizedClientService = InMemoryOAuth2AuthorizedClientService(clients)
val manager = AuthorizedClientServiceOAuth2AuthorizedClientManager(clients, service)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ class CorePersonRecordV1Resource(
description = "Unauthorised, requires a valid Oauth2 token",
content = [Content(schema = Schema(implementation = ErrorResponse::class))],
),
ApiResponse(
responseCode = "403",
description = "Missing required role. Requires ${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE} or ${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}.",
content = [
Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = Schema(implementation = ErrorResponse::class),
),
],
),
ApiResponse(
responseCode = "404",
description = "Not found, the reference data domain was not found",
Expand All @@ -180,5 +190,5 @@ class CorePersonRecordV1Resource(
description = "The reference data domain",
example = "COUNTRY",
) domain: String,
): Collection<ReferenceDataCodeDto> = listOf()
): ResponseEntity<List<ReferenceDataCodeDto>> = corePersonRecordService.getReferenceDataCodes(domain)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.service

import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.PrisonApiClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateNationality
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.ReferenceDataClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CorePersonRecordV1UpdateRequestDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.NationalityUpdateDto
Expand All @@ -12,13 +15,41 @@ import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.except
@Service
class CorePersonRecordService(
private val prisonApiClient: PrisonApiClient,
private val referenceDataClient: ReferenceDataClient,
) {

fun updateCorePersonRecordField(prisonerNumber: String, updateRequestDto: CorePersonRecordV1UpdateRequestDto) {
when (updateRequestDto) {
is BirthplaceUpdateDto -> prisonApiClient.updateBirthPlaceForWorkingName(prisonerNumber, UpdateBirthPlace(updateRequestDto.value))
is NationalityUpdateDto -> prisonApiClient.updateNationalityForWorkingName(prisonerNumber, UpdateNationality(updateRequestDto.value))
is BirthplaceUpdateDto -> prisonApiClient.updateBirthPlaceForWorkingName(
prisonerNumber,
UpdateBirthPlace(updateRequestDto.value),
)

is NationalityUpdateDto -> prisonApiClient.updateNationalityForWorkingName(
prisonerNumber,
UpdateNationality(updateRequestDto.value),
)

else -> throw UnknownCorePersonFieldException("Field '${updateRequestDto.fieldName}' cannot be updated.")
}
}

fun getReferenceDataCodes(domain: String): ResponseEntity<List<ReferenceDataCodeDto>> {
val response = referenceDataClient.getReferenceDataByDomain(domain)

if (response.statusCode.is2xxSuccessful) {
val mappedResponse = response.body?.map {
ReferenceDataCodeDto(
"${domain}_${it.code}",
it.code,
it.description,
it.listSeq,
it.activeFlag == "Y",
)
}
return ResponseEntity.ok(mappedResponse)
} else {
return ResponseEntity.status(response.statusCode).build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,22 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
inner class HappyPath {

@Test
fun `can update core person record profile image by prisoner number`() {
fun `can get reference data codes by domain`() {
val domain = "TEST"
val response =
webTestClient.get().uri("/v1/core-person-record/reference-data/domain/$TEST_DOMAIN/codes")
webTestClient.get().uri("/v1/core-person-record/reference-data/domain/$domain/codes")
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE)))
.exchange()
.expectStatus().isOk
.expectBodyList(ReferenceDataCodeDto::class.java)
.returnResult().responseBody

assertThat(response).isEqualTo(emptyList<ReferenceDataCodeDto>())
assertThat(response).isEqualTo(
listOf(
ReferenceDataCodeDto("TEST_ONE", "ONE", "Code One", 99, true),
ReferenceDataCodeDto("TEST_TWO", "TWO", "Code Two", 99, true),
),
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.service

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.reset
import org.mockito.kotlin.whenever
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.PrisonApiClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateNationality
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.ReferenceDataClient
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateBirthPlace
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response.ReferenceDataCode
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.DateOfBirthUpdateDto
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.NationalityUpdateDto
Expand All @@ -27,24 +34,27 @@ class CorePersonRecordServiceTest {
@Mock
lateinit var prisonApiClient: PrisonApiClient

@Mock
lateinit var referenceDataClient: ReferenceDataClient

@InjectMocks
lateinit var underTest: CorePersonRecordService

@AfterEach
fun afterEach() {
reset(prisonApiClient)
}

@BeforeEach
fun beforeEach() {
whenever(prisonApiClient.updateBirthPlaceForWorkingName(PRISONER_NUMBER, TEST_BIRTHPLACE_BODY))
.thenReturn(ResponseEntity.noContent().build())
whenever(prisonApiClient.updateNationalityForWorkingName(PRISONER_NUMBER, TEST_NATIONALITY_BODY))
.thenReturn(ResponseEntity.noContent().build())
reset(prisonApiClient, referenceDataClient)
}

@Nested
inner class UpdateCorePersonRecordField {
@BeforeEach
fun beforeEach() {
whenever(prisonApiClient.updateBirthPlaceForWorkingName(PRISONER_NUMBER, TEST_BIRTHPLACE_BODY))
.thenReturn(ResponseEntity.noContent().build())
whenever(prisonApiClient.updateNationalityForWorkingName(PRISONER_NUMBER, TEST_NATIONALITY_BODY))
.thenReturn(ResponseEntity.noContent().build())
}

@Test
fun `can update the birthplace field`() {
underTest.updateCorePersonRecordField(PRISONER_NUMBER, BirthplaceUpdateDto(TEST_BIRTHPLACE_VALUE))
Expand All @@ -63,6 +73,43 @@ class CorePersonRecordServiceTest {
}
}

@Nested
inner class ReferenceData {
private val domain = "TEST"

@Test
fun `Can retrieve reference data codes`() {
val referenceCodes = listOf(
ReferenceDataCode(domain, "CODE1", "Code one", "Y", 1),
ReferenceDataCode(domain, "CODE2", "Code two", "Y", 2),
ReferenceDataCode(domain, "CODE3", "Code three", "F", 3),
)
val expected = listOf(
ReferenceDataCodeDto("TEST_CODE1", "CODE1", "Code one", 1, true),
ReferenceDataCodeDto("TEST_CODE2", "CODE2", "Code two", 2, true),
ReferenceDataCodeDto("TEST_CODE3", "CODE3", "Code three", 3, false),
)
whenever(referenceDataClient.getReferenceDataByDomain(domain)).thenReturn(
ResponseEntity.ok(referenceCodes),
)

val response = underTest.getReferenceDataCodes(domain)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.body).isEqualTo(expected)
}

@ParameterizedTest(name = "{0}")
@ValueSource(ints = [400, 401, 403, 404, 422, 500])
fun `Propagates non-2xx status codes`(status: Int) {
whenever(referenceDataClient.getReferenceDataByDomain(domain)).thenReturn(
ResponseEntity.status(status).build(),
)

val response = underTest.getReferenceDataCodes(domain)
assertThat(response.statusCode.value()).isEqualTo(status)
}
}

private companion object {
const val PRISONER_NUMBER = "A1234AA"
const val TEST_BIRTHPLACE_VALUE = "London"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ internal const val PRISON_API_NOT_FOUND_RESPONSE = """
"developerMessage": "Prisoner not found"
}
"""
internal const val PRISON_API_REFERENCE_CODES = """
[
{
"domain": "TEST",
"code": "ONE",
"description": "Code One",
"activeFlag": "Y",
"listSeq": 99,
"subCodes": []
},
{
"domain": "TEST",
"code": "TWO",
"description": "Code Two",
"activeFlag": "Y",
"listSeq": 99,
"subCodes": []
}
]
"""

class PrisonApiMockServer : WireMockServer(8082) {
fun stubHealthPing(status: Int) {
Expand Down Expand Up @@ -57,6 +77,16 @@ class PrisonApiMockServer : WireMockServer(8082) {
)
}

fun stubReferenceDataCodes(domain: String = "TEST", body: String = PRISON_API_REFERENCE_CODES) {
stubFor(
get(urlPathMatching("/api/reference-domains/domains/$domain/all-codes")).willReturn(
aResponse().withHeader("Content-Type", "application/json")
.withStatus(HttpStatus.OK.value())
.withBody(body),
),
)
}

private fun stubOffenderEndpoint(endpoint: String, status: HttpStatus, prisonerNumber: String, body: String? = null) {
stubFor(
put(urlPathMatching("/api/offenders/$prisonerNumber/$endpoint")).willReturn(
Expand All @@ -79,6 +109,7 @@ class PrisonApiExtension : BeforeAllCallback, AfterAllCallback, BeforeEachCallba
prisonApi.resetAll()
prisonApi.stubUpdateBirthPlaceForWorkingName()
prisonApi.stubUpdateNationalityForWorkingName()
prisonApi.stubReferenceDataCodes()
}

override fun afterAll(context: ExtensionContext): Unit = prisonApi.stop()
Expand Down

0 comments on commit a30f7c1

Please sign in to comment.