Skip to content

Commit

Permalink
3) Unit Test Validations on Request Models (#78)
Browse files Browse the repository at this point in the history
* Unit test validations

* add gender test

* Refactors

* Fixes

* Fixes

* Fixes

* Fixes

---------

Co-authored-by: Ewan Donovan <ewan.donovan@digital.justice.com>
  • Loading branch information
Ewan-Donovan and Ewan Donovan authored Feb 13, 2025
1 parent aece714 commit c23ac81
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue

enum class Gender(val description: String, val code: Int) {
MALE("MALE", 1),
FEMALE("FEMALE", 2),
NOT_KNOWN("NOT_KNOWN", 0),
NOT_SPECIFIED("NOT_SPECIFIED", 9),
;

@JsonValue
override fun toString(): String = description

companion object {
@JsonCreator
@JvmStatic
fun fromString(value: String): Gender = entries.find { it.description.equals(value, ignoreCase = true) }
?: throw IllegalArgumentException("Invalid gender value: $value")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ class LearnerEventsRequest(
familyName = familyName,
uln = uln,
dateOfBirth = dateOfBirth,
gender = gender?.value,
gender = gender?.code,
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request

import com.google.gson.annotations.SerializedName
import jakarta.validation.constraints.Past
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size
import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.lrsapi.request.LearnersLRSRequest
Expand All @@ -15,6 +16,7 @@ data class LearnersRequest(
@SerializedName("familyName")
val familyName: String,

@field:Past
@SerializedName("dateOfBirth")
val dateOfBirth: LocalDate,

Expand Down Expand Up @@ -44,18 +46,11 @@ data class LearnersRequest(
givenName = givenName,
familyName = familyName,
dateOfBirth = dateOfBirth,
gender = gender.value,
gender = gender.code,
lastKnownPostCode = lastKnownPostCode,
previousFamilyName = previousFamilyName,
schoolAtAge16 = schoolAtAge16,
placeOfBirth = placeOfBirth,
emailAddress = emailAddress,
)
}

enum class Gender(val value: Int) {
MALE(1),
FEMALE(2),
NOT_KNOWN(0),
NOT_SPECIFIED(9),
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(findLearnerByDemographicsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -87,6 +88,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(findLearnerByDemographicsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -122,6 +124,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learner-events")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(learnerEventsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -160,6 +163,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(findLearnerByDemographicsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -198,6 +202,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(findLearnerByDemographicsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -236,6 +241,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(findLearnerByDemographicsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -270,6 +276,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learner-events")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(learnerEventsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand Down Expand Up @@ -304,6 +311,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learner-events")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.bodyValue(learnerEventsRequest)
.accept(MediaType.parseMediaType("application/json"))
.exchange()
Expand All @@ -323,7 +331,7 @@ class ValidationTest : IntegrationTestBase() {
HttpStatus.BAD_REQUEST,
"Unreadable HTTP message",
"Unreadable HTTP message",
"JSON parse error: Cannot deserialize value of type `uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.Gender` from String \"TESTINGENUM\": not one of the values accepted for Enum class: [NOT_SPECIFIED, MALE, NOT_KNOWN, FEMALE]",
"JSON parse error: Cannot construct instance of `uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.Gender`, problem: Invalid gender value: TESTINGENUM",
"Unreadable HTTP message",
)

Expand All @@ -338,6 +346,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(findLearnerByDemographicsRequest)
.exchange()
Expand All @@ -357,7 +366,7 @@ class ValidationTest : IntegrationTestBase() {
HttpStatus.BAD_REQUEST,
"Unreadable HTTP message",
"Unreadable HTTP message",
"JSON parse error: Cannot deserialize value of type `uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.Gender` from String \"TESTINGENUM\": not one of the values accepted for Enum class: [NOT_SPECIFIED, MALE, NOT_KNOWN, FEMALE]",
"JSON parse error: Cannot construct instance of `uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.Gender`, problem: Invalid gender value: TESTINGENUM",
"Unreadable HTTP message",
)

Expand All @@ -372,6 +381,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learner-events")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(findLearnerByDemographicsRequest)
.exchange()
Expand Down Expand Up @@ -410,6 +420,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learners")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestJsonWithoutGivenName)
.accept(MediaType.parseMediaType("application/json"))
Expand Down Expand Up @@ -449,6 +460,7 @@ class ValidationTest : IntegrationTestBase() {
val actualResponse = webTestClient.post()
.uri("/learner-events")
.headers(setAuthorisation(roles = listOf("ROLE_LEARNER_RECORDS_SEARCH__RO")))
.header("X-Username", "TestUser")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestJsonWithoutGivenName)
.accept(MediaType.parseMediaType("application/json"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class LearnersResourceIntTest : IntegrationTestBase() {
extendedRequestBody["givenName"] = "Some"
extendedRequestBody["familyName"] = "Person"
extendedRequestBody["dateOfBirth"] = "2024-01-01"
extendedRequestBody["gender"] = "1"
extendedRequestBody["gender"] = "MALE"
extendedRequestBody["lastKnownPostCode"] = "CV49EE"
extendedRequestBody["previousFamilyName"] = "Test"
extendedRequestBody["schoolAtAge16"] = "Test High School"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request

import com.fasterxml.jackson.databind.exc.ValueInstantiationException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class GenderTest {

private val objectMapper = jacksonObjectMapper()

@Test
fun `valid gender should parse`() {
objectMapper.readValue("\"MALE\"", Gender::class.java)
objectMapper.readValue("\"FEMALE\"", Gender::class.java)
objectMapper.readValue("\"NOT_KNOWN\"", Gender::class.java)
objectMapper.readValue("\"NOT_SPECIFIED\"", Gender::class.java)
}

@Test
fun `invalid gender should fail at runtime with an illegal argument exception`() {
assertThrows<ValueInstantiationException> {
objectMapper.readValue("\"STRAWBERRY\"", Gender::class.java)
}

assertThrows<ValueInstantiationException> {
objectMapper.readValue("\"2\"", Gender::class.java)
}

assertThrows<ValueInstantiationException> {
objectMapper.readValue("\"1\"", Gender::class.java)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request

import jakarta.validation.Validation
import jakarta.validation.ValidatorFactory
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.time.LocalDate

class LearnerEventsRequestTest {

private val factory: ValidatorFactory = Validation.buildDefaultValidatorFactory()
private val validator = factory.validator

@Test
fun `valid request should pass validation`() {
val request = LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.MALE,
)

assertTrue(validator.validate(request).isEmpty())
}

@Test
fun `invalid givenNames and familyNames should fail validation`() {
val requestWithSymbolsInNames = LearnerEventsRequest(
givenName = "Firstname!",
familyName = "L@stname",
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.MALE,
)

val requestWithTooShortNames = LearnerEventsRequest(
givenName = "Fi",
familyName = "La",
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.MALE,
)

val requestWithTooLongNames = LearnerEventsRequest(
givenName = "F" + "i".repeat(35),
familyName = "L" + "a".repeat(35),
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.MALE,
)

assertTrue(validator.validate(requestWithSymbolsInNames).size == 2)
assertTrue(validator.validate(requestWithTooShortNames).size == 2)
assertTrue(validator.validate(requestWithTooLongNames).size == 2)
}

@Test
fun `invalid uln should fail validation`() {
val requestWithLettersInUln = LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "I2E4S67B90",
dateOfBirth = LocalDate.of(2000, 1, 1),
gender = Gender.MALE,
)

val requestWithTooLongUln = LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "1".repeat(50),
dateOfBirth = LocalDate.of(2000, 1, 1),
gender = Gender.MALE,
)

val requestWithEmptyUln = LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "",
dateOfBirth = LocalDate.of(2000, 1, 1),
gender = Gender.MALE,
)

assertTrue(validator.validate(requestWithLettersInUln).size == 1)
assertTrue(validator.validate(requestWithTooLongUln).size == 1)
assertTrue(validator.validate(requestWithEmptyUln).size == 1)
}

@Test
fun `dateOfBirth in the future should fail validation`() {
val requestWithDateInFuture = LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "1234567890",
dateOfBirth = LocalDate.now().plusDays(1),
gender = Gender.MALE,
)

assertTrue(validator.validate(requestWithDateInFuture).size == 1)
}

@Test
fun `invalid gender should fail at runtime with an illegal argument exception`() {
assertThrows<IllegalArgumentException> {
LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.valueOf("ENGLAND"),
)
}
assertThrows<IllegalArgumentException> {
LearnerEventsRequest(
givenName = "Firstname",
familyName = "Lastname",
uln = "1234567890",
dateOfBirth = LocalDate.of(1990, 1, 1),
gender = Gender.valueOf("1"),
)
}
}
}
Loading

0 comments on commit c23ac81

Please sign in to comment.