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

CDPS-1075 Get reference data codes from Prison API #15

Merged
merged 1 commit into from
Dec 18, 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
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
Loading