@defra-fish/sales-api-service 1.47.0-rc.2 → 1.47.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/sales-api-service",
3
- "version": "1.47.0-rc.2",
3
+ "version": "1.47.0-rc.4",
4
4
  "description": "Rod Licensing Sales API",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,9 +35,9 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/business-rules-lib": "1.47.0-rc.2",
39
- "@defra-fish/connectors-lib": "1.47.0-rc.2",
40
- "@defra-fish/dynamics-lib": "1.47.0-rc.2",
38
+ "@defra-fish/business-rules-lib": "1.47.0-rc.4",
39
+ "@defra-fish/connectors-lib": "1.47.0-rc.4",
40
+ "@defra-fish/dynamics-lib": "1.47.0-rc.4",
41
41
  "@hapi/boom": "^9.1.2",
42
42
  "@hapi/hapi": "^20.1.3",
43
43
  "@hapi/inert": "^6.0.3",
@@ -52,5 +52,5 @@
52
52
  "moment-timezone": "^0.5.34",
53
53
  "uuid": "^8.3.2"
54
54
  },
55
- "gitHead": "cd0cd62c056ea84175c092048ab5ad760fcd2cc4"
55
+ "gitHead": "a9a1bc2f8c9ecdaa8740a88eadb59df39c60193e"
56
56
  }
