Skip to content

Commit b8099bd

Browse files
authored
CDPS-1115 Allow update to other nationalities (#22)
* CDPS-1115 Allow update to other nationalities * CDPS-1115 Remove PATCH support for nationality and other nationalities
1 parent f883e9a commit b8099bd

File tree

6 files changed

+151
-66
lines changed

6 files changed

+151
-66
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/request/UpdateNationality.kt

+2
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema
66
data class UpdateNationality(
77
@Schema(description = "Nationality code", example = "BRIT", required = true, nullable = true)
88
val nationality: String?,
9+
@Schema(description = "Other nationalities", example = "French", required = true, nullable = true)
10+
val otherNationalities: String?,
911
)

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/dto/request/CorePersonRecordV1UpdateRequestDto.kt

-22
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import java.time.LocalDate
1616
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.BIRTHPLACE, value = BirthplaceUpdateDto::class),
1717
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.COUNTRY_OF_BIRTH, value = CountryOfBirthUpdateDto::class),
1818
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.DATE_OF_BIRTH, value = DateOfBirthUpdateDto::class),
19-
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.NATIONALITY, value = NationalityUpdateDto::class),
2019
)
2120
@Schema(description = "Core Person Record V1 update request base")
2221
sealed class CorePersonRecordV1UpdateRequestDto {
@@ -27,7 +26,6 @@ sealed class CorePersonRecordV1UpdateRequestDto {
2726
const val BIRTHPLACE = "BIRTHPLACE"
2827
const val COUNTRY_OF_BIRTH = "COUNTRY_OF_BIRTH"
2928
const val DATE_OF_BIRTH = "DATE_OF_BIRTH"
30-
const val NATIONALITY = "NATIONALITY"
3129
}
3230
}
3331

@@ -91,23 +89,3 @@ data class CountryOfBirthUpdateDto(
9189
)
9290
override val fieldName: String = COUNTRY_OF_BIRTH
9391
}
94-
95-
@Schema(description = "Core Person Record V1 nationality update request")
96-
data class NationalityUpdateDto(
97-
@Schema(
98-
description = "The new value for the nationality field",
99-
example = "BRIT",
100-
required = true,
101-
nullable = true,
102-
)
103-
override val value: String?,
104-
) : CorePersonRecordV1UpdateRequestDto() {
105-
@Schema(
106-
type = "String",
107-
description = "The field to be updated",
108-
allowableValues = [NATIONALITY],
109-
required = true,
110-
nullable = false,
111-
)
112-
override val fieldName: String = COUNTRY_OF_BIRTH
113-
}

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt

+50
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.springframework.web.multipart.MultipartFile
2727
import uk.gov.justice.digital.hmpps.personintegrationapi.common.annotation.ValidPrisonerNumber
2828
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.CreateMilitaryRecord
2929
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateMilitaryRecord
30+
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
3031
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
3132
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.CorePersonRecordRoleConstants
3233
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response.MilitaryRecordDto
@@ -330,4 +331,53 @@ class CorePersonRecordV1Resource(
330331
@RequestParam(required = true) @Valid @ValidPrisonerNumber prisonerNumber: String,
331332
@RequestBody(required = true) @Valid createMilitaryRecord: CreateMilitaryRecord,
332333
): ResponseEntity<Void> = corePersonRecordService.createMilitaryRecord(prisonerNumber, createMilitaryRecord)
334+
335+
@PutMapping("/nationality")
336+
@ResponseStatus(HttpStatus.NO_CONTENT)
337+
@Operation(
338+
summary = "Update the nationality for a given prisoner number",
339+
description = "Updates the nationality and other nationalities info. " +
340+
"Requires role `${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}`",
341+
responses = [
342+
ApiResponse(
343+
responseCode = "204",
344+
description = "Nationality successfully updated.",
345+
),
346+
ApiResponse(
347+
responseCode = "401",
348+
description = "Unauthorized to access this endpoint",
349+
content = [
350+
Content(
351+
mediaType = MediaType.APPLICATION_JSON_VALUE,
352+
schema = Schema(implementation = ErrorResponse::class),
353+
),
354+
],
355+
),
356+
ApiResponse(
357+
responseCode = "403",
358+
description = "Missing required role. Requires ${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}",
359+
content = [
360+
Content(
361+
mediaType = MediaType.APPLICATION_JSON_VALUE,
362+
schema = Schema(implementation = ErrorResponse::class),
363+
),
364+
],
365+
),
366+
ApiResponse(
367+
responseCode = "404",
368+
description = "Record not found",
369+
content = [
370+
Content(
371+
mediaType = MediaType.APPLICATION_JSON_VALUE,
372+
schema = Schema(implementation = ErrorResponse::class),
373+
),
374+
],
375+
),
376+
],
377+
)
378+
@PreAuthorize("hasRole('${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}')")
379+
fun updateNationality(
380+
@RequestParam(required = true) @Valid @ValidPrisonerNumber prisonerNumber: String,
381+
@RequestBody(required = true) @Valid updateNationality: UpdateNationality,
382+
): ResponseEntity<Void> = corePersonRecordService.updateNationality(prisonerNumber, updateNationality)
333383
}

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt

