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 e877af9..c178837 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 @@ -6,6 +6,7 @@ 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 @HttpExchange("/api/offenders") interface PrisonApiClient { @@ -14,4 +15,10 @@ interface PrisonApiClient { @PathVariable offenderNo: String, @RequestBody updateBirthPlace: UpdateBirthPlace, ): ResponseEntity + + @PutExchange("/{offenderNo}/nationality") + fun updateNationalityForWorkingName( + @PathVariable offenderNo: String, + @RequestBody updateNationality: UpdateNationality, + ): ResponseEntity } 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/dto/UpdateNationality.kt new file mode 100644 index 0000000..c81a2d9 --- /dev/null +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/dto/UpdateNationality.kt @@ -0,0 +1,9 @@ +package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "Update to prisoner nationality") +data class UpdateNationality( + @Schema(description = "Nationality code", example = "BRIT", required = true, nullable = true) + val nationality: String?, +) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/dto/request/CorePersonRecordV1UpdateRequestDto.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/dto/request/CorePersonRecordV1UpdateRequestDto.kt index a000269..35dbb4e 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/dto/request/CorePersonRecordV1UpdateRequestDto.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/dto/request/CorePersonRecordV1UpdateRequestDto.kt @@ -16,6 +16,7 @@ import java.time.LocalDate JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.BIRTHPLACE, value = BirthplaceUpdateDto::class), JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.COUNTRY_OF_BIRTH, value = CountryOfBirthUpdateDto::class), JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.DATE_OF_BIRTH, value = DateOfBirthUpdateDto::class), + JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.NATIONALITY, value = NationalityUpdateDto::class), ) @Schema(description = "Core Person Record V1 update request base") sealed class CorePersonRecordV1UpdateRequestDto { @@ -26,6 +27,7 @@ sealed class CorePersonRecordV1UpdateRequestDto { const val BIRTHPLACE = "BIRTHPLACE" const val COUNTRY_OF_BIRTH = "COUNTRY_OF_BIRTH" const val DATE_OF_BIRTH = "DATE_OF_BIRTH" + const val NATIONALITY = "NATIONALITY" } } @@ -73,7 +75,7 @@ data class DateOfBirthUpdateDto( @Schema(description = "Core Person Record V1 country of birth update request") data class CountryOfBirthUpdateDto( @Schema( - description = "The new value for the country of brith field", + description = "The new value for the country of birth field", example = "UK", required = true, nullable = true, @@ -89,3 +91,23 @@ data class CountryOfBirthUpdateDto( ) override val fieldName: String = COUNTRY_OF_BIRTH } + +@Schema(description = "Core Person Record V1 nationality update request") +data class NationalityUpdateDto( + @Schema( + description = "The new value for the nationality field", + example = "BRIT", + required = true, + nullable = true, + ) + override val value: String?, +) : CorePersonRecordV1UpdateRequestDto() { + @Schema( + type = "String", + description = "The field to be updated", + allowableValues = [NATIONALITY], + required = true, + nullable = false, + ) + override val fieldName: String = COUNTRY_OF_BIRTH +} 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 660ff86..2a7505d 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 @@ -3,8 +3,10 @@ package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.servi 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.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 import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException @Service @@ -15,6 +17,7 @@ class CorePersonRecordService( fun updateCorePersonRecordField(prisonerNumber: String, updateRequestDto: CorePersonRecordV1UpdateRequestDto) { when (updateRequestDto) { 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.") } } 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 13bf570..1927b12 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 @@ -68,6 +68,26 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() { .exchange() .expectStatus().isNoContent } + + @Test + fun `can patch core person record nationality by prisoner number`() { + webTestClient.patch().uri("/v1/core-person-record?prisonerNumber=$PRISONER_NUMBER") + .contentType(MediaType.APPLICATION_JSON) + .headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE))) + .bodyValue(NATIONALITY_PATCH_REQUEST_BODY) + .exchange() + .expectStatus().isNoContent + } + + @Test + fun `patch core person record nationality accepts null value`() { + webTestClient.patch().uri("/v1/core-person-record?prisonerNumber=$PRISONER_NUMBER") + .contentType(MediaType.APPLICATION_JSON) + .headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE))) + .bodyValue(NULL_NATIONALITY_PATCH_REQUEST_BODY) + .exchange() + .expectStatus().isNoContent + } } @Nested @@ -191,6 +211,24 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() { } """.trimIndent() + val NATIONALITY_PATCH_REQUEST_BODY = + // language=json + """ + { + "fieldName": "NATIONALITY", + "value": "BRIT" + } + """.trimIndent() + + val NULL_NATIONALITY_PATCH_REQUEST_BODY = + // language=json + """ + { + "fieldName": "NATIONALITY", + "value": null + } + """.trimIndent() + val VALID_PATCH_REQUEST_BODY = BIRTHPLACE_PATCH_REQUEST_BODY val MULTIPART_FILE: MultipartFile = MockMultipartFile( 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 79e0b1e..1df7f5d 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 @@ -14,8 +14,10 @@ import org.mockito.kotlin.whenever 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.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 import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException import java.time.LocalDate @@ -35,7 +37,10 @@ class CorePersonRecordServiceTest { @BeforeEach fun beforeEach() { - whenever(prisonApiClient.updateBirthPlaceForWorkingName(PRISONER_NUMBER, TEST_BIRTHPLACE_BODY)).thenReturn(ResponseEntity.noContent().build()) + whenever(prisonApiClient.updateBirthPlaceForWorkingName(PRISONER_NUMBER, TEST_BIRTHPLACE_BODY)) + .thenReturn(ResponseEntity.noContent().build()) + whenever(prisonApiClient.updateNationalityForWorkingName(PRISONER_NUMBER, TEST_NATIONALITY_BODY)) + .thenReturn(ResponseEntity.noContent().build()) } @Nested @@ -45,6 +50,11 @@ class CorePersonRecordServiceTest { underTest.updateCorePersonRecordField(PRISONER_NUMBER, BirthplaceUpdateDto(TEST_BIRTHPLACE_VALUE)) } + @Test + fun `can update the nationality field`() { + underTest.updateCorePersonRecordField(PRISONER_NUMBER, NationalityUpdateDto(TEST_NATIONALITY_VALUE)) + } + @Test fun `throws an exception if the field type is not supported`() { assertThrows { @@ -56,6 +66,8 @@ class CorePersonRecordServiceTest { private companion object { const val PRISONER_NUMBER = "A1234AA" const val TEST_BIRTHPLACE_VALUE = "London" + const val TEST_NATIONALITY_VALUE = "BRIT" val TEST_BIRTHPLACE_BODY = UpdateBirthPlace("London") + val TEST_NATIONALITY_BODY = UpdateNationality("BRIT") } } 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 ad360c5..7fe78aa 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 @@ -33,31 +33,36 @@ class PrisonApiMockServer : WireMockServer(8082) { ) } - fun stubUpdateBirthPlaceForWorkingName(prisonerNumber: String = PRISONER_NUMBER) { - stubFor( - put(urlPathMatching("/api/offenders/$prisonerNumber/birth-place")).willReturn( - aResponse().withHeader("Content-Type", "application/json") - .withStatus(HttpStatus.NO_CONTENT.value()), - ), + fun stubUpdateBirthPlaceForWorkingName() { + val endpoint = "birth-place" + stubOffenderEndpoint(endpoint, HttpStatus.NO_CONTENT, PRISONER_NUMBER) + stubOffenderEndpoint(endpoint, HttpStatus.INTERNAL_SERVER_ERROR, PRISONER_NUMBER_THROW_EXCEPTION) + stubOffenderEndpoint( + endpoint, + HttpStatus.NOT_FOUND, + PRISONER_NUMBER_NOT_FOUND, + PRISON_API_NOT_FOUND_RESPONSE.trimIndent(), ) } - fun stubUpdateBirthPlaceForWorkingNameException(prisonerNumber: String = PRISONER_NUMBER_THROW_EXCEPTION) { - stubFor( - put(urlPathMatching("/api/offenders/$prisonerNumber/birth-place")).willReturn( - aResponse().withHeader("Content-Type", "application/json") - .withStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()), - ), + fun stubUpdateNationalityForWorkingName() { + val endpoint = "nationality" + stubOffenderEndpoint(endpoint, HttpStatus.NO_CONTENT, PRISONER_NUMBER) + stubOffenderEndpoint(endpoint, HttpStatus.INTERNAL_SERVER_ERROR, PRISONER_NUMBER_THROW_EXCEPTION) + stubOffenderEndpoint( + endpoint, + HttpStatus.NOT_FOUND, + PRISONER_NUMBER_NOT_FOUND, + PRISON_API_NOT_FOUND_RESPONSE.trimIndent(), ) } - fun stubUpdateBirthPlaceForWorkingNameNotFound(prisonerNumber: String = PRISONER_NUMBER_NOT_FOUND) { + private fun stubOffenderEndpoint(endpoint: String, status: HttpStatus, prisonerNumber: String, body: String? = null) { stubFor( - put(urlPathMatching("/api/offenders/$prisonerNumber/birth-place")).willReturn( + put(urlPathMatching("/api/offenders/$prisonerNumber/$endpoint")).willReturn( aResponse().withHeader("Content-Type", "application/json") - .withStatus(HttpStatus.NOT_FOUND.value()).withBody( - PRISON_API_NOT_FOUND_RESPONSE.trimIndent(), - ), + .withStatus(status.value()) + .withBody(body), ), ) } @@ -73,8 +78,7 @@ class PrisonApiExtension : BeforeAllCallback, AfterAllCallback, BeforeEachCallba override fun beforeEach(context: ExtensionContext) { prisonApi.resetAll() prisonApi.stubUpdateBirthPlaceForWorkingName() - prisonApi.stubUpdateBirthPlaceForWorkingNameException() - prisonApi.stubUpdateBirthPlaceForWorkingNameNotFound() + prisonApi.stubUpdateNationalityForWorkingName() } override fun afterAll(context: ExtensionContext): Unit = prisonApi.stop()