@@ -0,0 +1,75 @@
1
+ import { permissionRenewalDataRequestParamsSchema, permissionRenewalDataResponseSchema } from '../renewals.schema.js'
2
+
3
+ describe('permissionRenewalDataRequestParamsSchema', () => {
4
+ it('validates successfully when referenceNumber is valid', async () => {
5
+ const result = await permissionRenewalDataRequestParamsSchema.validateAsync({ referenceNumber: '00310321-2DC3FAS-F4A315' })
6
+ expect(result).toBeInstanceOf(Object)
7
+ })
8
+
9
+ it('does not validate when referenceNumber is invalid', async () => {
10
+ await expect(permissionRenewalDataRequestParamsSchema.validateAsync({ foo: 'bar' })).rejects.toThrow('"referenceNumber" is required')
11
+ })
12
+ })
13
+
14
+ describe('permissionRenewalDataResponseSchema', () => {
15
+ const mockResponseData = () => ({
16
+ permission: {
17
+ id: 'dbd3a8a7-cad7-4567-9e68-c787899f5093',
18
+ referenceNumber: '00310321-2DC3FAS-F4A315',
19
+ issueDate: new Date().toISOString(),
20
+ startDate: new Date().toISOString(),
21
+ endDate: new Date().toISOString(),
22
+ stagingId: 'dbd3a8a7-cad7-4567-9e68-c787899f5093',
23
+ dataSource: {
24
+ id: 910400000,
25
+ label: 'Example Label',
26
+ description: 'Example Description'
27
+ },
28
+ licensee: {
29
+ firstName: 'Sally',
30
+ lastName: 'Salmon',
31
+ birthDate: '1990-01-01'
32
+ },
33
+ concessions: [],
34
+ permit: {
35
+ id: 'dbd3a8a7-cad7-4567-9e68-c787899f5093',
36
+ description: 'Coarse 12 month 3 Rod Licence',
37
+ availableFrom: new Date().toISOString(),
38
+ availableTo: new Date().toISOString(),
39
+ isForFulfilment: true,
40
+ isCounterSales: false,
41
+ isRecurringPaymentSupported: true
42
+ },
43
+ permitId: 'dbd3a8a7-cad7-4567-9e68-c787899f5093'
44
+ }
45
+ })
46
+
47
+ it('validates successfully when the response data is valid', async () => {
48
+ const result = await permissionRenewalDataResponseSchema.validateAsync(mockResponseData())
49
+ expect(result).toBeInstanceOf(Object)
50
+ })
51
+
52
+ it('does not validate when finalisedPermissionSchemaContent is invalid', async () => {
53
+ const response = mockResponseData()
54
+ response.issueDate = 'foo'
55
+ await expect(permissionRenewalDataRequestParamsSchema.validateAsync(response)).rejects.toThrow('"referenceNumber" is required')
56
+ })
57
+
58
+ it('does not validate when contactResponseSchema is invalid', async () => {
59
+ const response = mockResponseData()
60
+ response.licensee = {}
61
+ await expect(permissionRenewalDataRequestParamsSchema.validateAsync(response)).rejects.toThrow('"referenceNumber" is required')
62
+ })
63
+
64
+ it('does not validate when concessionProofSchema is invalid', async () => {
65
+ const response = mockResponseData()
66
+ response.concessions = {}
67
+ await expect(permissionRenewalDataRequestParamsSchema.validateAsync(response)).rejects.toThrow('"referenceNumber" is required')
68
+ })
69
+
70
+ it('does not validate when permitSchema is invalid', async () => {
71
+ const response = mockResponseData()
72
+ response.permit = {}
73
+ await expect(permissionRenewalDataRequestParamsSchema.validateAsync(response)).rejects.toThrow('"referenceNumber" is required')
74
+ })
75
+ })
@@ -0,0 +1,21 @@
1
+ import Joi from 'joi'
2
+ import { validation } from '@defra-fish/business-rules-lib'
3
+ import { concessionProofSchema } from './concession-proof.schema.js'
4
+ import { permitSchema } from './permit.schema.js'
5
+ import { contactResponseSchema } from './contact.schema.js'
6
+ import { finalisedPermissionSchemaContent } from './permission.schema.js'
7
+ import { v4 as uuid } from 'uuid'
8
+
9
+ export const permissionRenewalDataRequestParamsSchema = Joi.object({
10
+ referenceNumber: validation.permission.createPermissionNumberValidator(Joi)
11
+ }).label('permission-renewal-data-request-params')
12
+
13
+ export const permissionRenewalDataResponseSchema = Joi.object({
14
+ permission: {
15
+ ...finalisedPermissionSchemaContent,
16
+ licensee: contactResponseSchema,
17
+ concessions: concessionProofSchema,
18
+ permit: permitSchema,
19
+ permitId: Joi.string().guid().required().example(uuid())
20
+ }
21
+ }).label('permission-renewal-data-response')
@@ -0,0 +1,455 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`permissionRenewalData metadata should have expected description 1`] = `"Prepare data for renewing a permission based on the existing data"`;
4
+
5
+ exports[`permissionRenewalData metadata should have expected notes 1`] = `
6
+ "
7
+ Prepare data for renewing a permission based on the existing data
8
+ "
9
+ `;
10
+
11
+ exports[`permissionRenewalData metadata should have expected tags 1`] = `
12
+ Array [
13
+ "api",
14
+ "renewals",
15
+ ]
16
+ `;
17
+
18
+ exports[`permissionRenewalData metadata should have expected validate 1`] = `
19
+ Object {
20
+ "params": Object {
21
+ "$_root": Object {
22
+ "ValidationError": [Function],
23
+ "_types": Set {
24
+ "alternatives",
25
+ "any",
26
+ "array",
27
+ "boolean",
28
+ "date",
29
+ "function",
30
+ "link",
31
+ "number",
32
+ "object",
33
+ "string",
34
+ "symbol",
35
+ "binary",
36
+ },
37
+ "allow": [Function],
38
+ "alt": [Function],
39
+ "alternatives": [Function],
40
+ "any": [Function],
41
+ "array": [Function],
42
+ "assert": [Function],
43
+ "attempt": [Function],
44
+ "binary": [Function],
45
+ "bool": [Function],
46
+ "boolean": [Function],
47
+ "build": [Function],
48
+ "cache": Object {
49
+ "provision": [Function],
50
+ },
51
+ "checkPreferences": [Function],
52
+ "compile": [Function],
53
+ "custom": [Function],
54
+ "date": [Function],
55
+ "defaults": [Function],
56
+ "disallow": [Function],
57
+ "equal": [Function],
58
+ "exist": [Function],
59
+ "expression": [Function],
60
+ "extend": [Function],
61
+ "forbidden": [Function],
62
+ "func": [Function],
63
+ "function": [Function],
64
+ "in": [Function],
65
+ "invalid": [Function],
66
+ "isError": [Function],
67
+ "isExpression": [Function],
68
+ "isRef": [Function],
69
+ "isSchema": [Function],
70
+ "link": [Function],
71
+ "not": [Function],
72
+ "number": [Function],
73
+ "object": [Function],
74
+ "only": [Function],
75
+ "optional": [Function],
76
+ "options": [Function],
77
+ "override": Symbol(override),
78
+ "preferences": [Function],
79
+ "prefs": [Function],
80
+ "ref": [Function],
81
+ "required": [Function],
82
+ "string": [Function],
83
+ "strip": [Function],
84
+ "symbol": [Function],
85
+ "trace": [Function],
86
+ "types": [Function],
87
+ "untrace": [Function],
88
+ "valid": [Function],
89
+ "version": "17.6.0",
90
+ "when": [Function],
91
+ "x": [Function],
92
+ Symbol(@hapi/lab/coverage/initialize): [Function],
93
+ },
94
+ "$_super": Object {
95
+ "default": [Function],
96
+ },
97
+ "$_temp": Object {
98
+ "ruleset": false,
99
+ "whens": Object {},
100
+ },
101
+ "$_terms": Object {
102
+ "alterations": null,
103
+ "dependencies": null,
104
+ "examples": null,
105
+ "externals": null,
106
+ "keys": [
107
+ Object {
108
+ "key": "referenceNumber",
109
+ "schema": Object {
110
+ "$_root": Object {
111
+ "ValidationError": [Function],
112
+ "_types": Set {
113
+ "alternatives",
114
+ "any",
115
+ "array",
116
+ "boolean",
117
+ "date",
118
+ "function",
119
+ "link",
120
+ "number",
121
+ "object",
122
+ "string",
123
+ "symbol",
124
+ "binary",
125
+ },
126
+ "allow": [Function],
127
+ "alt": [Function],
128
+ "alternatives": [Function],
129
+ "any": [Function],
130
+ "array": [Function],
131
+ "assert": [Function],
132
+ "attempt": [Function],
133
+ "binary": [Function],
134
+ "bool": [Function],
135
+ "boolean": [Function],
136
+ "build": [Function],
137
+ "cache": Object {
138
+ "provision": [Function],
139
+ },
140
+ "checkPreferences": [Function],
141
+ "compile": [Function],
142
+ "custom": [Function],
143
+ "date": [Function],
144
+ "defaults": [Function],
145
+ "disallow": [Function],
146
+ "equal": [Function],
147
+ "exist": [Function],
148
+ "expression": [Function],
149
+ "extend": [Function],
150
+ "forbidden": [Function],
151
+ "func": [Function],
152
+ "function": [Function],
153
+ "in": [Function],
154
+ "invalid": [Function],
155
+ "isError": [Function],
156
+ "isExpression": [Function],
157
+ "isRef": [Function],
158
+ "isSchema": [Function],
159
+ "link": [Function],
160
+ "not": [Function],
161
+ "number": [Function],
162
+ "object": [Function],
163
+ "only": [Function],
164
+ "optional": [Function],
165
+ "options": [Function],
166
+ "override": Symbol(override),
167
+ "preferences": [Function],
168
+ "prefs": [Function],
169
+ "ref": [Function],
170
+ "required": [Function],
171
+ "string": [Function],
172
+ "strip": [Function],
173
+ "symbol": [Function],
174
+ "trace": [Function],
175
+ "types": [Function],
176
+ "untrace": [Function],
177
+ "valid": [Function],
178
+ "version": "17.6.0",
179
+ "when": [Function],
180
+ "x": [Function],
181
+ Symbol(@hapi/lab/coverage/initialize): [Function],
182
+ },
183
+ "$_super": Object {},
184
+ "$_temp": Object {
185
+ "ruleset": false,
186
+ "whens": Object {},
187
+ },
188
+ "$_terms": Object {
189
+ "alterations": null,
190
+ "examples": Array [
191
+ "17030621-3WC3FFT-B6HLG9",
192
+ ],
193
+ "externals": null,
194
+ "metas": Array [],
195
+ "notes": Array [],
196
+ "replacements": null,
197
+ "shared": null,
198
+ "tags": Array [],
199
+ "whens": null,
200
+ },
201
+ "_cache": null,
202
+ "_flags": Object {
203
+ "description": "The permission reference number",
204
+ "presence": "required",
205
+ },
206
+ "_ids": Object {
207
+ "_byId": Map {},
208
+ "_byKey": Map {},
209
+ "_schemaChain": false,
210
+ },
211
+ "_invalids": null,
212
+ "_preferences": null,
213
+ "_refs": Object {
214
+ "refs": Array [],
215
+ },
216
+ "_rules": Array [
217
+ Object {
218
+ "_resolve": Array [],
219
+ "args": Object {
220
+ "enabled": true,
221
+ },
222
+ "method": "trim",
223
+ "name": "trim",
224
+ },
225
+ Object {
226
+ "_resolve": Array [],
227
+ "args": Object {
228
+ "direction": "upper",
229
+ },
230
+ "method": "case",
231
+ "name": "case",
232
+ },
233
+ Object {
234
+ "_resolve": Array [],
235
+ "args": Object {
236
+ "options": Object {},
237
+ "regex": /\\^\\\\d\\{8\\}-\\\\d\\[A-Z\\]\\{2\\}\\\\d\\[A-Z\\]\\{3\\}-\\[A-Z0-9\\]\\{6\\}\\$/,
238
+ },
239
+ "errorCode": "string.pattern.base",
240
+ "method": "pattern",
241
+ "name": "pattern",
242
+ },
243
+ ],
244
+ "_singleRules": Map {
245
+ "trim" => Object {
246
+ "_resolve": Array [],
247
+ "args": Object {
248
+ "enabled": true,
249
+ },
250
+ "method": "trim",
251
+ "name": "trim",
252
+ },
253
+ "case" => Object {
254
+ "_resolve": Array [],
255
+ "args": Object {
256
+ "direction": "upper",
257
+ },
258
+ "method": "case",
259
+ "name": "case",
260
+ },
261
+ },
262
+ "_valids": null,
263
+ "type": "string",
264
+ },
265
+ },
266
+ ],
267
+ "metas": Array [],
268
+ "notes": Array [],
269
+ "patterns": null,
270
+ "renames": null,
271
+ "shared": null,
272
+ "tags": Array [],
273
+ "whens": null,
274
+ },
275
+ "_cache": null,
276
+ "_flags": Object {
277
+ "label": "permission-renewal-data-request-params",
278
+ },
279
+ "_ids": Object {
280
+ "_byId": Map {},
281
+ "_byKey": Map {
282
+ "referenceNumber" => Object {
283
+ "id": "referenceNumber",
284
+ "schema": Object {
285
+ "$_root": Object {
286
+ "ValidationError": [Function],
287
+ "_types": Set {
288
+ "alternatives",
289
+ "any",
290
+ "array",
291
+ "boolean",
292
+ "date",
293
+ "function",
294
+ "link",
295
+ "number",
296
+ "object",
297
+ "string",
298
+ "symbol",
299
+ "binary",
300
+ },
301
+ "allow": [Function],
302
+ "alt": [Function],
303
+ "alternatives": [Function],
304
+ "any": [Function],
305
+ "array": [Function],
306
+ "assert": [Function],
307
+ "attempt": [Function],
308
+ "binary": [Function],
309
+ "bool": [Function],
310
+ "boolean": [Function],
311
+ "build": [Function],
312
+ "cache": Object {
313
+ "provision": [Function],
314
+ },
315
+ "checkPreferences": [Function],
316
+ "compile": [Function],
317
+ "custom": [Function],
318
+ "date": [Function],
319
+ "defaults": [Function],
320
+ "disallow": [Function],
321
+ "equal": [Function],
322
+ "exist": [Function],
323
+ "expression": [Function],
324
+ "extend": [Function],
325
+ "forbidden": [Function],
326
+ "func": [Function],
327
+ "function": [Function],
328
+ "in": [Function],
329
+ "invalid": [Function],
330
+ "isError": [Function],
331
+ "isExpression": [Function],
332
+ "isRef": [Function],
333
+ "isSchema": [Function],
334
+ "link": [Function],
335
+ "not": [Function],
336
+ "number": [Function],
337
+ "object": [Function],
338
+ "only": [Function],
339
+ "optional": [Function],
340
+ "options": [Function],
341
+ "override": Symbol(override),
342
+ "preferences": [Function],
343
+ "prefs": [Function],
344
+ "ref": [Function],
345
+ "required": [Function],
346
+ "string": [Function],
347
+ "strip": [Function],
348
+ "symbol": [Function],
349
+ "trace": [Function],
350
+ "types": [Function],
351
+ "untrace": [Function],
352
+ "valid": [Function],
353
+ "version": "17.6.0",
354
+ "when": [Function],
355
+ "x": [Function],
356
+ Symbol(@hapi/lab/coverage/initialize): [Function],
357
+ },
358
+ "$_super": Object {},
359
+ "$_temp": Object {
360
+ "ruleset": false,
361
+ "whens": Object {},
362
+ },
363
+ "$_terms": Object {
364
+ "alterations": null,
365
+ "examples": Array [
366
+ "17030621-3WC3FFT-B6HLG9",
367
+ ],
368
+ "externals": null,
369
+ "metas": Array [],
370
+ "notes": Array [],
371
+ "replacements": null,
372
+ "shared": null,
373
+ "tags": Array [],
374
+ "whens": null,
375
+ },
376
+ "_cache": null,
377
+ "_flags": Object {
378
+ "description": "The permission reference number",
379
+ "presence": "required",
380
+ },
381
+ "_ids": Object {
382
+ "_byId": Map {},
383
+ "_byKey": Map {},
384
+ "_schemaChain": false,
385
+ },
386
+ "_invalids": null,
387
+ "_preferences": null,
388
+ "_refs": Object {
389
+ "refs": Array [],
390
+ },
391
+ "_rules": Array [
392
+ Object {
393
+ "_resolve": Array [],
394
+ "args": Object {
395
+ "enabled": true,
396
+ },
397
+ "method": "trim",
398
+ "name": "trim",
399
+ },
400
+ Object {
401
+ "_resolve": Array [],
402
+ "args": Object {
403
+ "direction": "upper",
404
+ },
405
+ "method": "case",
406
+ "name": "case",
407
+ },
408
+ Object {
409
+ "_resolve": Array [],
410
+ "args": Object {
411
+ "options": Object {},
412
+ "regex": /\\^\\\\d\\{8\\}-\\\\d\\[A-Z\\]\\{2\\}\\\\d\\[A-Z\\]\\{3\\}-\\[A-Z0-9\\]\\{6\\}\\$/,
413
+ },
414
+ "errorCode": "string.pattern.base",
415
+ "method": "pattern",
416
+ "name": "pattern",
417
+ },
418
+ ],
419
+ "_singleRules": Map {
420
+ "trim" => Object {
421
+ "_resolve": Array [],
422
+ "args": Object {
423
+ "enabled": true,
424
+ },
425
+ "method": "trim",
426
+ "name": "trim",
427
+ },
428
+ "case" => Object {
429
+ "_resolve": Array [],
430
+ "args": Object {
431
+ "direction": "upper",
432
+ },
433
+ "method": "case",
434
+ "name": "case",
435
+ },
436
+ },
437
+ "_valids": null,
438
+ "type": "string",
439
+ },
440
+ },
441
+ },
442
+ "_schemaChain": false,
443
+ },
444
+ "_invalids": null,
445
+ "_preferences": null,
446
+ "_refs": Object {
447
+ "refs": Array [],
448
+ },
449
+ "_rules": Array [],
450
+ "_singleRules": Map {},
451
+ "_valids": null,
452
+ "type": "object",
453
+ },
454
+ }
455
+ `;
@@ -0,0 +1,117 @@
1
+ import Boom from '@hapi/boom'
2
+ import permissionRenewalData from '../renewals.js'
3
+ import { preparePermissionDataForRenewal } from '../../../services/renewals/renewals.service.js'
4
+ import { executeQuery, permissionForFullReferenceNumber } from '@defra-fish/dynamics-lib'
5
+ import {
6
+ MOCK_EXISTING_PERMISSION_ENTITY,
7
+ MOCK_EXISTING_CONTACT_ENTITY,
8
+ MOCK_1DAY_SENIOR_PERMIT_ENTITY,
9
+ MOCK_CONCESSION_PROOF_ENTITY,
10
+ MOCK_CONCESSION
11
+ } from '../../../__mocks__/test-data.js'
12
+
13
+ jest.mock('../../../services/renewals/renewals.service.js', () => ({
14
+ preparePermissionDataForRenewal: jest.fn()
15
+ }))
16
+
17
+ jest.mock('@defra-fish/dynamics-lib', () => ({
18
+ ...jest.requireActual('@defra-fish/dynamics-lib'),
19
+ permissionForFullReferenceNumber: jest.fn(),
20
+ executeQuery: jest.fn()
21
+ }))
22
+
23
+ const getMockRequest = (referenceNumber = 'ABC123') => ({
24
+ params: { referenceNumber }
25
+ })
26
+
27
+ const getMockResponseToolkit = () => ({
28
+ response: jest.fn()
29
+ })
30
+
31
+ const permissionForFullReferenceNumberMock = () => ({
32
+ entity: MOCK_EXISTING_PERMISSION_ENTITY,
33
+ expanded: {
34
+ licensee: { entity: MOCK_EXISTING_CONTACT_ENTITY, expanded: {} },
35
+ concessionProofs: [{ entity: MOCK_CONCESSION_PROOF_ENTITY, expanded: { concession: { entity: MOCK_CONCESSION } } }],
36
+ permit: { entity: MOCK_1DAY_SENIOR_PERMIT_ENTITY, expanded: {} }
37
+ }
38
+ })
39
+
40
+ describe('permissionRenewalData', () => {
41
+ beforeEach(jest.clearAllMocks)
42
+
43
+ it('should call permissionForFullReferenceNumber with referenceNumber', async () => {
44
+ executeQuery.mockResolvedValueOnce([permissionForFullReferenceNumberMock()])
45
+
46
+ const referenceNumber = 'REFERENCE123'
47
+ const request = getMockRequest(referenceNumber)
48
+ const responseToolkit = getMockResponseToolkit()
49
+
50
+ await permissionRenewalData[0].options.handler(request, responseToolkit)
51
+
52
+ expect(permissionForFullReferenceNumber).toHaveBeenCalledWith(referenceNumber)
53
+ })
54
+
55
+ it('should call preparePermissionDataForRenewal with the expected data', async () => {
56
+ const permissionMock = permissionForFullReferenceNumberMock()
57
+ executeQuery.mockResolvedValueOnce([permissionMock])
58
+
59
+ await permissionRenewalData[0].options.handler(getMockRequest(), getMockResponseToolkit())
60
+
61
+ const expectedData = {
62
+ ...permissionMock.entity.toJSON(),
63
+ licensee: permissionMock.expanded.licensee.entity.toJSON(),
64
+ concessions: [],
65
+ permit: permissionMock.expanded.permit.entity.toJSON()
66
+ }
67
+ expect(preparePermissionDataForRenewal).toHaveBeenCalledWith(expectedData)
68
+ })
69
+
70
+ it('should return continue response', async () => {
71
+ executeQuery.mockResolvedValueOnce([permissionForFullReferenceNumberMock()])
72
+ const request = getMockRequest({})
73
+ const responseToolkit = getMockResponseToolkit()
74
+
75
+ expect(await permissionRenewalData[0].options.handler(request, responseToolkit)).toEqual(responseToolkit.continue)
76
+ })
77
+
78
+ describe('metadata', () => {
79
+ it.each(['description', 'notes', 'tags', 'validate'])('should have expected %s', async key => {
80
+ expect(permissionRenewalData[0].options[key]).toMatchSnapshot()
81
+ })
82
+ })
83
+
84
+ describe('if there are no matching permissions', () => {
85
+ it('should raise an error', async () => {
86
+ executeQuery.mockResolvedValueOnce([])
87
+ const request = getMockRequest({})
88
+ const responseToolkit = getMockResponseToolkit()
89
+
90
+ const boomError = Boom.unauthorized('Permission not found for renewal')
91
+ await expect(permissionRenewalData[0].options.handler(request, responseToolkit)).rejects.toThrow(boomError)
92
+ })
93
+ })
94
+
95
+ describe('if there are multiple matching permissions', () => {
96
+ it('should raise an error', async () => {
97
+ executeQuery.mockResolvedValueOnce(['foo', 'bar'])
98
+ const request = getMockRequest({})
99
+ const responseToolkit = getMockResponseToolkit()
100
+
101
+ const error = 'Unable to get permission data for renewal, non-unique results for query'
102
+ await expect(permissionRenewalData[0].options.handler(request, responseToolkit)).rejects.toThrow(error)
103
+ })
104
+ })
105
+
106
+ it('should throw error if query execution fails', async () => {
107
+ permissionForFullReferenceNumber.mockResolvedValueOnce({ filter: 'coffee' })
108
+ const error = 'Wuh-oh!'
109
+ executeQuery.mockImplementationOnce(() => {
110
+ throw new Error(error)
111
+ })
112
+ const request = getMockRequest({})
113
+ const responseToolkit = getMockResponseToolkit()
114
+
115
+ await expect(permissionRenewalData[0].options.handler(request, responseToolkit)).rejects.toThrow(error)
116
+ })
117
+ })
@@ -7,6 +7,7 @@ import StagingExceptions from './staging-exceptions.js'
7
7
  import Authenticate from './authenticate.js'
