Skip to content

Commit

Permalink
Added validation for licence to start page
Browse files Browse the repository at this point in the history
  • Loading branch information
jaucourt committed Nov 15, 2024
1 parent 0551751 commit 8a950cb
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 113 deletions.
11 changes: 6 additions & 5 deletions packages/gafl-webapp-service/src/locales/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,16 +408,17 @@
"licence_num": "Rhif trwydded",
"licence_start_days": " diwrnod nesaf",
"licence_start_enter_todays_date": "Rhowch ddyddiad heddiw os ydych chi am i’r drwydded 1 diwrnod neu 8 diwrnod ddechrau yn hwyrach heddiw.",
"licence_start_error_choose_when": "Dewiswch pryd y dylai'r drwydded ddechrau",
"licence_start_error_format": "Nodwch y dyddiad y mae angen i'r drwydded ddechrau a chynnwys diwrnod, mis a blwyddyn",
"licence_start_error_within": "Nodwch ddyddiad o fewn y ",
"licence_start_error_date_real": "Mae’n rhaid i ddyddiad dechrau’r drwydded fod yn ddyddiad dilys",
"licence_start_error_missing_day_and_month": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod a mis",
"licence_start_error_missing_day_and_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod a blwyddyn",
"licence_start_error_missing_month_and_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys mis a blwyddyn",
"licence_start_error": "Rhowch ddyddiad dechrau’r drwydded",
"licence_start_error_missing_day": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys diwrnod",
"licence_start_error_missing_month": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys mis",
"licence_start_error_missing_year": "Mae’n rhaid i ddyddiad dechrau’r drwydded gynnwys blwyddyn",
"licence_start_error_non_numeric": "Rhowch rifau yn unig",
"licence_start_error_year_min": "Licence start date is too long ago",
"licence_start_error_year_max": "The licence start date must be in the past",
"licence_start_error_choose_when": "Dewiswch pryd y dylai'r drwydded ddechrau",
"licence_start_error_within": "Nodwch ddyddiad o fewn y ",
"licence_start_hint": "Rhowch ddyddiad hyd at a chan gynnwys ",
"licence_start_later": "Yn hwyrach",
"licence_start_minutes_after": " munud ar ôl derbyn y taliad",
Expand Down
16 changes: 11 additions & 5 deletions packages/gafl-webapp-service/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@
"disability_concession_title_you": "Do you receive any of the following?",
"dob_day": "day",
"dob_entry_hint": "For example, 23 11 1979",

"dob_error_date_real": "Date of birth must be a real date",
"dob_error_missing_day_and_month": "Date of birth must include a day and month",
"dob_error_missing_day_and_year": "Date of birth must include a day and year",
Expand All @@ -276,6 +277,7 @@
"dob_error_year_min": "Date of birth is too long ago",
"dob_error_year_max": "The date of birth must be in the past",
"dob_error": "Enter a date of birth",

"dob_month": "month",
"dob_privacy_link_prefix": "If you do not provide a correct date of birth, this may cause delays when a licence is renewed or mean that a licence is not valid. Read about ",
"dob_privacy_link": "how we use personal information (opens in new tab)",
Expand Down Expand Up @@ -408,16 +410,20 @@
"licence_num": "Licence number",
"licence_start_days": " days",
"licence_start_enter_todays_date": "Enter today’s date if you want the 1-day or 8-day licence to start later today.",
"licence_start_error_choose_when": "Choose when the licence should start",
"licence_start_error_format": "Enter the date the licence needs to start, include a day, month and year",
"licence_start_error_within": "Enter a date within the next ",

"licence_start_error_date_real": "Licence start date must be a real date",
"licence_start_error_missing_day_and_month": "Licence start date must include a day and month",
"licence_start_error_missing_day_and_year": "Licence start date must include a day and year",
"licence_start_error_missing_month_and_year": "Licence start date must include a month and year",
"licence_start_error_missing_day": "Licence start date must include a day",
"licence_start_error_missing_month": "Licence start date must include a month",
"licence_start_error_missing_year": "Licence start date must include a year",
"licence_start_error_non_numeric": "Enter only numbers",
"licence_start_error_year_min": "Licence start date is too long ago",
"licence_start_error_year_max": "The licence start date must be in the past",
"licence_start_error": "Enter a licence start date",

"licence_start_error_choose_when": "Choose when the licence should start",
"licence_start_error_within": "Enter a date within the next ",

"licence_start_hint": "Enter a date up to and including ",
"licence_start_later": "Later",
"licence_start_minutes_after": " minutes after payment",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { DATE_OF_BIRTH, LICENCE_FOR } from '../../../uri.js'
import Joi from 'joi'
import pageRoute from '../../../routes/page-route.js'
// import { validation } from '@defra-fish/business-rules-lib'
import { nextPage } from '../../../routes/next-page.js'
import GetDataRedirect from '../../../handlers/get-data-redirect.js'
import { dateSchema, dateSchemaInput } from '../../../schema/date.schema.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { getData } from '../route'
import Joi from 'joi'
import { getData, validator } from '../route'
import moment from 'moment'
const dateSchema = require('../../../../schema/date.schema.js')