+2-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.re
1414
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
1515
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CorePersonRecordV1UpdateRequestDto
1616
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CountryOfBirthUpdateDto
17-
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.NationalityUpdateDto
1817
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException
1918

2019
@Service
@@ -35,11 +34,6 @@ class CorePersonRecordService(
3534
UpdateBirthCountry(updateRequestDto.value),
3635
)
3736

38-
is NationalityUpdateDto -> prisonApiClient.updateNationalityForWorkingName(
39-
prisonerNumber,
40-
UpdateNationality(updateRequestDto.value),
41-
)
42-
4337
else -> throw UnknownCorePersonFieldException("Field '${updateRequestDto.fieldName}' cannot be updated.")
4438
}
4539
}
@@ -103,4 +97,6 @@ class CorePersonRecordService(
10397
fun updateMilitaryRecord(prisonerNumber: String, updateMilitaryRecord: UpdateMilitaryRecord): ResponseEntity<Void> = prisonApiClient.updateMilitaryRecord(prisonerNumber, updateMilitaryRecord)
10498

10599
fun createMilitaryRecord(prisonerNumber: String, createMilitaryRecord: CreateMilitaryRecord): ResponseEntity<Void> = prisonApiClient.createMilitaryRecord(prisonerNumber, createMilitaryRecord)
100+
101+
fun updateNationality(prisonerNumber: String, updateNationality: UpdateNationality): ResponseEntity<Void> = prisonApiClient.updateNationalityForWorkingName(prisonerNumber, updateNationality)
106102
}

src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt

+69-28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.springframework.web.multipart.MultipartFile
1212
import org.springframework.web.reactive.function.BodyInserters
1313
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.CreateMilitaryRecord
1414
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateMilitaryRecord
15+
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
1516
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
1617
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.CorePersonRecordRoleConstants
1718
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response.MilitaryRecordDto
@@ -73,16 +74,6 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
7374
expectSuccessfulRequestWith(NULL_COUNTRY_OF_BIRTH_PATCH_REQUEST_BODY)
7475
}
7576

76-
@Test
77-
fun `can patch core person record nationality by prisoner number`() {
78-
expectSuccessfulRequestWith(NATIONALITY_PATCH_REQUEST_BODY)
79-
}
80-
81-
@Test
82-
fun `patch core person record nationality accepts null value`() {
83-
expectSuccessfulRequestWith(NULL_NATIONALITY_PATCH_REQUEST_BODY)
84-
}
85-
8677
private fun expectSuccessfulRequestWith(body: Any) {
8778
webTestClient.patch().uri("/v1/core-person-record?prisonerNumber=$PRISONER_NUMBER")
8879
.contentType(MediaType.APPLICATION_JSON)
@@ -399,6 +390,72 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
399390
}
400391
}
401392