8
8
  import Users from './system-users.js'
9
9
  import RecurringPayment from './recurring-payments.js'
10
+ import Renewals from './renewals.js'
10
11
 
11
12
  import Static from './static.js'
12
13
 
@@ -20,5 +21,6 @@ export default [
20
21
  ...StagingExceptions,
21
22
  ...Authenticate,
22
23
  ...Users,
23
- ...RecurringPayment
24
+ ...RecurringPayment,
25
+ ...Renewals
24
26
  ]
@@ -0,0 +1,58 @@
1
+ import Boom from '@hapi/boom'
2
+ import { preparePermissionDataForRenewal } from '../../services/renewals/renewals.service.js'
3
+ import { permissionRenewalDataRequestParamsSchema, permissionRenewalDataResponseSchema } from '../../schema/renewals.schema.js'
4
+ import db from 'debug'
5
+ import { permissionForFullReferenceNumber, executeQuery } from '@defra-fish/dynamics-lib'
6
+ const debug = db('sales:permission-renewal-data')
7
+
8
+ const executeWithErrorLog = async query => {
9
+ try {
10
+ return await executeQuery(query)
11
+ } catch (e) {
12
+ debug(`Error executing query with filter ${query.filter}`)
13
+ throw e
14
+ }
15
+ }
16
+
17
+ export default [
18
+ {
19
+ method: 'GET',
20
+ path: '/permissionRenewalData/{referenceNumber}',
21
+ options: {
22
+ handler: async (request, h) => {
23
+ const results = await executeWithErrorLog(permissionForFullReferenceNumber(request.params.referenceNumber))
24
+
25
+ if (results.length === 1) {
26
+ const permissionData = preparePermissionDataForRenewal({
27
+ ...results[0].entity.toJSON(),
28
+ licensee: results[0].expanded.licensee.entity.toJSON(),
29
+ concessions: [],
30
+ permit: results[0].expanded.permit.entity.toJSON()
31
+ })
32
+ return h.response(permissionData)
33
+ } else if (results.length === 0) {
34
+ throw Boom.unauthorized('Permission not found for renewal')
35
+ } else {
36
+ throw new Error('Unable to get permission data for renewal, non-unique results for query')
37
+ }
38
+ },
39
+ description: 'Prepare data for renewing a permission based on the existing data',
40
+ notes: `
41
+ Prepare data for renewing a permission based on the existing data
42
+ `,
43
+ tags: ['api', 'renewals'],
44
+ validate: {
45
+ params: permissionRenewalDataRequestParamsSchema
46
+ },
47
+ plugins: {
48
+ 'hapi-swagger': {
49
+ responses: {
50
+ 200: { description: 'Renewal data was prepared for the permission', schema: permissionRenewalDataResponseSchema },
51
+ 401: { description: 'Renewal data could not be prepared for the permission' }
52
+ },
53
+ order: 1
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ]
@@ -0,0 +1,113 @@
1
+ import moment from 'moment'
2
+ import { preparePermissionDataForRenewal } from '../renewals.service.js'
3
+
4
+ describe('preparePermissionDataForRenewal', () => {
5
+ const existingPermission = overrides => ({
6
+ licensee: {
7
+ birthDate: '1991-01-01',
8
+ country: {
9
+ label: 'England',
10
+ description: 'GB-ENG'
11
+ },
12
+ email: 'email@example.com',
13
+ firstName: 'Sally',
14
+ lastName: 'Salmon',
15
+ mobilePhone: null,
16
+ postalFulfilment: false,
17
+ postcode: 'TE1 1ST',
18
+ street: 'Angler Street',
19
+ town: 'Fishville',
20
+ preferredMethodOfNewsletter: {
21
+ label: 'Email'
22
+ },
23
+ preferredMethodOfConfirmation: {
24
+ label: 'Text'
25
+ },
26
+ preferredMethodOfReminder: {
27
+ label: 'Letter'
28
+ },
29
+ shortTermPreferredMethodOfConfirmation: {
30
+ label: 'Text'
31
+ }
32
+ },
33
+ permit: {
34
+ id: '123456',
35
+ permitSubtype: {
36
+ label: 'Salmon and sea trout'
37
+ },
38
+ numberOfRods: 1
39
+ },
40
+ ...overrides
41
+ })
42
+
43
+ it('should assign the correct data to the base permission', async () => {
44
+ const expectedData = {
45
+ isRenewal: true,
46
+ licenceLength: '12M',
47
+ licenceType: 'Salmon and sea trout',
48
+ numberOfRods: '1',
49
+ isLicenceForYou: true,
50
+ permitId: '123456'
51
+ }
52
+ expect(preparePermissionDataForRenewal(existingPermission())).toEqual(expect.objectContaining(expectedData))
53
+ })
54
+
55
+ it('should copy the relevant licensee data', async () => {
56
+ const expectedData = {
57
+ birthDate: '1991-01-01',
58
+ country: 'England',
59
+ countryCode: 'GB-ENG',
60
+ email: 'email@example.com',
61
+ firstName: 'Sally',
62
+ lastName: 'Salmon',
63
+ postcode: 'TE1 1ST',
64
+ street: 'Angler Street',
65
+ town: 'Fishville',
66
+ preferredMethodOfNewsletter: 'Email',
67
+ preferredMethodOfConfirmation: 'Text',
68
+ preferredMethodOfReminder: 'Letter'
69
+ }
70
+ expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.objectContaining(expectedData))
71
+ })
72
+
73
+ it('should not assign shortTermPreferredMethodOfConfirmation to the licensee', async () => {
74
+ const licenseeData = preparePermissionDataForRenewal(existingPermission()).licensee
75
+ expect(licenseeData.shortTermPreferredMethodOfConfirmation).toBeUndefined()
76
+ })
77
+
78
+ it('should remove null values from the licensee object', async () => {
79
+ expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.not.objectContaining({ mobilePhone: null }))
80
+ })
81
+
82
+ it('should keep false values on the licensee object', async () => {
83
+ expect(preparePermissionDataForRenewal(existingPermission()).licensee).toEqual(expect.objectContaining({ postalFulfilment: false }))
84
+ })
85
+
86
+ describe('when the original permission has expired', () => {
87
+ it('should process the data correctly', async () => {
88
+ const endDate = moment().subtract(5, 'days')
89
+ const expectedData = {
90
+ renewedHasExpired: true,
91
+ licenceToStart: 'after-payment',
92
+ licenceStartDate: moment().format('YYYY-MM-DD'),
93
+ licenceStartTime: 0,
94
+ renewedEndDate: endDate.toISOString()
95
+ }
96
+ expect(preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
97
+ })
98
+ })
99
+
100
+ describe('when the original permission has not expired', () => {
101
+ it('should process the data correctly', async () => {
102
+ const endDate = moment().add(5, 'days')
103
+ const expectedData = {
104
+ renewedHasExpired: false,
105
+ licenceToStart: 'another-date',
106
+ licenceStartDate: endDate.format('YYYY-MM-DD'),
107
+ licenceStartTime: endDate.hours(),
108
+ renewedEndDate: endDate.toISOString()
109
+ }
110
+ expect(preparePermissionDataForRenewal(existingPermission({ endDate }))).toEqual(expect.objectContaining(expectedData))
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,86 @@
1
+ import { SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
2
+ import moment from 'moment-timezone'
3
+
4
+ // Replicated from GAFL - need to decide whether to move
5
+ const cacheDateFormat = 'YYYY-MM-DD'
6
+ const licenceToStart = {
7
+ AFTER_PAYMENT: 'after-payment',
8
+ ANOTHER_DATE: 'another-date'
9
+ }
10
+
11
+ export const preparePermissionDataForRenewal = existingPermission => ({
12
+ ...prepareBasePermissionData(existingPermission),
13
+ ...prepareDateData(existingPermission),
14
+ licensee: prepareLicenseeData(existingPermission),
15
+ permitId: preparePermitId(existingPermission)
16
+ })
17
+
18
+ const prepareBasePermissionData = existingPermission => ({
19
+ isRenewal: true,
20
+ licenceLength: '12M', // Always for renewals
21
+ licenceType: existingPermission.permit.permitSubtype.label,
22
+ numberOfRods: existingPermission.permit.numberOfRods.toString(),
23
+ isLicenceForYou: true
24
+ })
25
+
26
+ const prepareLicenseeData = existingPermission => {
27
+ const licenseeData = Object.assign(copyFilteredLicenseeData(existingPermission), prepareCountryData(existingPermission))
28
+
29
+ // Delete any licensee objects which are null
30
+ Object.entries(licenseeData)
31
+ .filter(e => e[1] === null)
32
+ .map(e => e[0])
33
+ .forEach(k => delete licenseeData[k])
34
+
35
+ Object.assign(licenseeData, prepareContactMethodData(existingPermission))
36
+
37
+ return licenseeData
38
+ }
39
+
40
+ const prepareDateData = existingPermission => {
41
+ const endDateMoment = moment.utc(existingPermission.endDate).tz(SERVICE_LOCAL_TIME)
42
+ const renewedHasExpired = !endDateMoment.isAfter(moment().tz(SERVICE_LOCAL_TIME))
43
+
44
+ const dateData = renewedHasExpired ? dateDataIfExpired() : dateDataIfNotExpired(endDateMoment)
45
+ dateData.renewedEndDate = endDateMoment.toISOString()
46
+
47
+ return dateData
48
+ }
49
+
50
+ // Retain existing data except country and shortTermPreferredMethodOfConfirmation
51
+ const copyFilteredLicenseeData = existingPermission =>
52
+ (({ country: _country, shortTermPreferredMethodOfConfirmation: _shortTermPreferredMethodOfConfirmation, ...l }) => l)(
53
+ existingPermission.licensee
54
+ )
55
+
56
+ const prepareCountryData = existingPermission => ({
57
+ country: existingPermission.licensee.country.label,
58
+ countryCode: existingPermission.licensee.country.description
59
+ })
60
+
61
+ const prepareContactMethodData = existingPermission => ({
62
+ preferredMethodOfNewsletter: existingPermission.licensee.preferredMethodOfNewsletter.label,
63
+ preferredMethodOfConfirmation: existingPermission.licensee.preferredMethodOfConfirmation.label,
64
+ preferredMethodOfReminder: existingPermission.licensee.preferredMethodOfReminder.label
65
+ })
66
+
67
+ const getLicenceStartDate = licenceEndDate => moment(licenceEndDate).add(1, 'minute').seconds(0).tz(SERVICE_LOCAL_TIME)
68
+
69
+ const dateDataIfExpired = () => ({
70
+ renewedHasExpired: true,
71
+ licenceToStart: licenceToStart.AFTER_PAYMENT,
72
+ licenceStartDate: moment().tz(SERVICE_LOCAL_TIME).format(cacheDateFormat),
73
+ licenceStartTime: 0
74
+ })
75
+
76
+ const dateDataIfNotExpired = endDateMoment => {
77
+ const licenceStartDate = getLicenceStartDate(endDateMoment)
78
+ return {
79
+ renewedHasExpired: false,
80
+ licenceToStart: licenceToStart.ANOTHER_DATE,
81
+ licenceStartDate: licenceStartDate.format(cacheDateFormat),
82
+ licenceStartTime: licenceStartDate.hours()
83
+ }
84
+ }
85
+
86
+ const preparePermitId = existingPermission => existingPermission.permit.id