Skip to content

Commit

Permalink
Refactor date of birth and start date validators to be reusable funct…
Browse files Browse the repository at this point in the history
…ions, apply them to the date of birth, start date and renewals id page (start date will be applied to renewal start date in a new ticket)
  • Loading branch information
jaucourt committed Nov 15, 2024
1 parent 7f0c55e commit 63ef199
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 376 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import Joi from 'joi'
import { getData, validator } from '../route'
import { getData } from '../route'
import pageRoute from '../../../../routes/page-route.js'
import { nextPage } from '../../../../routes/next-page.js'
import { LICENCE_FOR } from '../../../../uri.js'
import moment from 'moment'
const dateSchema = require('../../../../schema/date.schema.js')
import { dateOfBirthValidator } from '../../../../schema/validators/validators.js'

jest.mock('../../../../routes/next-page.js', () => ({
nextPage: jest.fn()
}))
jest.mock('../../../../routes/next-page.js')
jest.mock('../../../../routes/page-route.js')
jest.mock('../../../../schema/validators/validators.js')

describe('name > route', () => {
const mockRequest = (statusGet = () => {}, transactionGet = () => {}) => ({
Expand Down Expand Up @@ -82,99 +79,8 @@ describe('name > route', () => {
})

describe('default', () => {
it('should call the pageRoute with date-of-birth, /buy/date-of-birth, validator and nextPage', async () => {
expect(pageRoute).toBeCalledWith('date-of-birth', '/buy/date-of-birth', validator, nextPage, getData)
})
})

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

const getSamplePayload = ({ day = '', month = '', year = '' } = {}) => ({
'date-of-birth-day': day,
'date-of-birth-month': month,
'date-of-birth-year': 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(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('should call the pageRoute with date-of-birth, /buy/date-of-birth, dateOfBirthValidator and nextPage', async () => {
expect(pageRoute).toBeCalledWith('date-of-birth', '/buy/date-of-birth', dateOfBirthValidator, nextPage, getData)
})
})
})
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import { DATE_OF_BIRTH, LICENCE_FOR } from '../../../uri.js'
import Joi from 'joi'
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 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) }))
}
import { dateOfBirthValidator } from '../../../schema/validators/validators.js'

const redirectToStartOfJourney = status => {
if (!status[LICENCE_FOR.page]) {
Expand All @@ -35,4 +19,4 @@ export const getData = async request => {
return { isLicenceForYou }
}

export default pageRoute(DATE_OF_BIRTH.page, DATE_OF_BIRTH.uri, validator, nextPage, getData)
export default pageRoute(DATE_OF_BIRTH.page, DATE_OF_BIRTH.uri, dateOfBirthValidator, nextPage, getData)
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import Joi from 'joi'
import { getData, validator } from '../route'
import moment from 'moment'
const dateSchema = require('../../../../schema/date.schema.js')
import pageRoute from '../../../../routes/page-route.js'
import { nextPage } from '../../../../routes/next-page.js'
import { getData } from '../route'
import { LICENCE_TO_START } from '../../../../uri.js'
import { startDateValidator } from '../../../../schema/validators/validators.js'

jest.mock('../../../../processors/uri-helper.js')
jest.mock('../../../../routes/next-page.js')
jest.mock('../../../../routes/page-route.js')
jest.mock('../../../../schema/validators/validators.js')

describe('licence-to-start > route', () => {
const getMockRequest = (isLicenceForYou = true) => ({
Expand Down Expand Up @@ -32,106 +35,9 @@ describe('licence-to-start > route', () => {
})
})

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()
describe('default', () => {
it('should call the pageRoute with date-of-birth, /buy/date-of-birth, dateOfBirthValidator and nextPage', async () => {
expect(pageRoute).toBeCalledWith(LICENCE_TO_START.page, LICENCE_TO_START.uri, startDateValidator, nextPage, getData)
})
})
})
Original file line number Diff line number Diff line change
@@ -1,33 +1,9 @@
import Joi from 'joi'
import moment from 'moment-timezone'
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'

export const validator = payload => {
Joi.assert(
{ '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()) }))
}
}
import { startDateValidator } from '../../../schema/validators/validators.js'

export const getData = async request => {
const fmt = 'DD MM YYYY'
Expand All @@ -43,4 +19,4 @@ export const getData = async request => {
}
}

export default pageRoute(LICENCE_TO_START.page, LICENCE_TO_START.uri, validator, nextPage, getData)
export default pageRoute(LICENCE_TO_START.page, LICENCE_TO_START.uri, startDateValidator, nextPage, getData)

This file was deleted.

This file was deleted.

Loading

0 comments on commit 63ef199

Please sign in to comment.