jest.mock('../../../../processors/uri-helper.js')

Expand Down Expand Up @@ -28,4 +31,107 @@ describe('licence-to-start > route', () => {
expect(result.isLicenceForYou).toBeFalsy()
})
})

describe('validation', () => {
beforeEach(jest.clearAllMocks)

const getSamplePayload = ({ day = '', month = '', year = '' } = {}) => ({
'licence-start-date-day': day,
'licence-start-date-month': month,
'licence-start-date-year': year,
'licence-to-start': 'another-date'
})

const setupMocks = () => {
Joi.originalAssert = Joi.assert
dateSchema.originalDateSchema = dateSchema.dateSchema
dateSchema.originalDateSchemaInput = dateSchema.dateSchemaInput

Joi.assert = jest.fn()
dateSchema.dateSchema = Symbol('dateSchema')
dateSchema.dateSchemaInput = jest.fn()
}

const tearDownMocks = () => {
Joi.assert = Joi.originalAssert
dateSchema.dateSchema = dateSchema.originalDateSchema
dateSchema.dateSchemaInput = dateSchema.originalDateSchemaInput
}

it('throws an error for a licence starting before today', () => {
const invalidStartDate = moment().subtract(1, 'day')
const samplePayload = getSamplePayload({
day: invalidStartDate.format('DD'),
month: invalidStartDate.format('MM'),
year: invalidStartDate.format('YYYY')
})
expect(() => validator(samplePayload)).toThrow()
})

it('throws an error for a licence starting more than 30 days hence', () => {
const invalidStartDate = moment().add(31, 'days')
const samplePayload = getSamplePayload({
day: invalidStartDate.format('DD'),
month: invalidStartDate.format('MM'),
year: invalidStartDate.format('YYYY')
})
expect(() => validator(samplePayload)).toThrow()
})

it('validates for a date within the next 30 days', () => {
const validStartDate = moment().add(4, 'days')
const samplePayload = getSamplePayload({
day: validStartDate.format('DD'),
month: validStartDate.format('MM'),
year: validStartDate.format('YYYY')
})
expect(() => validator(samplePayload)).not.toThrow()
})

it.each([
['1-3-2024', moment('2024-02-28')],
['9-7-2024', moment('2024-07-08')]
])('handles single digit date %s', (date, now) => {
jest.useFakeTimers()
jest.setSystemTime(now.toDate())

const [day, month, year] = date.split('-')
const samplePayload = getSamplePayload({
day,
month,
year
})
expect(() => validator(samplePayload)).not.toThrow()
jest.useRealTimers()
})

it.each([
['01', '03', '1994'],
['10', '12', '2004']
])('passes start date day (%s), month (%s) and year (%s) to dateSchemaInput', (day, month, year) => {
setupMocks()
validator(getSamplePayload({ day, month, year }))
expect(dateSchema.dateSchemaInput).toHaveBeenCalledWith(day, month, year)
tearDownMocks()
})

it('passes dateSchemaInput output and dateSchema to Joi.assert', () => {
setupMocks()
const dsi = Symbol('dsi')
dateSchema.dateSchemaInput.mockReturnValueOnce(dsi)
validator(getSamplePayload())
expect(Joi.assert).toHaveBeenCalledWith(dsi, dateSchema.dateSchema)
tearDownMocks()
})

it('passes validation if licence is set to start after payment', () => {
const samplePayload = { 'licence-to-start': 'after-payment' }
expect(() => validator(samplePayload)).not.toThrow()
})

it('throws an error if licence-to-start is set to an invalid value', () => {
const samplePayload = { 'licence-to-start': '12th-of-never' }
expect(() => validator(samplePayload)).toThrow()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,37 @@

{%
set errorMap = {
'licence-to-start': {
'any.required': { ref: '#licence-to-start', text: mssgs.licence_start_error_choose_when }
},
'licence-start-date-old': {
'date.format': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_format },
'date.max': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_within + data.advancedPurchaseMaxDays + mssgs.licence_start_days },
'date.min': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_within + data.advancedPurchaseMaxDays + mssgs.licence_start_days },
'date.real': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_date_real }
},
'day': {
'any.required': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_missing_day },
'number.base': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_non_numeric },
'number.integer': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_date_real },
'number.min': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_date_real },
'number.max': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_date_real }
},
'month': {
'any.required': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_missing_month },
'number.base': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_non_numeric },
'number.integer': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_date_real },
'number.min': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_date_real },
'number.max': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_date_real }
},
'year': {
'any.required': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_missing_year },
'number.base': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_non_numeric },
'number.integer': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_date_real },
'number.min': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_year_min },
'number.max': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_year_max }
}
'full-date': {
'object.missing': { ref: '#licence-start-date-day', text: mssgs.licence_start_error }
},
'day-and-month': {
'object.missing': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_missing_day_and_month }
},
'day-and-year': {
'object.missing': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_missing_day_and_year }
},
'month-and-year': {
'object.missing': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_missing_month_and_year }
},
'day': {
'any.required': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_missing_day }
},
'month': {
'any.required': { ref: '#licence-start-date-month', text: mssgs.licence_start_error_missing_month }
},
'year': {
'any.required': { ref: '#licence-start-date-year', text: mssgs.licence_start_error_missing_year }
},
'non-numeric': {
'number.base': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_non_numeric }
},
'invalid-date': {
'any.custom': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_date_real }
},
'startDate': {
'date.min': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_within + data.advancedPurchaseMaxDays + mssgs.licence_start_days },
'date.max': { ref: '#licence-start-date-day', text: mssgs.licence_start_error_within + data.advancedPurchaseMaxDays + mssgs.licence_start_days }
}
}
%}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,86 +1,32 @@
import Joi from 'joi'
import moment from 'moment-timezone'
import { START_AFTER_PAYMENT_MINUTES, ADVANCED_PURCHASE_MAX_DAYS, SERVICE_LOCAL_TIME, validation } from '@defra-fish/business-rules-lib'
import { START_AFTER_PAYMENT_MINUTES, ADVANCED_PURCHASE_MAX_DAYS, SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
import { LICENCE_TO_START } from '../../../uri.js'
import pageRoute from '../../../routes/page-route.js'
import { nextPage } from '../../../routes/next-page.js'
import { dateSchema, dateSchemaInput } from '../../../schema/date.schema.js'

const LICENCE_TO_START_FIELD = 'licence-to-start'
const AFTER_PAYMENT = 'after-payment'
const ANOTHER_DATE = 'another-date'

const minTime = moment().tz(SERVICE_LOCAL_TIME).startOf('day')
const maxTime = moment().tz(SERVICE_LOCAL_TIME).add(ADVANCED_PURCHASE_MAX_DAYS, 'days')
const minYear = minTime.year()
const maxYear = maxTime.year()

const daySchema = () => {
Joi.when(LICENCE_TO_START_FIELD, {
is: ANOTHER_DATE,
then: Joi.any().required().concat(validation.date.createDayValidator(Joi)),
otherwise: Joi.any()
})
}

const monthSchema = () => {
Joi.when(LICENCE_TO_START_FIELD, {
is: ANOTHER_DATE,
then: Joi.any().required().concat(validation.date.createMonthValidator(Joi))
})
}

const yearSchema = () => {
Joi.when(LICENCE_TO_START_FIELD, {
is: ANOTHER_DATE,
then: Joi.any().required().concat(validation.date.createYearValidator(Joi, minYear, maxYear))
})
}

const licenceStartDateSchema = () => {
Joi.when(LICENCE_TO_START_FIELD, {
is: ANOTHER_DATE,
then: Joi.when(
Joi.object({
day: Joi.any().required().concat(validation.date.createDayValidator(Joi)),
month: Joi.any().required().concat(validation.date.createMonthValidator(Joi)),
year: Joi.any().required().concat(validation.date.createYearValidator(Joi, minYear, maxYear))
}).unknown(),
{
then: validation.date.createRealDateValidator(Joi)
}
)
})
}

const validator = payload => {
const day = payload['licence-start-date-day']
const month = payload['licence-start-date-month']
const year = payload['licence-start-date-year']

const licenceStartDate = { day: parseInt(day), month: parseInt(month), year: parseInt(year) }
const licenceStartDateForRange = `${year}-${month}-${day}`

export const validator = payload => {
Joi.assert(
{
'licence-start-date-for-range': licenceStartDateForRange,
'licence-to-start': payload[LICENCE_TO_START_FIELD],
day: day || undefined,
month: month || undefined,
year: year || undefined,
'licence-start-date': licenceStartDate
},
Joi.object({
'licence-to-start': Joi.string().valid(AFTER_PAYMENT, ANOTHER_DATE).required(),
'licence-start-date-for-range': Joi.when(LICENCE_TO_START_FIELD, {
is: ANOTHER_DATE,
then: Joi.date().min(minTime).max(maxTime).required()
}),
day: daySchema,
month: monthSchema,
year: yearSchema,
'licence-start-date': licenceStartDateSchema
}).options({ abortEarly: false, allowUnknown: true })
{ 'licence-to-start': payload[LICENCE_TO_START_FIELD] },
Joi.object({ 'licence-to-start': Joi.string().valid(AFTER_PAYMENT, ANOTHER_DATE).required() })
)
if (payload[LICENCE_TO_START_FIELD] === ANOTHER_DATE) {
const minDate = moment().tz(SERVICE_LOCAL_TIME).startOf('day')
const maxDate = moment().tz(SERVICE_LOCAL_TIME).add(ADVANCED_PURCHASE_MAX_DAYS, 'days')

const day = payload['licence-start-date-day']
const month = payload['licence-start-date-month']
const year = payload['licence-start-date-year']

Joi.assert(dateSchemaInput(day, month, year), dateSchema)
const startDate = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T00:00:00.000Z`)
Joi.assert({ startDate }, Joi.object({ startDate: Joi.date().min(minDate.toDate()).max(maxDate.toDate()) }))
}
}

export const getData = async request => {
Expand Down

0 comments on commit 8a950cb

Please sign in to comment.