diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/PrisonApiClient.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/PrisonApiClient.kt index c178837..4f6e45e 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/PrisonApiClient.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/PrisonApiClient.kt @@ -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 { diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/ReferenceDataClient.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/ReferenceDataClient.kt new file mode 100644 index 0000000..8476dbc --- /dev/null +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/ReferenceDataClient.kt @@ -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> +} diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateBirthPlace.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateBirthPlace.kt similarity index 95% rename from src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateBirthPlace.kt rename to src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateBirthPlace.kt index 0d17796..38e3ea0 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateBirthPlace.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateBirthPlace.kt @@ -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 diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateNationality.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateNationality.kt similarity index 95% rename from src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateNationality.kt rename to src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateNationality.kt index c81a2d9..39d41ff 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateNationality.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateNationality.kt @@ -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 diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/response/ReferenceDataCode.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/response/ReferenceDataCode.kt new file mode 100644 index 0000000..04487c7 --- /dev/null +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/response/ReferenceDataCode.kt @@ -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, +) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/config/WebClientConfiguration.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/config/WebClientConfiguration.kt index 054fd70..ec27354 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/config/WebClientConfiguration.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/config/WebClientConfiguration.kt @@ -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 @@ -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) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt index 25267e1..2c69d73 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt @@ -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", @@ -180,5 +190,5 @@ class CorePersonRecordV1Resource( description = "The reference data domain", example = "COUNTRY", ) domain: String, - ): Collection = listOf() + ): ResponseEntity> = corePersonRecordService.getReferenceDataCodes(domain) } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt index 2a7505d..ec9600a 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt @@ -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 @@ -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> { + 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() + } + } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt index 1927b12..78a2ee4 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt @@ -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()) + assertThat(response).isEqualTo( + listOf( + ReferenceDataCodeDto("TEST_ONE", "ONE", "Code One", 99, true), + ReferenceDataCodeDto("TEST_TWO", "TWO", "Code Two", 99, true), + ), + ) } } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt index 1df7f5d..970da1f 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt @@ -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 @@ -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)) @@ -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" diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/integration/wiremock/PrisonApiMockServer.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/integration/wiremock/PrisonApiMockServer.kt index 7fe78aa..05c8004 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/integration/wiremock/PrisonApiMockServer.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/integration/wiremock/PrisonApiMockServer.kt @@ -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) { @@ -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( @@ -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()