@defra-fish/gafl-webapp-service 1.57.0-rc.3 → 1.57.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/locales/cy.json +26 -7
- package/src/locales/en.json +29 -8
- package/src/pages/concessions/date-of-birth/__tests__/route.spec.js +75 -20
- package/src/pages/concessions/date-of-birth/date-of-birth.njk +35 -9
- package/src/pages/concessions/date-of-birth/route.js +10 -14
- package/src/pages/licence-details/licence-to-start/__tests__/route.spec.js +65 -2
- package/src/pages/licence-details/licence-to-start/licence-to-start.njk +35 -12
- package/src/pages/licence-details/licence-to-start/route.js +14 -32
- package/src/pages/renewals/identify/__tests__/identity.spec.js +3 -0
- package/src/pages/renewals/identify/__tests__/route.spec.js +126 -0
- package/src/pages/renewals/identify/identify.njk +34 -8
- package/src/pages/renewals/identify/route.js +10 -10
- package/src/schema/__tests__/__snapshots__/date.schema.test.js.snap +32 -0
- package/src/schema/__tests__/date.schema.test.js +64 -0
- package/src/schema/date.schema.js +63 -0
- package/src/schema/validators/__tests__/validators.spec.js +208 -0
- package/src/schema/validators/validators.js +65 -0
- package/src/pages/renewals/identify/__tests__/identity.next-page.spec.js +0 -36
- package/src/pages/renewals/identify/__tests__/route-spec.js +0 -38
|
@@ -1,49 +1,31 @@
|
|
|
1
|
-
import Joi from 'joi'
|
|
2
1
|
import moment from 'moment-timezone'
|
|
3
|
-
|
|
4
|
-
import JoiDate from '@hapi/joi-date'
|
|
5
2
|
import { START_AFTER_PAYMENT_MINUTES, ADVANCED_PURCHASE_MAX_DAYS, SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
|
|
6
3
|
import { LICENCE_TO_START } from '../../../uri.js'
|
|
7
4
|
import pageRoute from '../../../routes/page-route.js'
|
|
8
|
-
import { dateFormats } from '../../../constants.js'
|
|
9
5
|
import { nextPage } from '../../../routes/next-page.js'
|
|
10
|
-
|
|
11
|
-
const JoiX = Joi.extend(JoiDate)
|
|
12
|
-
|
|
13
|
-
const validator = payload => {
|
|
14
|
-
const licenceStartDate = `${payload['licence-start-date-year']}-${payload['licence-start-date-month']}-${payload['licence-start-date-day']}`
|
|
15
|
-
Joi.assert(
|
|
16
|
-
{
|
|
17
|
-
'licence-start-date': licenceStartDate,
|
|
18
|
-
'licence-to-start': payload['licence-to-start']
|
|
19
|
-
},
|
|
20
|
-
Joi.object({
|
|
21
|
-
'licence-to-start': Joi.string().valid('after-payment', 'another-date').required(),
|
|
22
|
-
'licence-start-date': Joi.alternatives().conditional('licence-to-start', {
|
|
23
|
-
is: 'another-date',
|
|
24
|
-
then: JoiX.date()
|
|
25
|
-
.format(dateFormats)
|
|
26
|
-
.min(moment().tz(SERVICE_LOCAL_TIME).startOf('day'))
|
|
27
|
-
.max(moment().tz(SERVICE_LOCAL_TIME).add(ADVANCED_PURCHASE_MAX_DAYS, 'days'))
|
|
28
|
-
.required(),
|
|
29
|
-
otherwise: Joi.string().empty('')
|
|
30
|
-
})
|
|
31
|
-
}).options({ abortEarly: false, allowUnknown: true })
|
|
32
|
-
)
|
|
33
|
-
}
|
|
6
|
+
import { getDateErrorFlags, startDateValidator } from '../../../schema/validators/validators.js'
|
|
34
7
|
|
|
35
8
|
export const getData = async request => {
|
|
36
9
|
const fmt = 'DD MM YYYY'
|
|
37
10
|
const { isLicenceForYou } = await request.cache().helpers.transaction.getCurrentPermission()
|
|
38
|
-
|
|
39
|
-
|
|
11
|
+
const page = await request.cache().helpers.page.getCurrentPermission(LICENCE_TO_START.page)
|
|
12
|
+
const pageData = {
|
|
40
13
|
isLicenceForYou,
|
|
41
14
|
exampleStartDate: moment().tz(SERVICE_LOCAL_TIME).add(1, 'days').format(fmt),
|
|
42
15
|
minStartDate: moment().tz(SERVICE_LOCAL_TIME).format(fmt),
|
|
43
16
|
maxStartDate: moment().tz(SERVICE_LOCAL_TIME).add(ADVANCED_PURCHASE_MAX_DAYS, 'days').format(fmt),
|
|
44
17
|
advancedPurchaseMaxDays: ADVANCED_PURCHASE_MAX_DAYS,
|
|
45
|
-
startAfterPaymentMinutes: START_AFTER_PAYMENT_MINUTES
|
|
18
|
+
startAfterPaymentMinutes: START_AFTER_PAYMENT_MINUTES,
|
|
19
|
+
...getDateErrorFlags(page?.error)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (page?.error) {
|
|
23
|
+
const [errorKey] = Object.keys(page.error)
|
|
24
|
+
const errorValue = page.error[errorKey]
|
|
25
|
+
pageData.error = { errorKey, errorValue }
|
|
46
26
|
}
|
|
27
|
+
|
|
28
|
+
return pageData
|
|
47
29
|
}
|
|
48
30
|
|
|
49
|
-
export default pageRoute(LICENCE_TO_START.page, LICENCE_TO_START.uri,
|
|
31
|
+
export default pageRoute(LICENCE_TO_START.page, LICENCE_TO_START.uri, startDateValidator, nextPage, getData)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import pageRoute from '../../../../routes/page-route.js'
|
|
2
|
+
import { addLanguageCodeToUri } from '../../../../processors/uri-helper.js'
|
|
3
|
+
import { getData, validator } from '../route.js'
|
|
4
|
+
import { IDENTIFY, NEW_TRANSACTION } from '../../../../uri.js'
|
|
5
|
+
import { dateOfBirthValidator, getDateErrorFlags } from '../../../../schema/validators/validators.js'
|
|
6
|
+
|
|
7
|
+
jest.mock('../../../../routes/page-route.js', () => jest.fn())
|
|
8
|
+
jest.mock('../../../../uri.js', () => ({
|
|
9
|
+
IDENTIFY: { page: 'identify page', uri: 'identify uri' },
|
|
10
|
+
AUTHENTICATE: { uri: Symbol('authenticate uri') },
|
|
11
|
+
NEW_TRANSACTION: { uri: Symbol('new transaction uri') }
|
|
12
|
+
}))
|
|
13
|
+
jest.mock('../../../../processors/uri-helper.js')
|
|
14
|
+
jest.mock('../../../../schema/validators/validators.js')
|
|
15
|
+
|
|
16
|
+
describe('getData', () => {
|
|
17
|
+
const getMockRequest = (referenceNumber, pageGet = async () => ({})) => ({
|
|
18
|
+
cache: () => ({
|
|
19
|
+
helpers: {
|
|
20
|
+
status: {
|
|
21
|
+
getCurrentPermission: () => ({
|
|
22
|
+
referenceNumber: referenceNumber
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
page: {
|
|
26
|
+
getCurrentPermission: pageGet
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('addLanguageCodeToUri is called with the expected arguments', async () => {
|
|
33
|
+
const request = getMockRequest('013AH6')
|
|
34
|
+
await getData(request)
|
|
35
|
+
expect(addLanguageCodeToUri).toHaveBeenCalledWith(request, NEW_TRANSACTION.uri)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('getData returns correct URI', async () => {
|
|
39
|
+
const expectedUri = Symbol('decorated uri')
|
|
40
|
+
addLanguageCodeToUri.mockReturnValueOnce(expectedUri)
|
|
41
|
+
|
|
42
|
+
const result = await getData(getMockRequest('013AH6'))
|
|
43
|
+
expect(result.uri.new).toEqual(expectedUri)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it.each([['09F6VF'], ['013AH6'], ['LK563F']])('getData returns referenceNumber', async referenceNumber => {
|
|
47
|
+
const result = await getData(getMockRequest(referenceNumber))
|
|
48
|
+
expect(result.referenceNumber).toEqual(referenceNumber)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('adds return value of getErrorFlags to the page data', async () => {
|
|
52
|
+
const errorFlags = { unique: Symbol('error-flags') }
|
|
53
|
+
getDateErrorFlags.mockReturnValueOnce(errorFlags)
|
|
54
|
+
const result = await getData(getMockRequest())
|
|
55
|
+
expect(result).toEqual(expect.objectContaining(errorFlags))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('passes error to getErrorFlags', async () => {
|
|
59
|
+
const error = Symbol('error')
|
|
60
|
+
await getData(getMockRequest(undefined, async () => ({ error })))
|
|
61
|
+
expect(getDateErrorFlags).toHaveBeenCalledWith(error)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('passes correct page name when getting page cache', async () => {
|
|
65
|
+
const pageGet = jest.fn(() => ({}))
|
|
66
|
+
await getData(getMockRequest(undefined, pageGet))
|
|
67
|
+
expect(pageGet).toHaveBeenCalledWith(IDENTIFY.page)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('default', () => {
|
|
72
|
+
it('should call the pageRoute with date-of-birth, /buy/date-of-birth, dateOfBirthValidator and nextPage', async () => {
|
|
73
|
+
expect(pageRoute).toBeCalledWith(IDENTIFY.page, IDENTIFY.uri, validator, expect.any(Function), getData)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('page route next', () => {
|
|
78
|
+
const nextPage = pageRoute.mock.calls[0][3]
|
|
79
|
+
beforeEach(jest.clearAllMocks)
|
|
80
|
+
|
|
81
|
+
it('passes a function as the nextPage argument', () => {
|
|
82
|
+
expect(typeof nextPage).toBe('function')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('calls addLanguageCodeToUri', () => {
|
|
86
|
+
nextPage()
|
|
87
|
+
expect(addLanguageCodeToUri).toHaveBeenCalled()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('passes request to addLanguageCodeToUri', () => {
|
|
91
|
+
const request = Symbol('request')
|
|
92
|
+
nextPage(request)
|
|
93
|
+
expect(addLanguageCodeToUri).toHaveBeenCalledWith(request, expect.anything())
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('next page returns result of addLanguageCodeToUri', () => {
|
|
97
|
+
const expectedResult = Symbol('add language code to uri')
|
|
98
|
+
addLanguageCodeToUri.mockReturnValueOnce(expectedResult)
|
|
99
|
+
expect(nextPage()).toBe(expectedResult)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('validator', () => {
|
|
104
|
+
const getMockRequest = (postcode = 'AA1 1AA', referenceNumber = 'A1B2C3') => ({
|
|
105
|
+
postcode,
|
|
106
|
+
referenceNumber
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('fails if dateOfBirth validator fails', () => {
|
|
110
|
+
const expectedError = new Error('expected error')
|
|
111
|
+
dateOfBirthValidator.mockImplementationOnce(() => {
|
|
112
|
+
throw expectedError
|
|
113
|
+
})
|
|
114
|
+
expect(() => validator(getMockRequest)).toThrow(expectedError)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('passes if dateOfBirth validator passes', () => {
|
|
118
|
+
expect(() => validator(getMockRequest())).not.toThrow()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('passes payload to dateOfBirth validator', () => {
|
|
122
|
+
const payload = getMockRequest()
|
|
123
|
+
validator(payload)
|
|
124
|
+
expect(dateOfBirthValidator).toHaveBeenCalledWith(payload)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -21,14 +21,40 @@
|
|
|
21
21
|
ref: "#ref"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
|
-
'date-of-birth': {
|
|
25
|
-
'date.format': { ref: '#date-of-birth-day', text: mssgs.identify_error_enter_bday },
|
|
26
|
-
'date.max': { ref: '#date-of-birth-day', text: mssgs.identify_error_enter_bday_max },
|
|
27
|
-
'date.min': { ref: '#date-of-birth-day', text: mssgs.identify_error_enter_bday_min }
|
|
28
|
-
},
|
|
29
24
|
'postcode': {
|
|
30
25
|
'string.empty': { ref: '#postcode', text: mssgs.identify_error_empty_postcode },
|
|
31
26
|
'string.pattern.base': { ref: '#postcode', text: mssgs.identify_error_pattern_postcode }
|
|
27
|
+
},
|
|
28
|
+
'full-date': {
|
|
29
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error }
|
|
30
|
+
},
|
|
31
|
+
'day-and-month': {
|
|
32
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_month }
|
|
33
|
+
},
|
|
34
|
+
'day-and-year': {
|
|
35
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_year }
|
|
36
|
+
},
|
|
37
|
+
'month-and-year': {
|
|
38
|
+
'object.missing': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month_and_year }
|
|
39
|
+
},
|
|
40
|
+
'day': {
|
|
41
|
+
'any.required': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day }
|
|
42
|
+
},
|
|
43
|
+
'month': {
|
|
44
|
+
'any.required': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month }
|
|
45
|
+
},
|
|
46
|
+
'year': {
|
|
47
|
+
'any.required': { ref: '#date-of-birth-year', text: mssgs.dob_error_missing_year }
|
|
48
|
+
},
|
|
49
|
+
'non-numeric': {
|
|
50
|
+
'number.base': { ref: '#date-of-birth-day', text: mssgs.dob_error_non_numeric }
|
|
51
|
+
},
|
|
52
|
+
'invalid-date': {
|
|
53
|
+
'any.custom': { ref: '#date-of-birth-day', text: mssgs.dob_error_date_real }
|
|
54
|
+
},
|
|
55
|
+
'date-range': {
|
|
56
|
+
'date.min': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_min },
|
|
57
|
+
'date.max': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_max }
|
|
32
58
|
}
|
|
33
59
|
}
|
|
34
60
|
%}
|
|
@@ -37,21 +63,21 @@
|
|
|
37
63
|
{
|
|
38
64
|
label: mssgs.dob_day,
|
|
39
65
|
name: "day",
|
|
40
|
-
classes: "govuk-input--width-2",
|
|
66
|
+
classes: "govuk-input--width-2 govuk-input--error" if data.isDayError else "govuk-input--width-2",
|
|
41
67
|
value: payload['date-of-birth-day'],
|
|
42
68
|
attributes: { maxlength : 2 }
|
|
43
69
|
},
|
|
44
70
|
{
|
|
45
71
|
label: mssgs.dob_month,
|
|
46
72
|
name: "month",
|
|
47
|
-
classes: "govuk-input--width-2",
|
|
73
|
+
classes: "govuk-input--width-2 govuk-input--error" if data.isMonthError else "govuk-input--width-2",
|
|
48
74
|
value: payload['date-of-birth-month'],
|
|
49
75
|
attributes: { maxlength : 2 }
|
|
50
76
|
},
|
|
51
77
|
{
|
|
52
78
|
label: mssgs.dob_year,
|
|
53
79
|
name: "year",
|
|
54
|
-
classes: "govuk-input--width-4",
|
|
80
|
+
classes: "govuk-input--width-4 govuk-input--error" if data.isYearError else "govuk-input--width-4",
|
|
55
81
|
value: payload['date-of-birth-year'],
|
|
56
82
|
attributes: { maxlength : 4 }
|
|
57
83
|
}
|
|
@@ -4,10 +4,12 @@ import Joi from 'joi'
|
|
|
4
4
|
import { validation } from '@defra-fish/business-rules-lib'
|
|
5
5
|
import { addLanguageCodeToUri } from '../../../processors/uri-helper.js'
|
|
6
6
|
import GetDataRedirect from '../../../handlers/get-data-redirect.js'
|
|
7
|
+
import { dateOfBirthValidator, getDateErrorFlags } from '../../../schema/validators/validators.js'
|
|
7
8
|
|
|
8
9
|
export const getData = async request => {
|
|
9
10
|
// If we are supplied a permission number, validate it or throw 400
|
|
10
11
|
const permission = await request.cache().helpers.status.getCurrentPermission()
|
|
12
|
+
const page = await request.cache().helpers.page.getCurrentPermission(IDENTIFY.page)
|
|
11
13
|
|
|
12
14
|
if (permission.referenceNumber) {
|
|
13
15
|
const validatePermissionNumber = validation.permission
|
|
@@ -23,25 +25,23 @@ export const getData = async request => {
|
|
|
23
25
|
referenceNumber: permission.referenceNumber,
|
|
24
26
|
uri: {
|
|
25
27
|
new: addLanguageCodeToUri(request, NEW_TRANSACTION.uri)
|
|
26
|
-
}
|
|
28
|
+
},
|
|
29
|
+
...getDateErrorFlags(page?.error)
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
'date-of-birth': validation.contact.createBirthDateValidator(Joi),
|
|
33
|
-
postcode: validation.contact.createOverseasPostcodeValidator(Joi)
|
|
34
|
-
}).options({ abortEarly: false, allowUnknown: true })
|
|
33
|
+
export const validator = payload => {
|
|
34
|
+
dateOfBirthValidator(payload)
|
|
35
35
|
|
|
36
|
-
const validator = async payload => {
|
|
37
|
-
const dateOfBirth = `${payload['date-of-birth-year']}-${payload['date-of-birth-month']}-${payload['date-of-birth-day']}`
|
|
38
36
|
Joi.assert(
|
|
39
37
|
{
|
|
40
|
-
'date-of-birth': dateOfBirth,
|
|
41
38
|
postcode: payload.postcode,
|
|
42
39
|
referenceNumber: payload.referenceNumber
|
|
43
40
|
},
|
|
44
|
-
|
|
41
|
+
Joi.object({
|
|
42
|
+
referenceNumber: validation.permission.permissionNumberUniqueComponentValidator(Joi),
|
|
43
|
+
postcode: validation.contact.createOverseasPostcodeValidator(Joi)
|
|
44
|
+
}).options({ abortEarly: false })
|
|
45
45
|
)
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`dateSchemaInput matches expected format 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"day": "1",
|
|
6
|
+
"day-and-month": Object {
|
|
7
|
+
"day": "1",
|
|
8
|
+
"month": "2",
|
|
9
|
+
},
|
|
10
|
+
"day-and-year": Object {
|
|
11
|
+
"day": "1",
|
|
12
|
+
"year": "2023",
|
|
13
|
+
},
|
|
14
|
+
"full-date": Object {
|
|
15
|
+
"day": "1",
|
|
16
|
+
"month": "2",
|
|
17
|
+
"year": "2023",
|
|
18
|
+
},
|
|
19
|
+
"invalid-date": "2023-02-01",
|
|
20
|
+
"month": "2",
|
|
21
|
+
"month-and-year": Object {
|
|
22
|
+
"month": "2",
|
|
23
|
+
"year": "2023",
|
|
24
|
+
},
|
|
25
|
+
"non-numeric": Object {
|
|
26
|
+
"day": "1",
|
|
27
|
+
"month": "2",
|
|
28
|
+
"year": "2023",
|
|
29
|
+
},
|
|
30
|
+
"year": "2023",
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import { dateSchemaInput, dateSchema } from '../date.schema.js'
|
|
3
|
+
|
|
4
|
+
describe('dateSchemaInput', () => {
|
|
5
|
+
it('matches expected format', () => {
|
|
6
|
+
expect(dateSchemaInput('1', '2', '2023')).toMatchSnapshot()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it.each`
|
|
10
|
+
desc | day | month | year | result
|
|
11
|
+
${'all empty'} | ${''} | ${''} | ${''} | ${{ 'full-date': { day: undefined, month: undefined, year: undefined } }}
|
|
12
|
+
${'day and month empty'} | ${''} | ${''} | ${'2020'} | ${{ 'day-and-month': { day: undefined, month: undefined } }}
|
|
13
|
+
${'day and year empty'} | ${''} | ${'11'} | ${''} | ${{ 'day-and-year': { day: undefined, year: undefined } }}
|
|
14
|
+
${'month and year empty'} | ${'12'} | ${''} | ${''} | ${{ 'month-and-year': { month: undefined, year: undefined } }}
|
|
15
|
+
${'day empty'} | ${''} | ${'3'} | ${'2021'} | ${{ day: undefined }}
|
|
16
|
+
${'month empty'} | ${'4'} | ${''} | ${'2003'} | ${{ month: undefined }}
|
|
17
|
+
${'year empty'} | ${'15'} | ${'11'} | ${''} | ${{ year: undefined }}
|
|
18
|
+
`('maps empty strings to undefined values when $desc', ({ day, month, year, result }) => {
|
|
19
|
+
expect(dateSchemaInput(day, month, year)).toEqual(expect.objectContaining(result))
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('dateSchema', () => {
|
|
24
|
+
it.each`
|
|
25
|
+
payload | expectedError | payloadDesc
|
|
26
|
+
${{}} | ${'full-date'} | ${'empty day, month and year'}
|
|
27
|
+
${{ year: '1' }} | ${'day-and-month'} | ${'empty day and month'}
|
|
28
|
+
${{ month: '2' }} | ${'day-and-year'} | ${'empty day and year'}
|
|
29
|
+
${{ day: '3' }} | ${'month-and-year'} | ${'empty month and year'}
|
|
30
|
+
${{ month: '5', year: '2023' }} | ${'day'} | ${'empty day'}
|
|
31
|
+
${{ day: '12', year: '2024' }} | ${'month'} | ${'empty month'}
|
|
32
|
+
${{ day: '15', month: '3' }} | ${'year'} | ${'empty year'}
|
|
33
|
+
${{ day: 'Ides', month: 'March', year: '44 B.C.' }} | ${'non-numeric.day'} | ${'non-numerics entered'}
|
|
34
|
+
${{ day: 'Thirteenth', month: '11', year: '1978' }} | ${'non-numeric.day'} | ${'non-numeric day'}
|
|
35
|
+
${{ day: '29', month: 'MAR', year: '2002' }} | ${'non-numeric.month'} | ${'non-numeric month '}
|
|
36
|
+
${{ day: '13', month: '1', year: 'Two thousand and five' }} | ${'non-numeric.year'} | ${'non-numeric year'}
|
|
37
|
+
${{ day: '30', month: '2', year: '1994' }} | ${'invalid-date'} | ${'an invalid date - 1994-02-40'}
|
|
38
|
+
${{ day: '1', month: '13', year: '2022' }} | ${'invalid-date'} | ${'an invalid date - 2022-13-01'}
|
|
39
|
+
${{ day: '29', month: '2', year: '2023' }} | ${'invalid-date'} | ${'an invalid date - 1994-02-40'}
|
|
40
|
+
${{ day: '-1.15', month: '18', year: '22.2222' }} | ${'invalid-date'} | ${'an invalid date - 22.2222-18-1.15'}
|
|
41
|
+
`('Error has $expectedError in details when payload has $payloadDesc', ({ payload: { day, month, year }, expectedError }) => {
|
|
42
|
+
expect(() => {
|
|
43
|
+
Joi.assert(dateSchemaInput(day, month, year), dateSchema)
|
|
44
|
+
}).toThrow(
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
details: expect.arrayContaining([
|
|
47
|
+
expect.objectContaining({
|
|
48
|
+
path: expectedError.split('.'),
|
|
49
|
+
context: expect.objectContaining({
|
|
50
|
+
label: expectedError,
|
|
51
|
+
key: expectedError.split('.').pop()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
])
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('valid date passes validation', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
Joi.assert(dateSchemaInput('12', '10', '1987'), dateSchema)
|
|
62
|
+
}).not.toThrow()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import Joi from 'joi'
|
|
3
|
+
|
|
4
|
+
export const dateSchemaInput = (unparsedDay, unparsedMonth, unparsedYear) => {
|
|
5
|
+
const day = unparsedDay === '' ? undefined : unparsedDay
|
|
6
|
+
const month = unparsedMonth === '' ? undefined : unparsedMonth
|
|
7
|
+
const year = unparsedYear === '' ? undefined : unparsedYear
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
'full-date': { day, month, year },
|
|
11
|
+
'day-and-month': { day, month },
|
|
12
|
+
'day-and-year': { day, year },
|
|
13
|
+
'month-and-year': { month, year },
|
|
14
|
+
day,
|
|
15
|
+
month,
|
|
16
|
+
year,
|
|
17
|
+
'non-numeric': { day, month, year },
|
|
18
|
+
'invalid-date': `${year}-${(month || '').padStart(2, '0')}-${(day || '').padStart(2, '0')}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const dateSchema = Joi.object({
|
|
23
|
+
'full-date': Joi.object()
|
|
24
|
+
.keys({
|
|
25
|
+
day: Joi.any(),
|
|
26
|
+
month: Joi.any(),
|
|
27
|
+
year: Joi.any()
|
|
28
|
+
})
|
|
29
|
+
.or('day', 'month', 'year'),
|
|
30
|
+
'day-and-month': Joi.object()
|
|
31
|
+
.keys({
|
|
32
|
+
day: Joi.any(),
|
|
33
|
+
month: Joi.any()
|
|
34
|
+
})
|
|
35
|
+
.or('day', 'month'),
|
|
36
|
+
'day-and-year': Joi.object()
|
|
37
|
+
.keys({
|
|
38
|
+
day: Joi.any(),
|
|
39
|
+
year: Joi.any()
|
|
40
|
+
})
|
|
41
|
+
.or('day', 'year'),
|
|
42
|
+
'month-and-year': Joi.object()
|
|
43
|
+
.keys({
|
|
44
|
+
month: Joi.any(),
|
|
45
|
+
year: Joi.any()
|
|
46
|
+
})
|
|
47
|
+
.or('month', 'year'),
|
|
48
|
+
day: Joi.any().required(),
|
|
49
|
+
month: Joi.any().required(),
|
|
50
|
+
year: Joi.any().required(),
|
|
51
|
+
'non-numeric': Joi.object().keys({
|
|
52
|
+
day: Joi.number(),
|
|
53
|
+
month: Joi.number(),
|
|
54
|
+
year: Joi.number()
|
|
55
|
+
}),
|
|
56
|
+
'invalid-date': Joi.custom((dateToValidate, helpers) => {
|
|
57
|
+
if (new Date(dateToValidate).toISOString() !== `${dateToValidate}T00:00:00.000Z`) {
|
|
58
|
+
throw helpers.error('invalid-date')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return dateToValidate
|
|
62
|
+
})
|
|
63
|
+
}).options({ abortEarly: true })
|