Skip to content

Commit

Permalink
Refactor dateSchemaInput to map zero-length strings to undefined valu…
Browse files Browse the repository at this point in the history
…es, added extra test for decimal values against date schema, added validation to date of birth page for no dates before 120 years ago and no dates in the future
  • Loading branch information
jaucourt committed Nov 13, 2024
1 parent 3502c60 commit 0551751
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,13 @@ import { getData, validator } from '../route'
import pageRoute from '../../../../routes/page-route.js'
import { nextPage } from '../../../../routes/next-page.js'
import { LICENCE_FOR } from '../../../../uri.js'
import { dateSchema, dateSchemaInput } from '../../../../schema/date.schema.js'
import moment from 'moment'
const dateSchema = require('../../../../schema/date.schema.js')

jest.mock('../../../../routes/next-page.js', () => ({
nextPage: jest.fn()
}))
jest.mock('../../../../routes/page-route.js')
jest.mock('../../../../schema/date.schema.js', () => ({
dateSchema: Symbol('dateSchema'),
dateSchemaInput: jest.fn()
}))
jest.mock('joi', () => ({
assert: jest.fn(),
object: () => ({ keys: () => ({ or: () => {} }), options: () => {} }),
any: () => ({ required: () => {} }),
number: () => {},
date: () => ({ iso: () => ({ strict: () => {} }) })
}))

