@defra-fish/gafl-webapp-service 1.64.0-rc.2 → 1.64.0-rc.21
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 +37 -37
- package/src/handlers/__tests__/cancel-recurring-payment-authentication-handler.spec.js +214 -0
- package/src/handlers/cancel-recurring-payment-authentication-handler.js +60 -0
- package/src/locales/cy.json +13 -1
- package/src/locales/en.json +13 -1
- package/src/pages/guidance/accessibility-statement.njk +5 -5
- package/src/pages/recurring-payments/cancel/identify/__tests__/route.spec.js +174 -0
- package/src/pages/recurring-payments/cancel/identify/cancel-rp-identify.njk +171 -4
- package/src/pages/recurring-payments/cancel/identify/route.js +51 -6
- package/src/processors/__tests__/recurring-payments-write-cache.spec.js +121 -0
- package/src/processors/recurring-payments-write-cache.js +16 -0
- package/src/routes/misc-routes.js +1 -1
- package/src/handlers/__tests__/cancel-rp-authentication-handler.spec.js +0 -18
- package/src/handlers/cancel-rp-authentication-handler.js +0 -6
- package/src/pages/recurring-payments/cancel/identify/__tests__/route.test.js +0 -106
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra-fish/gafl-webapp-service",
|
|
3
|
-
"version": "1.64.0-rc.
|
|
3
|
+
"version": "1.64.0-rc.21",
|
|
4
4
|
"description": "The websales frontend for the GAFL service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,48 +36,48 @@
|
|
|
36
36
|
"prepare": "gulp --gulpfile build/gulpfile.cjs"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@defra-fish/business-rules-lib": "1.64.0-rc.
|
|
40
|
-
"@defra-fish/connectors-lib": "1.64.0-rc.
|
|
41
|
-
"@defra/hapi-gapi": "
|
|
42
|
-
"@hapi/boom": "
|
|
43
|
-
"@hapi/catbox-redis": "
|
|
44
|
-
"@hapi/cookie": "
|
|
45
|
-
"@hapi/crumb": "
|
|
46
|
-
"@hapi/hapi": "
|
|
47
|
-
"@hapi/inert": "
|
|
48
|
-
"@hapi/joi-date": "
|
|
49
|
-
"@hapi/scooter": "
|
|
50
|
-
"@hapi/vision": "
|
|
51
|
-
"blankie": "
|
|
52
|
-
"debug": "
|
|
53
|
-
"disinfect": "
|
|
54
|
-
"find": "
|
|
55
|
-
"govuk-frontend": "
|
|
56
|
-
"hapi-i18n": "
|
|
57
|
-
"joi": "
|
|
58
|
-
"moment": "
|
|
59
|
-
"moment-timezone": "
|
|
60
|
-
"node-fetch": "
|
|
61
|
-
"nunjucks": "
|
|
62
|
-
"openid-client": "
|
|
63
|
-
"semver": "
|
|
64
|
-
"uuid": "
|
|
39
|
+
"@defra-fish/business-rules-lib": "1.64.0-rc.21",
|
|
40
|
+
"@defra-fish/connectors-lib": "1.64.0-rc.21",
|
|
41
|
+
"@defra/hapi-gapi": "2.0.0",
|
|
42
|
+
"@hapi/boom": "9.1.2",
|
|
43
|
+
"@hapi/catbox-redis": "6.0.2",
|
|
44
|
+
"@hapi/cookie": "11.0.2",
|
|
45
|
+
"@hapi/crumb": "8.0.1",
|
|
46
|
+
"@hapi/hapi": "20.2.1",
|
|
47
|
+
"@hapi/inert": "6.0.5",
|
|
48
|
+
"@hapi/joi-date": "2.0.1",
|
|
49
|
+
"@hapi/scooter": "6.0.1",
|
|
50
|
+
"@hapi/vision": "6.1.0",
|
|
51
|
+
"blankie": "5.0.0",
|
|
52
|
+
"debug": "4.3.3",
|
|
53
|
+
"disinfect": "1.1.0",
|
|
54
|
+
"find": "0.3.0",
|
|
55
|
+
"govuk-frontend": "5.10.2",
|
|
56
|
+
"hapi-i18n": "3.0.1",
|
|
57
|
+
"joi": "17.6.0",
|
|
58
|
+
"moment": "2.29.1",
|
|
59
|
+
"moment-timezone": "0.5.34",
|
|
60
|
+
"node-fetch": "2.7.0",
|
|
61
|
+
"nunjucks": "3.2.3",
|
|
62
|
+
"openid-client": "4.9.1",
|
|
63
|
+
"semver": "7.3.5",
|
|
64
|
+
"uuid": "8.3.2"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@hapi/catbox-memory": "
|
|
68
|
-
"del": "
|
|
69
|
-
"gulp": "
|
|
70
|
-
"gulp-concat": "
|
|
71
|
-
"gulp-merge": "
|
|
72
|
-
"gulp-minify": "
|
|
73
|
-
"gulp-sass": "
|
|
74
|
-
"gulp-sourcemaps": "
|
|
75
|
-
"sass": "
|
|
67
|
+
"@hapi/catbox-memory": "5.0.1",
|
|
68
|
+
"del": "6.0.0",
|
|
69
|
+
"gulp": "4.0.2",
|
|
70
|
+
"gulp-concat": "2.6.1",
|
|
71
|
+
"gulp-merge": "0.1.1",
|
|
72
|
+
"gulp-minify": "3.1.0",
|
|
73
|
+
"gulp-sass": "5.1.0",
|
|
74
|
+
"gulp-sourcemaps": "3.0.0",
|
|
75
|
+
"sass": "1.69.3"
|
|
76
76
|
},
|
|
77
77
|
"jest": {
|
|
78
78
|
"setupFilesAfterEnv": [
|
|
79
79
|
"./gafl-jest-matchers.js"
|
|
80
80
|
]
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "d93d2a58352234a4f929a566809b338516ebeb1c"
|
|
83
83
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import handler from '../cancel-recurring-payment-authentication-handler'
|
|
2
|
+
import { CANCEL_RP_IDENTIFY, CANCEL_RP_DETAILS } from '../../uri.js'
|
|
3
|
+
import { addLanguageCodeToUri } from '../../processors/uri-helper.js'
|
|
4
|
+
import { salesApi } from '@defra-fish/connectors-lib'
|
|
5
|
+
|
|
6
|
+
jest.mock('../../processors/uri-helper.js')
|
|
7
|
+
jest.mock('@defra-fish/connectors-lib')
|
|
8
|
+
jest.mock('../../processors/renewals-write-cache.js', () => ({
|
|
9
|
+
setUpCacheFromAuthenticationResult: jest.fn().mockResolvedValue(undefined),
|
|
10
|
+
setUpPayloads: jest.fn().mockResolvedValue(undefined)
|
|
11
|
+
}))
|
|
12
|
+
jest.mock('@defra-fish/business-rules-lib', () => ({
|
|
13
|
+
validation: {
|
|
14
|
+
contact: {
|
|
15
|
+
createBirthDateValidator: () => ({ validateAsync: async () => '1970-01-01' }),
|
|
16
|
+
createOverseasPostcodeValidator: () => ({ validateAsync: async () => 'AA1 1AA' })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}))
|
|
20
|
+
jest.mock('../../uri.js', () => ({
|
|
21
|
+
CANCEL_RP_IDENTIFY: { page: 'cancel-rp-identify page', uri: Symbol('cancel-rp-identify-uri') },
|
|
22
|
+
CANCEL_RP_DETAILS: { uri: Symbol('cancel-rp-details-uri') }
|
|
23
|
+
}))
|
|
24
|
+
jest.mock('../../processors/recurring-payments-write-cache.js')
|
|
25
|
+
|
|
26
|
+
const getSampleRequest = (payloadOverride = {}) => {
|
|
27
|
+
const getCurrentPermission = jest.fn(async () => ({}))
|
|
28
|
+
const setCurrentPermission = jest.fn()
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
cache: () => ({
|
|
32
|
+
helpers: {
|
|
33
|
+
page: {
|
|
34
|
+
getCurrentPermission: jest.fn(async () => ({
|
|
35
|
+
payload: {
|
|
36
|
+
referenceNumber: 'ABC123',
|
|
37
|
+
'date-of-birth-day': '01',
|
|
38
|
+
'date-of-birth-month': '01',
|
|
39
|
+
'date-of-birth-year': '1970',
|
|
40
|
+
postcode: 'AA1 1AA',
|
|
41
|
+
...payloadOverride
|
|
42
|
+
}
|
|
43
|
+
})),
|
|
44
|
+
setCurrentPermission
|
|
45
|
+
},
|
|
46
|
+
status: {
|
|
47
|
+
getCurrentPermission,
|
|
48
|
+
setCurrentPermission
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const getSampleResponseTooklkit = () => ({
|
|
56
|
+
redirectWithLanguageCode: jest.fn().mockReturnValue('redirected')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const invokeHandlerWithMocks = async ({ salesApiResponse, decoratedIdentifyUri } = {}) => {
|
|
60
|
+
if (typeof salesApiResponse !== 'undefined') {
|
|
61
|
+
salesApi.authenticateRecurringPayment.mockResolvedValueOnce(salesApiResponse)
|
|
62
|
+
}
|
|
63
|
+
if (decoratedIdentifyUri) {
|
|
64
|
+
addLanguageCodeToUri.mockReturnValueOnce(decoratedIdentifyUri)
|
|
65
|
+
}
|
|
66
|
+
const request = getSampleRequest()
|
|
67
|
+
const h = getSampleResponseTooklkit()
|
|
68
|
+
if (decoratedIdentifyUri) {
|
|
69
|
+
h.redirect = jest.fn().mockReturnValue('redirect-response')
|
|
70
|
+
}
|
|
71
|
+
const result = await handler(request, h)
|
|
72
|
+
return { request, h, result }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const mockSuccessResponse = () => ({
|
|
76
|
+
permission: { id: 'perm-id' },
|
|
77
|
+
recurringPayment: { id: 'rcp-id', status: 0, cancelledDate: null }
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('Cancel RP Authentication Handler', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
jest.resetAllMocks()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('Successful authentication', () => {
|
|
86
|
+
it('returns the redirect result', async () => {
|
|
87
|
+
const { result } = await invokeHandlerWithMocks({
|
|
88
|
+
salesApiResponse: mockSuccessResponse()
|
|
89
|
+
})
|
|
90
|
+
expect(result).toBe('redirected')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('redirects to details', async () => {
|
|
94
|
+
const { h } = await invokeHandlerWithMocks({
|
|
95
|
+
salesApiResponse: mockSuccessResponse()
|
|
96
|
+
})
|
|
97
|
+
expect(h.redirectWithLanguageCode).toHaveBeenCalledWith(CANCEL_RP_DETAILS.uri)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('marks status as authorised', async () => {
|
|
101
|
+
const { request } = await invokeHandlerWithMocks({
|
|
102
|
+
salesApiResponse: mockSuccessResponse()
|
|
103
|
+
})
|
|
104
|
+
expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith({ authentication: { authorised: true } })
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('Unsuccessful authentication - no match', () => {
|
|
109
|
+
it('redirects to the decorated identify URI', async () => {
|
|
110
|
+
const { h } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
|
|
111
|
+
expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('sets page cache error and preserves payload', async () => {
|
|
115
|
+
const { request } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
|
|
116
|
+
expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
|
|
117
|
+
CANCEL_RP_IDENTIFY.page,
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
payload: expect.any(Object),
|
|
120
|
+
error: { referenceNumber: 'not-found' }
|
|
121
|
+
})
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('marks status as unauthorised', async () => {
|
|
126
|
+
const { request } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
|
|
127
|
+
expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
|
|
128
|
+
expect.objectContaining({ authentication: { authorised: false } })
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('Unsuccessful authentication - no recurring payment agreement', () => {
|
|
134
|
+
it('redirects to the decorated identify URI', async () => {
|
|
135
|
+
const { h } = await invokeHandlerWithMocks({
|
|
136
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
|
|
137
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
138
|
+
})
|
|
139
|
+
expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('sets page cache error for no RCP setup', async () => {
|
|
143
|
+
const { request } = await invokeHandlerWithMocks({
|
|
144
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
|
|
145
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
146
|
+
})
|
|
147
|
+
expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
|
|
148
|
+
CANCEL_RP_IDENTIFY.page,
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
payload: expect.any(Object),
|
|
151
|
+
error: { recurringPayment: 'not-set-up' }
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('marks status as unauthorised', async () => {
|
|
157
|
+
const { request } = await invokeHandlerWithMocks({
|
|
158
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
|
|
159
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
160
|
+
})
|
|
161
|
+
expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
|
|
162
|
+
expect.objectContaining({ authentication: { authorised: false } })
|
|
163
|
+
)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('Unsuccessful authentication - RCP cancelled', () => {
|
|
168
|
+
it('redirects to the decorated identify URI', async () => {
|
|
169
|
+
const { h } = await invokeHandlerWithMocks({
|
|
170
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
|
|
171
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
172
|
+
})
|
|
173
|
+
expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('sets page cache error for RCP cancelled', async () => {
|
|
177
|
+
const { request } = await invokeHandlerWithMocks({
|
|
178
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
|
|
179
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
180
|
+
})
|
|
181
|
+
expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
|
|
182
|
+
CANCEL_RP_IDENTIFY.page,
|
|
183
|
+
expect.objectContaining({
|
|
184
|
+
payload: expect.any(Object),
|
|
185
|
+
error: { recurringPayment: 'rcp-cancelled' }
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('marks status as unauthorised', async () => {
|
|
191
|
+
const { request } = await invokeHandlerWithMocks({
|
|
192
|
+
salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
|
|
193
|
+
decoratedIdentifyUri: 'decorated-identify-uri'
|
|
194
|
+
})
|
|
195
|
+
expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
|
|
196
|
+
expect.objectContaining({ authentication: { authorised: false } })
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('uses referenceNumber from status when payload is missing', async () => {
|
|
202
|
+
salesApi.authenticateRecurringPayment.mockResolvedValueOnce({
|
|
203
|
+
permission: { id: 'perm-id' },
|
|
204
|
+
recurringPayment: { id: 'rcp-id', status: 0, cancelledDate: null }
|
|
205
|
+
})
|
|
206
|
+
const request = getSampleRequest({ referenceNumber: undefined })
|
|
207
|
+
request.cache().helpers.status.getCurrentPermission.mockReturnValueOnce({
|
|
208
|
+
referenceNumber: 'A1B2C3'
|
|
209
|
+
})
|
|
210
|
+
const h = getSampleResponseTooklkit()
|
|
211
|
+
await handler(request, h)
|
|
212
|
+
expect(salesApi.authenticateRecurringPayment).toHaveBeenCalledWith('A1B2C3', expect.anything(), expect.anything())
|
|
213
|
+
})
|
|
214
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { CANCEL_RP_IDENTIFY, CANCEL_RP_DETAILS } from '../../src/uri.js'
|
|
2
|
+
import { addLanguageCodeToUri } from '../processors/uri-helper.js'
|
|
3
|
+
import { salesApi } from '@defra-fish/connectors-lib'
|
|
4
|
+
import { validation } from '@defra-fish/business-rules-lib'
|
|
5
|
+
import { setupCancelRecurringPaymentCacheFromAuthResult } from '../processors/recurring-payments-write-cache.js'
|
|
6
|
+
import Joi from 'joi'
|
|
7
|
+
|
|
8
|
+
const buildAuthFailure = (referenceNumber, payload, error) => ({
|
|
9
|
+
page: {
|
|
10
|
+
page: CANCEL_RP_IDENTIFY.page,
|
|
11
|
+
data: { payload, error }
|
|
12
|
+
},
|
|
13
|
+
status: {
|
|
14
|
+
referenceNumber,
|
|
15
|
+
authentication: { authorised: false }
|
|
16
|
+
},
|
|
17
|
+
redirectPath: CANCEL_RP_IDENTIFY.uri
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const applyAuthFailure = async (request, h, failure) => {
|
|
21
|
+
await request.cache().helpers.page.setCurrentPermission(failure.page.page, failure.page.data)
|
|
22
|
+
await request.cache().helpers.status.setCurrentPermission(failure.status)
|
|
23
|
+
return h.redirect(addLanguageCodeToUri(request, failure.redirectPath))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cancelRecurringPaymentAuthenticationHandler = async (request, h) => {
|
|
27
|
+
const { payload } = await request.cache().helpers.page.getCurrentPermission(CANCEL_RP_IDENTIFY.page)
|
|
28
|
+
const permission = await request.cache().helpers.status.getCurrentPermission()
|
|
29
|
+
|
|
30
|
+
const referenceNumber = payload.referenceNumber || permission.referenceNumber
|
|
31
|
+
|
|
32
|
+
const dateOfBirth = await validation.contact
|
|
33
|
+
.createBirthDateValidator(Joi)
|
|
34
|
+
.validateAsync(`${payload['date-of-birth-year']}-${payload['date-of-birth-month']}-${payload['date-of-birth-day']}`)
|
|
35
|
+
const postcode = await validation.contact.createOverseasPostcodeValidator(Joi).validateAsync(payload.postcode)
|
|
36
|
+
|
|
37
|
+
const authenticationResult = await salesApi.authenticateRecurringPayment(referenceNumber, dateOfBirth, postcode)
|
|
38
|
+
|
|
39
|
+
const failures = error => applyAuthFailure(request, h, buildAuthFailure(referenceNumber, payload, error))
|
|
40
|
+
|
|
41
|
+
if (!authenticationResult) {
|
|
42
|
+
return failures({ referenceNumber: 'not-found' })
|
|
43
|
+
}
|
|
44
|
+
if (!authenticationResult.recurringPayment) {
|
|
45
|
+
return failures({ recurringPayment: 'not-set-up' })
|
|
46
|
+
}
|
|
47
|
+
if (authenticationResult.recurringPayment.cancelledDate) {
|
|
48
|
+
return failures({ recurringPayment: 'rcp-cancelled' })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await setupCancelRecurringPaymentCacheFromAuthResult(request, authenticationResult)
|
|
52
|
+
|
|
53
|
+
await request.cache().helpers.status.setCurrentPermission({
|
|
54
|
+
authentication: { authorised: true }
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return h.redirectWithLanguageCode(CANCEL_RP_DETAILS.uri)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default cancelRecurringPaymentAuthenticationHandler
|
package/src/locales/cy.json
CHANGED
|
@@ -140,6 +140,19 @@
|
|
|
140
140
|
"back": "Yn ôl",
|
|
141
141
|
"buy_another_licence": "Prynu trwydded arall",
|
|
142
142
|
"buy_different_licence": "Prynu trwydded wahanol",
|
|
143
|
+
"cancel_rp_identify_caption": "Cancel your recurring card payment agreement",
|
|
144
|
+
"cancel_rp_identify_title": "Enter your details",
|
|
145
|
+
"cancel_rp_identify_body": "Cancelling your recurring card payment agreement will not cancel or refund your current licence. You can still fish until your current licence expires but your licence will not renew automatically.",
|
|
146
|
+
"cancel_rp_identify_ref_number": "Last 6 characters of your licence number",
|
|
147
|
+
"cancel_rp_identify_ref_number_hint": "For example, F4A315",
|
|
148
|
+
"cancel_rp_identify_ref_number_error": "Enter the last 6 characters of your licence number",
|
|
149
|
+
"cancel_rp_identify_ref_pattern": "The last six characters of your licence number don’t look right. Check and enter again",
|
|
150
|
+
"cancel_rp_identify_dob": "Date of birth",
|
|
151
|
+
"cancel_rp_identify_dob_hint": "For example, 31 3 1980",
|
|
152
|
+
"cancel_rp_identify_postcode": "Postcode",
|
|
153
|
+
"cancel_rp_identify_postcode_hint": "For example, WA4 1AB",
|
|
154
|
+
"cancel_rp_identify_postcode_error": "Enter a postcode",
|
|
155
|
+
"cancel_rp_identify_postcode_pattern": "Your postcode doesn’t look right. Check and enter again",
|
|
143
156
|
"change_licence_details_you": "Adolygu neu newid manylion eich trwydded",
|
|
144
157
|
"change_licence_details_other": "Adolygu neu newid manylion y drwydded",
|
|
145
158
|
"change_licence_number": "Newid rhif y drwydded",
|
|
@@ -775,7 +788,6 @@
|
|
|
775
788
|
"rp_cancel_complete_title": "Cancel your recurring card payment agreement - complete",
|
|
776
789
|
"rp_cancel_confirm_title": "Cancel your recurring card payment agreement - confirm",
|
|
777
790
|
"rp_cancel_details_title": "Cancel your recurring card payment agreement - details",
|
|
778
|
-
"rp_cancel_identify_title": "Cancel your recurring card payment agreement - identify",
|
|
779
791
|
"save_changes": "Cadw newidiadau",
|
|
780
792
|
"server_error_title_suffix": " - GOV.UK",
|
|
781
793
|
"server_error_bulletpoint_1": "Os ydych chi wedi gofyn am hysbysiad dros e-bost neu neges destun, dylai’r hysbysiad gyrraedd o fewn 3 awr os bydd y taliad wedi cael ei gymryd.",
|
package/src/locales/en.json
CHANGED
|
@@ -140,6 +140,19 @@
|
|
|
140
140
|
"back": "Back",
|
|
141
141
|
"buy_another_licence": "Buy another licence",
|
|
142
142
|
"buy_different_licence": "Buy a different licence",
|
|
143
|
+
"cancel_rp_identify_caption": "Cancel your recurring card payment agreement",
|
|
144
|
+
"cancel_rp_identify_title": "Enter your details",
|
|
145
|
+
"cancel_rp_identify_body": "Cancelling your recurring card payment agreement will not cancel or refund your current licence. You can still fish until your current licence expires but your licence will not renew automatically.",
|
|
146
|
+
"cancel_rp_identify_ref_number": "Last 6 characters of your licence number",
|
|
147
|
+
"cancel_rp_identify_ref_number_hint": "For example, F4A315",
|
|
148
|
+
"cancel_rp_identify_ref_number_error": "Enter the last 6 characters of your licence number",
|
|
149
|
+
"cancel_rp_identify_ref_pattern": "The last six characters of your licence number don’t look right. Check and enter again",
|
|
150
|
+
"cancel_rp_identify_dob": "Date of birth",
|
|
151
|
+
"cancel_rp_identify_dob_hint": "For example, 31 3 1980",
|
|
152
|
+
"cancel_rp_identify_postcode": "Postcode",
|
|
153
|
+
"cancel_rp_identify_postcode_hint": "For example, WA4 1AB",
|
|
154
|
+
"cancel_rp_identify_postcode_error": "Enter a postcode",
|
|
155
|
+
"cancel_rp_identify_postcode_pattern": "Your postcode doesn’t look right. Check and enter again",
|
|
143
156
|
"change_licence_details_you": "Review or change the licence details",
|
|
144
157
|
"change_licence_details_other": "Review or change the licence details",
|
|
145
158
|
"change_licence_number": "Change licence number",
|
|
@@ -775,7 +788,6 @@
|
|
|
775
788
|
"rp_cancel_complete_title": "Cancel your recurring card payment agreement - complete",
|
|
776
789
|
"rp_cancel_confirm_title": "Cancel your recurring card payment agreement - confirm",
|
|
777
790
|
"rp_cancel_details_title": "Cancel your recurring card payment agreement - details",
|
|
778
|
-
"rp_cancel_identify_title": "Cancel your recurring card payment agreement - identify",
|
|
779
791
|
"save_changes": "Save changes",
|
|
780
792
|
"server_error_title_suffix": " - GOV.UK",
|
|
781
793
|
"server_error_bulletpoint_1": "If you have requested an email or text notification, this should be delivered in 3 hours if a payment has been taken.",
|
|
@@ -59,17 +59,17 @@
|
|
|
59
59
|
<p class="govuk-body">{{ mssgs.access_statement_tech_body }}</p>
|
|
60
60
|
|
|
61
61
|
<h2 class="govuk-heading-m">{{ mssgs.access_statement_compliance_heading }}</h2>
|
|
62
|
-
<p class="govuk-body">{{ mssgs.access_statement_compliance_body_1 }}<a href="https://www.w3.org/TR/
|
|
63
|
-
<p class="govuk-body">{{ mssgs.access_statement_compliance_body_2 }}<a href="https://www.w3.org/TR/
|
|
62
|
+
<p class="govuk-body">{{ mssgs.access_statement_compliance_body_1 }}<a href="https://www.w3.org/TR/WCAG22/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_1 }}</a>{{ mssgs.full_stop }}</p>
|
|
63
|
+
<p class="govuk-body">{{ mssgs.access_statement_compliance_body_2 }}<a href="https://www.w3.org/TR/WCAG22/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_2 }}</a>{{ mssgs.full_stop }}</p>
|
|
64
64
|
|
|
65
65
|
<h2 class="govuk-heading-m">{{ mssgs.access_statement_nonaccess_heading }}</h2>
|
|
66
66
|
<p class="govuk-body">{{ mssgs.access_statement_nonaccess_body }}</p>
|
|
67
67
|
|
|
68
68
|
<h2 class="govuk-heading-m">{{ mssgs.access_statement_noncompliance_heading }}</h2>
|
|
69
69
|
<ul class="govuk-list govuk-list--bullet">
|
|
70
|
-
<li> {{ mssgs.access_statement_noncompliance_body_1 }}<a href="https://www.w3.org/WAI/
|
|
71
|
-
<li> {{ mssgs.access_statement_noncompliance_body_3 }}<a href="https://www.w3.org/WAI/
|
|
72
|
-
<li> {{ mssgs.access_statement_noncompliance_body_5 }}<a href="https://www.w3.org/WAI/
|
|
70
|
+
<li> {{ mssgs.access_statement_noncompliance_body_1 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard-no-exception.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_1 }}</a>{{ mssgs.access_statement_noncompliance_body_2 }}</li>
|
|
71
|
+
<li> {{ mssgs.access_statement_noncompliance_body_3 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_2 }}</a>{{ mssgs.access_statement_noncompliance_body_4 }} </li>
|
|
72
|
+
<li> {{ mssgs.access_statement_noncompliance_body_5 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_3 }}</a>{{ mssgs.access_statement_noncompliance_body_6 }} </li>
|
|
73
73
|
</ul>
|
|
74
74
|
|
|
75
75
|
<h2 class="govuk-heading-m">{{ mssgs.access_statement_burden_title }}</h2>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import pageRoute from '../../../../../routes/page-route.js'
|
|
2
|
+
import { CANCEL_RP_IDENTIFY } from '../../../../../uri.js'
|
|
3
|
+
import { addLanguageCodeToUri } from '../../../../../processors/uri-helper.js'
|
|
4
|
+
import { getData, validator } from '../route.js'
|
|
5
|
+
import { dateOfBirthValidator, getDateErrorFlags } from '../../../../../schema/validators/validators.js'
|
|
6
|
+
import { validation } from '@defra-fish/business-rules-lib'
|
|
7
|
+
import GetDataRedirect from '../../../../../handlers/get-data-redirect.js'
|
|
8
|
+
|
|
9
|
+
require('../route.js')
|
|
10
|
+
|
|
11
|
+
jest.mock('../../../../../routes/page-route.js')
|
|
12
|
+
jest.mock('../../../../../uri.js', () => ({
|
|
13
|
+
...jest.requireActual('../../../../../uri.js'),
|
|
14
|
+
CANCEL_RP_IDENTIFY: { page: 'cancel-rp identify page', uri: Symbol('cancel-rp identify uri') },
|
|
15
|
+
CANCEL_RP_AUTHENTICATE: { uri: 'cancel-rp-authenticate uri' }
|
|
16
|
+
}))
|
|
17
|
+
jest.mock('../../../../../processors/uri-helper.js')
|
|
18
|
+
jest.mock('../../../../../schema/validators/validators.js')
|
|
19
|
+
|
|
20
|
+
describe('cancel recurring payment identify route', () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.restoreAllMocks()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('getData', () => {
|
|
26
|
+
const getMockRequest = (referenceNumber, pageGet = async () => ({})) => ({
|
|
27
|
+
cache: () => ({
|
|
28
|
+
helpers: {
|
|
29
|
+
transaction: {
|
|
30
|
+
getCurrentPermission: () => ({
|
|
31
|
+
referenceNumber
|
|
32
|
+
}),
|
|
33
|
+
setCurrentPermission: jest.fn()
|
|
34
|
+
},
|
|
35
|
+
page: {
|
|
36
|
+
getCurrentPermission: pageGet
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
i18n: {
|
|
41
|
+
getCatalog: () => [],
|
|
42
|
+
getLocales: () => []
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('passes correct page name when getting page cache', async () => {
|
|
47
|
+
const pageGet = jest.fn(() => ({}))
|
|
48
|
+
await getData(getMockRequest(undefined, pageGet))
|
|
49
|
+
expect(pageGet).toHaveBeenCalledWith(CANCEL_RP_IDENTIFY.page)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it.each([['09F6VF'], ['013AH6'], ['LK563F']])('returns referenceNumber when permission includes %s', async referenceNumber => {
|
|
53
|
+
const result = await getData(getMockRequest(referenceNumber))
|
|
54
|
+
expect(result.referenceNumber).toEqual(referenceNumber)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('throws redirect when permission number fails validation', async () => {
|
|
58
|
+
const spy = jest
|
|
59
|
+
.spyOn(validation.permission, 'permissionNumberUniqueComponentValidator')
|
|
60
|
+
.mockReturnValue({ validate: () => ({ error: true }) })
|
|
61
|
+
const request = getMockRequest('BAD123')
|
|
62
|
+
request.cache().helpers.transaction.setCurrentPermission = jest.fn()
|
|
63
|
+
await expect(getData(request)).rejects.toBeInstanceOf(GetDataRedirect)
|
|
64
|
+
spy.mockRestore()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('adds return value of getErrorFlags to the page data', async () => {
|
|
68
|
+
const errorFlags = { unique: Symbol('error-flags') }
|
|
69
|
+
getDateErrorFlags.mockReturnValueOnce(errorFlags)
|
|
70
|
+
const result = await getData(getMockRequest())
|
|
71
|
+
expect(result).toEqual(expect.objectContaining(errorFlags))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('passes error to getErrorFlags', async () => {
|
|
75
|
+
const error = Symbol('error')
|
|
76
|
+
await getData(getMockRequest(undefined, async () => ({ error })))
|
|
77
|
+
expect(getDateErrorFlags).toHaveBeenCalledWith(error)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it.each([
|
|
81
|
+
['full-date', 'object.missing'],
|
|
82
|
+
['day', 'any.required']
|
|
83
|
+
])('adds error details (%s: %s) to the page data', async (errorKey, errorValue) => {
|
|
84
|
+
const pageGet = async () => ({
|
|
85
|
+
error: { [errorKey]: errorValue }
|
|
86
|
+
})
|
|
87
|
+
const result = await getData(getMockRequest(undefined, pageGet))
|
|
88
|
+
expect(result.error).toEqual({ errorKey, errorValue })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('omits error if there is no error', async () => {
|
|
92
|
+
const result = await getData(getMockRequest())
|
|
93
|
+
expect(result.error).toBeUndefined()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('pageRoute', () => {
|
|
98
|
+
it('passes CANCEL_RP_IDENTIFY.page as the first argument to pageRoute', () => {
|
|
99
|
+
expect(pageRoute.mock.calls[0][0]).toBe(CANCEL_RP_IDENTIFY.page)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('passes CANCEL_RP_IDENTIFY.uri as the second argument to pageRoute', () => {
|
|
103
|
+
expect(pageRoute.mock.calls[0][1]).toBe(CANCEL_RP_IDENTIFY.uri)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('calls pageRoute with validator, nextPage function, and getData', () => {
|
|
107
|
+
expect(pageRoute).toBeCalledWith(CANCEL_RP_IDENTIFY.page, CANCEL_RP_IDENTIFY.uri, validator, expect.any(Function), getData)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('page route next', () => {
|
|
112
|
+
const getNextPage = () => pageRoute.mock.calls[0][3]
|
|
113
|
+
|
|
114
|
+
it('passes a function', () => {
|
|
115
|
+
const nextPage = getNextPage()
|
|
116
|
+
expect(typeof nextPage).toBe('function')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('calls addLanguageCodeToUri', () => {
|
|
120
|
+
const nextPage = getNextPage()
|
|
121
|
+
nextPage()
|
|
122
|
+
expect(addLanguageCodeToUri).toHaveBeenCalled()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('passes request to addLanguageCodeToUri', () => {
|
|
126
|
+
const request = Symbol('request')
|
|
127
|
+
const nextPage = getNextPage()
|
|
128
|
+
nextPage(request)
|
|
129
|
+
expect(addLanguageCodeToUri).toHaveBeenCalledWith(request, expect.anything())
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('returns result of addLanguageCodeToUri', () => {
|
|
133
|
+
const expectedResult = Symbol('add language code to uri')
|
|
134
|
+
const nextPage = getNextPage()
|
|
135
|
+
addLanguageCodeToUri.mockReturnValueOnce(expectedResult)
|
|
136
|
+
expect(nextPage()).toBe(expectedResult)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('validator', () => {
|
|
141
|
+
const getMockPayload = (postcode = 'AA1 1AA', referenceNumber = 'A1B2C3') => ({
|
|
142
|
+
postcode,
|
|
143
|
+
referenceNumber
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('fails if dateOfBirthValidator throws', () => {
|
|
147
|
+
const expectedError = new Error('expected error')
|
|
148
|
+
dateOfBirthValidator.mockImplementationOnce(() => {
|
|
149
|
+
throw expectedError
|
|
150
|
+
})
|
|
151
|
+
expect(() => validator(getMockPayload())).toThrow(expectedError)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('passes if dateOfBirthValidator succeeds', () => {
|
|
155
|
+
expect(() => validator(getMockPayload())).not.toThrow()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('passes payload to dateOfBirthValidator', () => {
|
|
159
|
+
const p = getMockPayload()
|
|
160
|
+
validator(p)
|
|
161
|
+
expect(dateOfBirthValidator).toHaveBeenCalledWith(p)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('fails if permission number is invalid', () => {
|
|
165
|
+
jest.spyOn(validation.permission, 'permissionNumberUniqueComponentValidator').mockReturnValue({ validate: () => ({ error: 'bad' }) })
|
|
166
|
+
expect(() => validator({ referenceNumber: 'BAD', postcode: 'AA1 1AA' })).toThrow()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('fails if postcode is invalid', () => {
|
|
170
|
+
jest.spyOn(validation.contact, 'createOverseasPostcodeValidator').mockReturnValue({ validate: () => ({ error: 'bad' }) })
|
|
171
|
+
expect(() => validator({ referenceNumber: 'ABC123', postcode: 'ZZZ' })).toThrow()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|
|
@@ -1,15 +1,175 @@
|
|
|
1
1
|
{% extends "layout.njk" %}
|
|
2
2
|
|
|
3
|
+
{% from "fieldset/macro.njk" import govukFieldset %}
|
|
3
4
|
{% from "button/macro.njk" import govukButton %}
|
|
4
5
|
{% from "page-title.njk" import pageTitle %}
|
|
6
|
+
{% from "date-input/macro.njk" import govukDateInput %}
|
|
7
|
+
{% from "input/macro.njk" import govukInput %}
|
|
8
|
+
{% from "error-summary.njk" import errorSummary %}
|
|
9
|
+
|
|
10
|
+
{% set title = mssgs.cancel_rp_identify_title %}
|
|
5
11
|
|
|
6
|
-
{% set title = mssgs.rp_cancel_identify_title %}
|
|
7
12
|
{% block pageTitle %}{{ pageTitle(title, error, mssgs) }}{% endblock %}
|
|
8
13
|
|
|
14
|
+
{%
|
|
15
|
+
set errorMap = {
|
|
16
|
+
'referenceNumber' : {
|
|
17
|
+
'string.empty': { text: mssgs.cancel_rp_identify_ref_number_error, ref: '#ref' },
|
|
18
|
+
'string.pattern.base': { text: mssgs.cancel_rp_identify_ref_pattern, ref: '#ref' }
|
|
19
|
+
},
|
|
20
|
+
'postcode': {
|
|
21
|
+
'string.empty': { ref: '#postcode', text: mssgs.cancel_rp_identify_postcode_error },
|
|
22
|
+
'string.pattern.base': { ref: '#postcode', text: mssgs.cancel_rp_identify_postcode_pattern }
|
|
23
|
+
},
|
|
24
|
+
'full-date': {
|
|
25
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error }
|
|
26
|
+
},
|
|
27
|
+
'day-and-month': {
|
|
28
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_month }
|
|
29
|
+
},
|
|
30
|
+
'day-and-year': {
|
|
31
|
+
'object.missing': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day_and_year }
|
|
32
|
+
},
|
|
33
|
+
'month-and-year': {
|
|
34
|
+
'object.missing': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month_and_year }
|
|
35
|
+
},
|
|
36
|
+
'day': {
|
|
37
|
+
'any.required': { ref: '#date-of-birth-day', text: mssgs.dob_error_missing_day }
|
|
38
|
+
},
|
|
39
|
+
'month': {
|
|
40
|
+
'any.required': { ref: '#date-of-birth-month', text: mssgs.dob_error_missing_month }
|
|
41
|
+
},
|
|
42
|
+
'year': {
|
|
43
|
+
'any.required': { ref: '#date-of-birth-year', text: mssgs.dob_error_missing_year }
|
|
44
|
+
},
|
|
45
|
+
'non-numeric': {
|
|
46
|
+
'number.base': { ref: '#date-of-birth-day', text: mssgs.dob_error_non_numeric }
|
|
47
|
+
},
|
|
48
|
+
'invalid-date': {
|
|
49
|
+
'any.custom': { ref: '#date-of-birth-day', text: mssgs.dob_error_date_real }
|
|
50
|
+
},
|
|
51
|
+
'date-range': {
|
|
52
|
+
'date.min': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_min },
|
|
53
|
+
'date.max': { ref: '#date-of-birth-day', text: mssgs.dob_error_year_max }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
%}
|
|
57
|
+
|
|
58
|
+
{% set dateInputItems = [
|
|
59
|
+
{
|
|
60
|
+
label: mssgs.date_day,
|
|
61
|
+
name: "day",
|
|
62
|
+
classes: "govuk-input--width-2 govuk-input--error" if data.isDayError else "govuk-input--width-2",
|
|
63
|
+
value: payload['date-of-birth-day'],
|
|
64
|
+
attributes: { maxlength : 2 }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: mssgs.date_month,
|
|
68
|
+
name: "month",
|
|
69
|
+
classes: "govuk-input--width-2 govuk-input--error" if data.isMonthError else "govuk-input--width-2",
|
|
70
|
+
value: payload['date-of-birth-month'],
|
|
71
|
+
attributes: { maxlength : 2 }
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: mssgs.date_year,
|
|
75
|
+
name: "year",
|
|
76
|
+
classes: "govuk-input--width-4 govuk-input--error" if data.isYearError else "govuk-input--width-4",
|
|
77
|
+
value: payload['date-of-birth-year'],
|
|
78
|
+
attributes: { maxlength : 4 }
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
%}
|
|
82
|
+
|
|
9
83
|
{% block content %}
|
|
10
84
|
<div class="govuk-grid-row">
|
|
11
|
-
|
|
12
|
-
|
|
85
|
+
<div class="govuk-grid-column-two-thirds">
|
|
86
|
+
{{ errorSummary(error, errorMap, mssgs.there_is_a_problem) }}
|
|
87
|
+
<form method="post" class="govuk-!-margin-bottom-6">
|
|
88
|
+
|
|
89
|
+
{% set legendHtml %}
|
|
90
|
+
<span class="govuk-caption-l">{{ mssgs.cancel_rp_identify_caption }}</span>
|
|
91
|
+
{{ title }}
|
|
92
|
+
{% endset %}
|
|
93
|
+
|
|
94
|
+
{% call govukFieldset({
|
|
95
|
+
describedBy: "ref-hint date-of-birth-hint postcode-hint",
|
|
96
|
+
legend: {
|
|
97
|
+
html: legendHtml,
|
|
98
|
+
classes: "govuk-fieldset__legend--l govuk-!-margin-bottom-3",
|
|
99
|
+
isPageHeading: true
|
|
100
|
+
}
|
|
101
|
+
}) %}
|
|
102
|
+
|
|
103
|
+
<p class="govuk-body-m">{{ mssgs.cancel_rp_identify_body }}</p>
|
|
104
|
+
|
|
105
|
+
{{ govukInput({
|
|
106
|
+
id: "ref",
|
|
107
|
+
name: "referenceNumber",
|
|
108
|
+
type: "text",
|
|
109
|
+
classes: "govuk-input--width-10",
|
|
110
|
+
errorMessage: { text: mssgs.cancel_rp_identify_ref_number_error } if error['referenceNumber'],
|
|
111
|
+
label: {
|
|
112
|
+
text: mssgs.cancel_rp_identify_ref_number,
|
|
113
|
+
classes: "govuk-!-font-weight-bold"
|
|
114
|
+
},
|
|
115
|
+
hint: {
|
|
116
|
+
text: mssgs.cancel_rp_identify_ref_number_hint
|
|
117
|
+
},
|
|
118
|
+
attributes: {
|
|
119
|
+
spellcheck: "false",
|
|
120
|
+
maxlength: "6"
|
|
121
|
+
},
|
|
122
|
+
value: payload.referenceNumber
|
|
123
|
+
}) }}
|
|
124
|
+
|
|
125
|
+
{{ govukDateInput({
|
|
126
|
+
id: "date-of-birth",
|
|
127
|
+
namePrefix: "date-of-birth",
|
|
128
|
+
items: dateInputItems,
|
|
129
|
+
fieldset: {
|
|
130
|
+
legend: {
|
|
131
|
+
text: mssgs.cancel_rp_identify_dob,
|
|
132
|
+
isPageHeading: false,
|
|
133
|
+
classes: "govuk-!-font-weight-bold govuk-label"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
errorMessage: ({
|
|
137
|
+
text: errorMap[data.error.errorKey][data.error.errorValue].text }
|
|
138
|
+
if data.error and data.error.errorKey in [
|
|
139
|
+
'full-date',
|
|
140
|
+
'day-and-month',
|
|
141
|
+
'day-and-year',
|
|
142
|
+
'month-and-year',
|
|
143
|
+
'day',
|
|
144
|
+
'month',
|
|
145
|
+
'year',
|
|
146
|
+
'non-numeric',
|
|
147
|
+
'invalid-date',
|
|
148
|
+
'date-range'
|
|
149
|
+
]
|
|
150
|
+
),
|
|
151
|
+
hint: {
|
|
152
|
+
text: mssgs.cancel_rp_identify_dob_hint
|
|
153
|
+
}
|
|
154
|
+
}) }}
|
|
155
|
+
|
|
156
|
+
{{ govukInput({
|
|
157
|
+
label: {
|
|
158
|
+
text: mssgs.cancel_rp_identify_postcode,
|
|
159
|
+
classes: "govuk-!-font-weight-bold"
|
|
160
|
+
},
|
|
161
|
+
id: "postcode",
|
|
162
|
+
name: "postcode",
|
|
163
|
+
hint: { text: mssgs.cancel_rp_identify_postcode_hint },
|
|
164
|
+
value: payload['postcode'],
|
|
165
|
+
autocomplete: 'postal-code',
|
|
166
|
+
classes: "govuk-input--width-10",
|
|
167
|
+
attributes: { maxlength: 10 },
|
|
168
|
+
errorMessage: { text: mssgs.cancel_rp_identify_postcode_error } if error['postcode']
|
|
169
|
+
}) }}
|
|
170
|
+
|
|
171
|
+
{% endcall %}
|
|
172
|
+
|
|
13
173
|
{{ govukButton({
|
|
14
174
|
attributes: { id: 'continue' },
|
|
15
175
|
preventDoubleClick: true,
|
|
@@ -21,4 +181,11 @@
|
|
|
21
181
|
</form>
|
|
22
182
|
</div>
|
|
23
183
|
</div>
|
|
24
|
-
{% endblock %}
|
|
184
|
+
{% endblock %}
|
|
185
|
+
|
|
186
|
+
{% block bodyEnd %}
|
|
187
|
+
<script type="module" nonce="{{ nonce }}">
|
|
188
|
+
import { initAll } from '/public/javascript/govuk-frontend-min.js'
|
|
189
|
+
initAll()
|
|
190
|
+
</script>
|
|
191
|
+
{% endblock %}
|
|
@@ -1,13 +1,58 @@
|
|
|
1
|
-
import pageRoute from '../../../../routes/page-route.js'
|
|
2
1
|
import { CANCEL_RP_AUTHENTICATE, CANCEL_RP_IDENTIFY } from '../../../../uri.js'
|
|
2
|
+
import pageRoute from '../../../../routes/page-route.js'
|
|
3
|
+
import Joi from 'joi'
|
|
4
|
+
import { validation } from '@defra-fish/business-rules-lib'
|
|
3
5
|
import { addLanguageCodeToUri } from '../../../../processors/uri-helper.js'
|
|
6
|
+
import GetDataRedirect from '../../../../handlers/get-data-redirect.js'
|
|
7
|
+
import { dateOfBirthValidator, getDateErrorFlags } from '../../../../schema/validators/validators.js'
|
|
8
|
+
|
|
9
|
+
export const getData = async request => {
|
|
10
|
+
const permission = await request.cache().helpers.transaction.getCurrentPermission()
|
|
11
|
+
const page = await request.cache().helpers.page.getCurrentPermission(CANCEL_RP_IDENTIFY.page)
|
|
12
|
+
|
|
13
|
+
if (permission.referenceNumber) {
|
|
14
|
+
const validatePermissionNumber = validation.permission
|
|
15
|
+
.permissionNumberUniqueComponentValidator(Joi)
|
|
16
|
+
.validate(permission.referenceNumber)
|
|
17
|
+
if (validatePermissionNumber.error) {
|
|
18
|
+
await request.cache().helpers.transaction.setCurrentPermission({ referenceNumber: null })
|
|
19
|
+
throw new GetDataRedirect(addLanguageCodeToUri(request, CANCEL_RP_IDENTIFY.uri))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pageData = {
|
|
24
|
+
referenceNumber: permission.referenceNumber,
|
|
25
|
+
...getDateErrorFlags(page?.error)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (page?.error) {
|
|
29
|
+
const [errorKey] = Object.keys(page.error)
|
|
30
|
+
const errorValue = page.error[errorKey]
|
|
31
|
+
pageData.error = { errorKey, errorValue }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return pageData
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const validator = payload => {
|
|
38
|
+
dateOfBirthValidator(payload)
|
|
39
|
+
|
|
40
|
+
Joi.assert(
|
|
41
|
+
{
|
|
42
|
+
postcode: payload.postcode,
|
|
43
|
+
referenceNumber: payload.referenceNumber
|
|
44
|
+
},
|
|
45
|
+
Joi.object({
|
|
46
|
+
referenceNumber: validation.permission.permissionNumberUniqueComponentValidator(Joi),
|
|
47
|
+
postcode: validation.contact.createOverseasPostcodeValidator(Joi)
|
|
48
|
+
}).options({ abortEarly: false })
|
|
49
|
+
)
|
|
50
|
+
}
|
|
4
51
|
|
|
5
52
|
export default pageRoute(
|
|
6
53
|
CANCEL_RP_IDENTIFY.page,
|
|
7
54
|
CANCEL_RP_IDENTIFY.uri,
|
|
8
|
-
|
|
9
|
-
request =>
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
() => {}
|
|
55
|
+
validator,
|
|
56
|
+
request => addLanguageCodeToUri(request, CANCEL_RP_AUTHENTICATE.uri),
|
|
57
|
+
getData
|
|
13
58
|
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { setupCancelRecurringPaymentCacheFromAuthResult } from '../recurring-payments-write-cache.js'
|
|
2
|
+
|
|
3
|
+
describe('setUpCancelRecurringPaymentCacheFromAuthenticationResult', () => {
|
|
4
|
+
const getSampleAuthResult = overrides => {
|
|
5
|
+
const defaults = {
|
|
6
|
+
referenceNumber: '23270624-2WC3FSD-ABNCY4',
|
|
7
|
+
endDate: '2024-12-31',
|
|
8
|
+
licensee: { firstName: 'Brenin', lastName: 'Pysgotwr' },
|
|
9
|
+
permit: { description: 'Coarse 6 month 15 Rod Licence (Half)' },
|
|
10
|
+
recurringPayment: { lastDigitsCardNumbers: '5678' }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
permission: {
|
|
15
|
+
referenceNumber: overrides.referenceNumber || defaults.referenceNumber,
|
|
16
|
+
endDate: overrides.endDate || defaults.endDate,
|
|
17
|
+
licensee: {
|
|
18
|
+
...defaults.licensee,
|
|
19
|
+
...(overrides.licensee ? overrides.licensee : {})
|
|
20
|
+
},
|
|
21
|
+
permit: {
|
|
22
|
+
...defaults.permit,
|
|
23
|
+
...(overrides.permit ? overrides.permit : {})
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
recurringPayment: {
|
|
27
|
+
...defaults.recurringPayment,
|
|
28
|
+
...(overrides.recurringPayment ? overrides.recurringPayment : {})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getSampleRequest = setCurrentPermission => ({
|
|
34
|
+
cache: () => ({
|
|
35
|
+
helpers: {
|
|
36
|
+
transaction: {
|
|
37
|
+
setCurrentPermission
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('permission caching', () => {
|
|
44
|
+
it.each([
|
|
45
|
+
['referenceNumber', '23270624-2WC3FSD-ABNCY4'],
|
|
46
|
+
['endDate', '2024-12-31'],
|
|
47
|
+
['licensee', { firstName: 'John', lastName: 'Bull' }],
|
|
48
|
+
['permit', { description: 'Coarse 12 month 2 Rod Licence (Full)' }]
|
|
49
|
+
])("Adds permission %s, value '%s', to transaction cache", async (fieldName, fieldValue) => {
|
|
50
|
+
const setCurrentPermission = jest.fn()
|
|
51
|
+
const mockRequest = getSampleRequest(setCurrentPermission)
|
|
52
|
+
const authResult = getSampleAuthResult({ [fieldName]: fieldValue })
|
|
53
|
+
|
|
54
|
+
await setupCancelRecurringPaymentCacheFromAuthResult(mockRequest, authResult)
|
|
55
|
+
|
|
56
|
+
expect(setCurrentPermission).toHaveBeenCalledWith(
|
|
57
|
+
expect.objectContaining({
|
|
58
|
+
permission: expect.objectContaining({
|
|
59
|
+
[fieldName]: fieldValue
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it.each([
|
|
66
|
+
['licensee', { anotherProperty: 'Should not be there' }],
|
|
67
|
+
['permit', { altProp: 'Should not be here' }]
|
|
68
|
+
])('Omits extraneous properties from permission %s', async (fieldName, fieldValue) => {
|
|
69
|
+
const setCurrentPermission = jest.fn()
|
|
70
|
+
const mockRequest = getSampleRequest(setCurrentPermission)
|
|
71
|
+
|
|
72
|
+
const authResult = getSampleAuthResult({ [fieldName]: fieldValue })
|
|
73
|
+
|
|
74
|
+
await setupCancelRecurringPaymentCacheFromAuthResult(mockRequest, authResult)
|
|
75
|
+
|
|
76
|
+
expect(setCurrentPermission).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
permission: expect.not.objectContaining({
|
|
79
|
+
[fieldName]: fieldValue
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('recurring payment caching', () => {
|
|
87
|
+
it('adds recurring payment payment card number last digits to transaction cache', async () => {
|
|
88
|
+
const setCurrentPermission = jest.fn()
|
|
89
|
+
const mockRequest = getSampleRequest(setCurrentPermission)
|
|
90
|
+
|
|
91
|
+
const authResult = getSampleAuthResult({ recurringPayment: { lastDigitsCardNumbers: '1234' } })
|
|
92
|
+
|
|
93
|
+
await setupCancelRecurringPaymentCacheFromAuthResult(mockRequest, authResult)
|
|
94
|
+
|
|
95
|
+
expect(setCurrentPermission).toHaveBeenCalledWith(
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
recurringPayment: expect.objectContaining({
|
|
98
|
+
lastDigitsCardNumbers: '1234'
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('omits extraneous properties from recurring payment', async () => {
|
|
105
|
+
const setCurrentPermission = jest.fn()
|
|
106
|
+
const mockRequest = getSampleRequest(setCurrentPermission)
|
|
107
|
+
|
|
108
|
+
const authResult = getSampleAuthResult({ recurringPayment: { someOtherProp: 'Should not be here' } })
|
|
109
|
+
|
|
110
|
+
await setupCancelRecurringPaymentCacheFromAuthResult(mockRequest, authResult)
|
|
111
|
+
|
|
112
|
+
expect(setCurrentPermission).toHaveBeenCalledWith(
|
|
113
|
+
expect.objectContaining({
|
|
114
|
+
recurringPayment: expect.not.objectContaining({
|
|
115
|
+
someOtherProp: 'Should not be here'
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const setupCancelRecurringPaymentCacheFromAuthResult = async (request, authenticationResult) => {
|
|
2
|
+
const { permission, recurringPayment } = authenticationResult
|
|
3
|
+
const { referenceNumber, endDate, licensee, permit } = permission
|
|
4
|
+
|
|
5
|
+
await request.cache().helpers.transaction.setCurrentPermission({
|
|
6
|
+
permission: {
|
|
7
|
+
referenceNumber,
|
|
8
|
+
endDate,
|
|
9
|
+
licensee: { firstName: licensee.firstName, lastName: licensee.lastName },
|
|
10
|
+
permit: { description: permit.description }
|
|
11
|
+
},
|
|
12
|
+
recurringPayment: {
|
|
13
|
+
lastDigitsCardNumbers: recurringPayment.lastDigitsCardNumbers
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -33,7 +33,7 @@ import authenticationHandler from '../handlers/authentication-handler.js'
|
|
|
33
33
|
import { addLanguageCodeToUri } from '../processors/uri-helper.js'
|
|
34
34
|
import analytics, { checkAnalyticsCookiesPage } from '../handlers/analytics-handler.js'
|
|
35
35
|
import { welshEnabledAndApplied } from '../processors/page-language-helper.js'
|
|
36
|
-
import cancelRPAuthenticationhander from '../handlers/cancel-
|
|
36
|
+
import cancelRPAuthenticationhander from '../handlers/cancel-recurring-payment-authentication-handler.js'
|
|
37
37
|
|
|
38
38
|
const gtmContainerIdOrNull = () => process.env.GTM_CONTAINER_ID || false
|
|
39
39
|
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import cancelRpAuthenticationHandler from '../cancel-rp-authentication-handler'
|
|
2
|
-
import { CONTROLLER } from '../../uri.js'
|
|
3
|
-
|
|
4
|
-
jest.mock('../../uri.js', () => ({
|
|
5
|
-
CONTROLLER: { uri: Symbol('controller-uri') }
|
|
6
|
-
}))
|
|
7
|
-
|
|
8
|
-
const getSampleResponseToolkit = () => ({
|
|
9
|
-
redirectWithLanguageCode: jest.fn()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
describe('Cancel RP Authentication Handler', () => {
|
|
13
|
-
it('redirects with language code to CONTROLLER.uri', () => {
|
|
14
|
-
const sampleResponseToolkit = getSampleResponseToolkit()
|
|
15
|
-
cancelRpAuthenticationHandler(undefined, sampleResponseToolkit)
|
|
16
|
-
expect(sampleResponseToolkit.redirectWithLanguageCode).toHaveBeenCalledWith(CONTROLLER.uri)
|
|
17
|
-
})
|
|
18
|
-
})
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import pageRoute from '../../../../../routes/page-route.js'
|
|
2
|
-
import { CANCEL_RP_AUTHENTICATE, CANCEL_RP_IDENTIFY } from '../../../../../uri.js'
|
|
3
|
-
import { addLanguageCodeToUri } from '../../../../../processors/uri-helper.js'
|
|
4
|
-
|
|
5
|
-
require('../route.js')
|
|
6
|
-
// eslint-disable-next-line no-unused-vars
|
|
7
|
-
const [[_v, _p, validator, completion, getData]] = pageRoute.mock.calls
|
|
8
|
-
|
|
9
|
-
jest.mock('../../../../../routes/page-route.js')
|
|
10
|
-
jest.mock('../../../../../uri.js', () => ({
|
|
11
|
-
...jest.requireActual('../../../../../uri.js'),
|
|
12
|
-
CANCEL_RP_IDENTIFY: { page: Symbol('cancel-rp-identify'), uri: Symbol('cancel-rp-identify-uri') },
|
|
13
|
-
CANCEL_RP_AUTHENTICATE: { uri: Symbol('cancel-rp-authenticate-uri') }
|
|
14
|
-
}))
|
|
15
|
-
jest.mock('../../../../../processors/uri-helper.js')
|
|
16
|
-
|
|
17
|
-
describe('pageRoute receives expected arguments', () => {
|
|
18
|
-
it('passes CANCEL_RP_IDENTIFY.page as the view name', () => {
|
|
19
|
-
jest.isolateModules(() => {
|
|
20
|
-
require('../route.js')
|
|
21
|
-
expect(pageRoute).toHaveBeenCalledWith(
|
|
22
|
-
CANCEL_RP_IDENTIFY.page,
|
|
23
|
-
expect.anything(),
|
|
24
|
-
expect.anything(),
|
|
25
|
-
expect.anything(),
|
|
26
|
-
expect.anything()
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('passes CANCEL_RP_IDENTIFY.uri as the path', () => {
|
|
32
|
-
jest.isolateModules(() => {
|
|
33
|
-
require('../route.js')
|
|
34
|
-
expect(pageRoute).toHaveBeenCalledWith(
|
|
35
|
-
expect.anything(),
|
|
36
|
-
CANCEL_RP_IDENTIFY.uri,
|
|
37
|
-
expect.anything(),
|
|
38
|
-
expect.anything(),
|
|
39
|
-
expect.anything()
|
|
40
|
-
)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('passes a function as the validator', () => {
|
|
45
|
-
jest.isolateModules(() => {
|
|
46
|
-
require('../route.js')
|
|
47
|
-
expect(pageRoute).toHaveBeenCalledWith(
|
|
48
|
-
expect.anything(),
|
|
49
|
-
expect.anything(),
|
|
50
|
-
expect.any(Function),
|
|
51
|
-
expect.anything(),
|
|
52
|
-
expect.anything()
|
|
53
|
-
)
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('passes a function to generate redirect location on completion', () => {
|
|
58
|
-
jest.isolateModules(() => {
|
|
59
|
-
require('../route.js')
|
|
60
|
-
expect(pageRoute).toHaveBeenCalledWith(
|
|
61
|
-
expect.anything(),
|
|
62
|
-
expect.anything(),
|
|
63
|
-
expect.anything(),
|
|
64
|
-
expect.any(Function),
|
|
65
|
-
expect.anything()
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('passes a function to get the page data', () => {
|
|
71
|
-
jest.isolateModules(() => {
|
|
72
|
-
require('../route.js')
|
|
73
|
-
expect(pageRoute).toHaveBeenCalledWith(
|
|
74
|
-
expect.anything(),
|
|
75
|
-
expect.anything(),
|
|
76
|
-
expect.anything(),
|
|
77
|
-
expect.anything(),
|
|
78
|
-
expect.any(Function)
|
|
79
|
-
)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('completion function', () => {
|
|
85
|
-
beforeEach(jest.clearAllMocks)
|
|
86
|
-
|
|
87
|
-
it('calls addLanguageCodeToUri with request', () => {
|
|
88
|
-
const sampleRequest = Symbol('sample request')
|
|
89
|
-
completion(sampleRequest)
|
|
90
|
-
expect(addLanguageCodeToUri).toHaveBeenCalledWith(sampleRequest, expect.anything())
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('calls addLanguageCodeToUri with CANCEL_RP_AUTHENTICATE uri', () => {
|
|
94
|
-
completion({})
|
|
95
|
-
expect(addLanguageCodeToUri).toHaveBeenCalledWith(expect.anything(), CANCEL_RP_AUTHENTICATE.uri)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('returns the value of addLanguageCodeToUri', () => {
|
|
99
|
-
const expectedCompletionRedirect = Symbol('expected-completion-redirect')
|
|
100
|
-
addLanguageCodeToUri.mockReturnValueOnce(expectedCompletionRedirect)
|
|
101
|
-
|
|
102
|
-
const completionRedirect = completion({})
|
|
103
|
-
|
|
104
|
-
expect(completionRedirect).toBe(expectedCompletionRedirect)
|
|
105
|
-
})
|
|
106
|
-
})
|