393+
@DisplayName("PUT v1/core-person-record/nationality")
394+
@Nested
395+
inner class UpdatePrisonerNationality {
396+
397+
@Nested
398+
inner class Security {
399+
@Test
400+
fun `access forbidden when no authority`() {
401+
webTestClient.put().uri("/v1/core-person-record/nationality?prisonerNumber=$PRISONER_NUMBER")
402+
.contentType(MediaType.APPLICATION_JSON)
403+
.bodyValue(UPDATE_NATIONALITY)
404+
.exchange()
405+
.expectStatus().isUnauthorized
406+
}
407+
408+
@Test
409+
fun `access forbidden with wrong role`() {
410+
webTestClient.put().uri("/v1/core-person-record/nationality?prisonerNumber=$PRISONER_NUMBER")
411+
.contentType(MediaType.APPLICATION_JSON)
412+
.headers(setAuthorisation(roles = listOf("ROLE_IS_WRONG")))
413+
.bodyValue(UPDATE_NATIONALITY)
414+
.exchange()
415+
.expectStatus().isForbidden
416+
}
417+
}
418+
419+
@Nested
420+
inner class HappyPath {
421+
422+
@Test
423+
fun `update nationality`() {
424+
webTestClient.put().uri("/v1/core-person-record/nationality?prisonerNumber=$PRISONER_NUMBER")
425+
.contentType(MediaType.APPLICATION_JSON)
426+
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE)))
427+
.bodyValue(CREATE_MILITARY_RECORD)
428+
.exchange()
429+
.expectStatus().isNoContent
430+
}
431+
432+
@Test
433+
fun `update nationality accepts null values`() {
434+
webTestClient.put().uri("/v1/core-person-record/nationality?prisonerNumber=$PRISONER_NUMBER")
435+
.contentType(MediaType.APPLICATION_JSON)
436+
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE)))
437+
.bodyValue(UpdateNationality(null, null))
438+
.exchange()
439+
.expectStatus().isNoContent
440+
}
441+
}
442+
443+
@Nested
444+
inner class NotFound {
445+
446+
@Test
447+
fun `handles a 404 not found response from downstream api`() {
448+
webTestClient.post().uri("/v1/core-person-record/military-records?prisonerNumber=$PRISONER_NUMBER_NOT_FOUND")
449+
.contentType(MediaType.APPLICATION_JSON)
450+
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE)))
451+
.bodyValue(CREATE_MILITARY_RECORD)
452+
.exchange()
453+
.expectStatus().isNotFound
454+
.expectBody().json(PRISON_API_NOT_FOUND_RESPONSE.trimIndent())
455+
}
456+
}
457+
}
458+
402459
private companion object {
403460

404461
val BIRTHPLACE_PATCH_REQUEST_BODY =
@@ -437,24 +494,6 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
437494
}
438495
""".trimIndent()
439496

440-
val NATIONALITY_PATCH_REQUEST_BODY =
441-
// language=json
442-
"""
443-
{
444-
"fieldName": "NATIONALITY",
445-
"value": "BRIT"
446-
}
447-
""".trimIndent()
448-
449-
val NULL_NATIONALITY_PATCH_REQUEST_BODY =
450-
// language=json
451-
"""
452-
{
453-
"fieldName": "NATIONALITY",
454-
"value": null
455-
}
456-
""".trimIndent()
457-
458497
val VALID_PATCH_REQUEST_BODY = BIRTHPLACE_PATCH_REQUEST_BODY
459498

460499
val MULTIPART_FILE: MultipartFile = MockMultipartFile(
@@ -494,5 +533,7 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
494533
militaryBranchCode = "NAV",
495534
selectiveServicesFlag = false,
496535
)
536+
537+
val UPDATE_NATIONALITY = UpdateNationality("BRIT", "French")
497538
}
498539
}

src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt

+28-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.re
3131
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
3232
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CountryOfBirthUpdateDto
3333
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.DateOfBirthUpdateDto
34-
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.NationalityUpdateDto
3534
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException
3635
import java.time.LocalDate
3736

@@ -60,8 +59,6 @@ class CorePersonRecordServiceTest {
6059
.thenReturn(ResponseEntity.noContent().build())
6160
whenever(prisonApiClient.updateBirthCountryForWorkingName(PRISONER_NUMBER, TEST_COUNTRY_OF_BIRTH_BODY))
6261
.thenReturn(ResponseEntity.noContent().build())
63-
whenever(prisonApiClient.updateNationalityForWorkingName(PRISONER_NUMBER, TEST_NATIONALITY_BODY))
64-
.thenReturn(ResponseEntity.noContent().build())
6562
}
6663

6764
@Test
@@ -74,11 +71,6 @@ class CorePersonRecordServiceTest {
7471
underTest.updateCorePersonRecordField(PRISONER_NUMBER, CountryOfBirthUpdateDto(TEST_COUNTRY_OF_BIRTH_VALUE))
7572
}
7673

77-
@Test
78-
fun `can update the nationality field`() {
79-
underTest.updateCorePersonRecordField(PRISONER_NUMBER, NationalityUpdateDto(TEST_NATIONALITY_VALUE))
80-
}
81-
8274
@Test
8375
fun `throws an exception if the field type is not supported`() {
8476
assertThrows<UnknownCorePersonFieldException> {
@@ -254,14 +246,38 @@ class CorePersonRecordServiceTest {
254246
}
255247
}
256248

249+
@Nested
250+
inner class UpdateNationality {
251+
private val prisonerNumber = "A1234AA"
252+
253+
@Test
254+
fun `can update the nationality field`() {
255+
whenever(prisonApiClient.updateNationalityForWorkingName(PRISONER_NUMBER, UPDATE_NATIONALITY))
256+
.thenReturn(ResponseEntity.noContent().build())
257+
258+
val response = underTest.updateNationality(PRISONER_NUMBER, UPDATE_NATIONALITY)
259+
assertThat(response.statusCode).isEqualTo(HttpStatus.NO_CONTENT)
260+
}
261+
262+
@ParameterizedTest(name = "{0}")
263+
@ValueSource(ints = [400, 401, 403, 404, 422, 500])
264+
fun `propagates non-2xx status codes`(status: Int) {
265+
whenever(prisonApiClient.updateNationalityForWorkingName(prisonerNumber, UPDATE_NATIONALITY)).thenReturn(
266+
ResponseEntity.status(status).build(),
267+
)
268+
269+
val response = underTest.updateNationality(prisonerNumber, UPDATE_NATIONALITY)
270+
assertThat(response.statusCode.value()).isEqualTo(status)
271+
}
272+
}
273+
257274
private companion object {
258275
const val PRISONER_NUMBER = "A1234AA"
259276
const val TEST_BIRTHPLACE_VALUE = "London"
260277
const val TEST_COUNTRY_OF_BIRTH_VALUE = "ENG"
261-
const val TEST_NATIONALITY_VALUE = "BRIT"
278+
const val TEST_OTHER_NATIONALITIES_VALUE = "French"
262279
val TEST_BIRTHPLACE_BODY = UpdateBirthPlace("London")
263280
val TEST_COUNTRY_OF_BIRTH_BODY = UpdateBirthCountry("ENG")
264-
val TEST_NATIONALITY_BODY = UpdateNationality("BRIT")
265281

266282
val UPDATE_MILITARY_RECORD = UpdateMilitaryRecord(
267283
bookingId = -1L,
@@ -285,5 +301,7 @@ class CorePersonRecordServiceTest {
285301
militaryBranchCode = "NAV",
286302
selectiveServicesFlag = false,
287303
)
304+
305+
val UPDATE_NATIONALITY = UpdateNationality("BRIT", "French")
288306
}
289307
}

0 commit comments

Comments
 (0)