describe('name > route', () => {
const mockRequest = (statusGet = () => {}, transactionGet = () => {}) => ({
Expand Down Expand Up @@ -106,20 +96,85 @@ describe('name > route', () => {
'date-of-birth-year': year
})

it('passes date of birth day, month and year to dateSchemaInput', () => {
const day = Symbol('date')
const month = Symbol('month')
const year = Symbol('year')
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 anyone over 120 years old', () => {
const invalidDoB = moment().subtract(120, 'years').subtract(1, 'day')
const samplePayload = getSamplePayload({
day: invalidDoB.format('DD'),
month: invalidDoB.format('MM'),
year: invalidDoB.format('YYYY')
})
expect(() => validator(samplePayload)).toThrow()
})

it('validates for anyone 120 years old', () => {
const validDoB = moment().subtract(120, 'years')
const samplePayload = getSamplePayload({
day: validDoB.format('DD'),
month: validDoB.format('MM'),
year: validDoB.format('YYYY')
})
expect(() => validator(samplePayload)).not.toThrow()
})

it.each([
['today', moment()],
['tomorrow', moment().add(1, 'day')],
['in the future', moment().add(1, 'month')]
])('throws an error for a date of birth %s', (_desc, invalidDoB) => {
const samplePayload = getSamplePayload({
day: invalidDoB.format('DD'),
month: invalidDoB.format('MM'),
year: invalidDoB.format('YYYY')
})
expect(() => validator(samplePayload)).toThrow()
})

it.each([
['1-3-2004', '1', '3', '2004'],
['12-1-1999', '12', '1', '1999'],
['1-12-2006', '1', '12', '2006']
])('handles single digit date %s', (_desc, day, month, year) => {
const samplePayload = getSamplePayload({
day,
month,
year
})
expect(() => validator(samplePayload)).not.toThrow()
})

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

it('passes dateSchemaInput output and dateSchema to Joi.assert', () => {
console.log('dateSchema', dateSchema)
setupMocks()
const dsi = Symbol('dsi')
dateSchemaInput.mockReturnValueOnce(dsi)
dateSchema.dateSchemaInput.mockReturnValueOnce(dsi)
validator(getSamplePayload())
expect(Joi.assert).toHaveBeenCalledWith(dsi, dateSchema)
expect(Joi.assert).toHaveBeenCalledWith(dsi, dateSchema.dateSchema)
tearDownMocks()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
'number.base': { ref: '#date-of-birth-day', text: mssgs.dob_error_non_numeric }
},
'invalid-date': {
'date.base': { ref: '#date-of-birth-day', text: mssgs.dob_error_date_real }
'any.custom': { ref: '#date-of-birth-day', text: mssgs.dob_error_date_real }
},
'birthDate': {
'date.min': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_min },
'date.max': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_max }
}
}
%}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import pageRoute from '../../../routes/page-route.js'
import { nextPage } from '../../../routes/next-page.js'
import GetDataRedirect from '../../../handlers/get-data-redirect.js'
import { dateSchema, dateSchemaInput } from '../../../schema/date.schema.js'
import moment from 'moment'

const MAX_AGE = 120

export const validator = payload => {
const maxYear = new Date().getFullYear()
const minYear = maxYear - MAX_AGE

const day = payload['date-of-birth-day']
const month = payload['date-of-birth-month']
const year = payload['date-of-birth-year']

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

const redirectToStartOfJourney = status => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ describe('dateSchemaInput', () => {
it('matches expected format', () => {
expect(dateSchemaInput('1', '2', '2023')).toMatchSnapshot()
})

it.each`
desc | day | month | year | result
${'all empty'} | ${''} | ${''} | ${''} | ${{ 'full-date': { day: undefined, month: undefined, year: undefined } }}
${'day and month empty'} | ${''} | ${''} | ${'2020'} | ${{ 'day-and-month': { day: undefined, month: undefined } }}
${'day and year empty'} | ${''} | ${'11'} | ${''} | ${{ 'day-and-year': { day: undefined, year: undefined } }}
${'month and year empty'} | ${'12'} | ${''} | ${''} | ${{ 'month-and-year': { month: undefined, year: undefined } }}
${'day empty'} | ${''} | ${'3'} | ${'2021'} | ${{ day: undefined }}
${'month empty'} | ${'4'} | ${''} | ${'2003'} | ${{ month: undefined }}
${'year empty'} | ${'15'} | ${'11'} | ${''} | ${{ year: undefined }}
`('maps empty strings to undefined values when $desc', ({ day, month, year, result }) => {
expect(dateSchemaInput(day, month, year)).toEqual(expect.objectContaining(result))
})
})

describe('dateSchema', () => {
Expand All @@ -24,6 +37,7 @@ describe('dateSchema', () => {
${{ day: '30', month: '2', year: '1994' }} | ${'invalid-date'} | ${'an invalid date - 1994-02-40'}
${{ day: '1', month: '13', year: '2022' }} | ${'invalid-date'} | ${'an invalid date - 2022-13-01'}
${{ day: '29', month: '2', year: '2023' }} | ${'invalid-date'} | ${'an invalid date - 1994-02-40'}
${{ day: '-1.15', month: '18', year: '22.2222' }} | ${'invalid-date'} | ${'an invalid date - 22.2222-18-1.15'}
`('Error has $expectedError in details when payload has $payloadDesc', ({ payload: { day, month, year }, expectedError }) => {
expect(() => {
Joi.assert(dateSchemaInput(day, month, year), dateSchema)
Expand Down
29 changes: 17 additions & 12 deletions packages/gafl-webapp-service/src/schema/date.schema.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
'use strict'
import Joi from 'joi'

export const dateSchemaInput = (day, month, year) => ({
'full-date': { day, month, year },
'day-and-month': { day, month },
'day-and-year': { day, year },
'month-and-year': { month, year },
day,
month,
year,
'non-numeric': { day, month, year },
'invalid-date': `${year}-${(month || '').padStart(2, '0')}-${(day || '').padStart(2, '0')}`
})
export const dateSchemaInput = (unparsedDay, unparsedMonth, unparsedYear) => {
const day = unparsedDay === '' ? undefined : unparsedDay
const month = unparsedMonth === '' ? undefined : unparsedMonth
const year = unparsedYear === '' ? undefined : unparsedYear

return {
'full-date': { day, month, year },
'day-and-month': { day, month },
'day-and-year': { day, year },
'month-and-year': { month, year },
day,
month,
year,
'non-numeric': { day, month, year },
'invalid-date': `${year}-${(month || '').padStart(2, '0')}-${(day || '').padStart(2, '0')}`
}
}

export const dateSchema = Joi.object({
'full-date': Joi.object()
Expand Down Expand Up @@ -47,7 +53,6 @@ export const dateSchema = Joi.object({
month: Joi.number(),
year: Joi.number()
}),
// 'invalid-date': Joi.date().iso().strict()
'invalid-date': Joi.custom((dateToValidate, helpers) => {
if (new Date(dateToValidate).toISOString() !== `${dateToValidate}T00:00:00.000Z`) {
throw helpers.error('invalid-date')
Expand Down

0 comments on commit 0551751

Please sign in to comment.