@defra/forms-engine-plugin 2.1.3 → 2.1.5
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/.server/server/plugins/engine/models/FormModel.js +1 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
- package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
- package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
- package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +5 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
- package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
- package/.server/server/plugins/engine/services/notifyService.js +11 -2
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +10 -0
- package/.server/server/plugins/engine/types/index.js +4 -0
- package/.server/server/plugins/engine/types/index.js.map +1 -0
- package/.server/server/plugins/engine/types/schema.d.ts +5 -0
- package/.server/server/plugins/engine/types/schema.js +24 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -0
- package/.server/server/plugins/engine/types.d.ts +37 -1
- package/.server/server/plugins/engine/types.js +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
- package/.server/server/types.d.ts +1 -1
- package/.server/server/types.js.map +1 -1
- package/package.json +6 -1
- package/src/server/plugins/engine/models/FormModel.test.ts +64 -0
- package/src/server/plugins/engine/models/FormModel.ts +5 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
- package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
- package/src/server/plugins/engine/services/notifyService.ts +24 -3
- package/src/server/plugins/engine/types/index.ts +96 -0
- package/src/server/plugins/engine/types/schema.test.ts +152 -0
- package/src/server/plugins/engine/types/schema.ts +45 -0
- package/src/server/plugins/engine/types.ts +51 -1
- package/src/server/types.ts +2 -1
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { type FormMetadata } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
4
|
+
import { type Field } from '~/src/server/plugins/engine/components/helpers.js'
|
|
5
|
+
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
6
|
+
import {
|
|
7
|
+
type DetailItem,
|
|
8
|
+
type DetailItemField,
|
|
9
|
+
type DetailItemRepeat
|
|
10
|
+
} from '~/src/server/plugins/engine/models/types.js'
|
|
11
|
+
import { format } from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
|
|
12
|
+
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
13
|
+
import {
|
|
14
|
+
FileStatus,
|
|
15
|
+
FormAdapterSubmissionSchemaVersion,
|
|
16
|
+
UploadStatus,
|
|
17
|
+
type FileState,
|
|
18
|
+
type FormAdapterSubmissionMessagePayload
|
|
19
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
20
|
+
import { FormStatus } from '~/src/server/routes/types.js'
|
|
21
|
+
import definition from '~/test/form/definitions/repeat-mixed.js'
|
|
22
|
+
|
|
23
|
+
const submitResponse = {
|
|
24
|
+
message: 'Submit completed',
|
|
25
|
+
result: {
|
|
26
|
+
files: {
|
|
27
|
+
main: '00000000-0000-0000-0000-000000000000',
|
|
28
|
+
repeaters: {
|
|
29
|
+
pizza: '11111111-1111-1111-1111-111111111111'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const model = new FormModel(definition, {
|
|
36
|
+
basePath: 'test'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const dummyField: Field = {
|
|
40
|
+
getFormValueFromState: (_) => 'hello world'
|
|
41
|
+
} as Field
|
|
42
|
+
|
|
43
|
+
const itemId1 = 'abc-123'
|
|
44
|
+
const itemId2 = 'xyz-987'
|
|
45
|
+
|
|
46
|
+
const state = {
|
|
47
|
+
$$__referenceNumber: 'foobar',
|
|
48
|
+
orderType: 'delivery',
|
|
49
|
+
pizza: [
|
|
50
|
+
{
|
|
51
|
+
toppings: 'Ham',
|
|
52
|
+
quantity: 2,
|
|
53
|
+
itemId: itemId1
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
toppings: 'Pepperoni',
|
|
57
|
+
quantity: 1,
|
|
58
|
+
itemId: itemId2
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pageUrl = new URL('http://example.com/repeat/pizza-order/summary')
|
|
64
|
+
|
|
65
|
+
const request = buildFormContextRequest({
|
|
66
|
+
method: 'get',
|
|
67
|
+
url: pageUrl,
|
|
68
|
+
path: pageUrl.pathname,
|
|
69
|
+
params: {
|
|
70
|
+
path: 'pizza-order',
|
|
71
|
+
slug: 'repeat'
|
|
72
|
+
},
|
|
73
|
+
query: {},
|
|
74
|
+
app: { model }
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const context = model.getFormContext(request, state)
|
|
78
|
+
|
|
79
|
+
const testDetailItemField: DetailItemField = {
|
|
80
|
+
name: 'exampleField',
|
|
81
|
+
label: 'Example Field',
|
|
82
|
+
href: '/example-field',
|
|
83
|
+
title: 'Example Field Title',
|
|
84
|
+
field: dummyField,
|
|
85
|
+
value: 'Example Value'
|
|
86
|
+
} as DetailItemField
|
|
87
|
+
|
|
88
|
+
const testDetailItemField2: DetailItemField = {
|
|
89
|
+
name: 'exampleField2',
|
|
90
|
+
label: 'Example Field 2',
|
|
91
|
+
href: '/example-field-2',
|
|
92
|
+
title: 'Example Field 2 Title',
|
|
93
|
+
field: dummyField,
|
|
94
|
+
value: 'Example Value 2'
|
|
95
|
+
} as DetailItemField
|
|
96
|
+
|
|
97
|
+
const testDetailItemRepeat: DetailItemRepeat = {
|
|
98
|
+
name: 'exampleRepeat',
|
|
99
|
+
label: 'Example Repeat',
|
|
100
|
+
href: '/example-repeat',
|
|
101
|
+
title: 'Example Repeat Title',
|
|
102
|
+
value: 'Example Repeat Value',
|
|
103
|
+
subItems: [
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
name: 'subItem1_1',
|
|
107
|
+
label: 'Sub Item 1 1',
|
|
108
|
+
field: dummyField,
|
|
109
|
+
href: '/sub-item-1-1',
|
|
110
|
+
title: 'Sub Item 1 1 Title',
|
|
111
|
+
value: 'Sub Item 1 1 Value'
|
|
112
|
+
} as DetailItemField,
|
|
113
|
+
{
|
|
114
|
+
name: 'subItem1_2',
|
|
115
|
+
label: 'Sub Item 1 2',
|
|
116
|
+
field: dummyField,
|
|
117
|
+
href: '/sub-item-1-2',
|
|
118
|
+
title: 'Sub Item 1 2 Title',
|
|
119
|
+
value: 'Sub Item 1 2 Value'
|
|
120
|
+
} as DetailItemField
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
name: 'subItem2_1',
|
|
125
|
+
label: 'Sub Item 2 1',
|
|
126
|
+
field: dummyField,
|
|
127
|
+
href: '/sub-item-2-1',
|
|
128
|
+
title: 'Sub Item 2 1 Title',
|
|
129
|
+
value: 'Sub Item 2 1 Value'
|
|
130
|
+
} as DetailItemField
|
|
131
|
+
]
|
|
132
|
+
]
|
|
133
|
+
} as DetailItemRepeat
|
|
134
|
+
|
|
135
|
+
const fileState: FileState = {
|
|
136
|
+
uploadId: '123',
|
|
137
|
+
status: {
|
|
138
|
+
form: {
|
|
139
|
+
file: {
|
|
140
|
+
fileId: '123-456-789',
|
|
141
|
+
contentLength: 1,
|
|
142
|
+
filename: 'foobar.txt',
|
|
143
|
+
fileStatus: FileStatus.complete
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
uploadStatus: UploadStatus.ready,
|
|
147
|
+
numberOfRejectedFiles: 0,
|
|
148
|
+
metadata: {
|
|
149
|
+
retrievalKey: '123'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const fileState2: FileState = {
|
|
155
|
+
uploadId: '456',
|
|
156
|
+
status: {
|
|
157
|
+
form: {
|
|
158
|
+
file: {
|
|
159
|
+
fileId: '456-789-123',
|
|
160
|
+
contentLength: 1,
|
|
161
|
+
filename: 'bazbuzz.txt',
|
|
162
|
+
fileStatus: FileStatus.complete
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
uploadStatus: UploadStatus.ready,
|
|
166
|
+
numberOfRejectedFiles: 0,
|
|
167
|
+
metadata: {
|
|
168
|
+
retrievalKey: '456'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const testDetailItemFile1: DetailItemField = Object.create(
|
|
174
|
+
FileUploadField.prototype
|
|
175
|
+
)
|
|
176
|
+
Object.assign(testDetailItemFile1, {
|
|
177
|
+
name: 'exampleFile1',
|
|
178
|
+
label: 'Example File Field',
|
|
179
|
+
href: '/example-file',
|
|
180
|
+
title: 'Example File Field Title',
|
|
181
|
+
field: testDetailItemFile1,
|
|
182
|
+
value: 'Example File Value',
|
|
183
|
+
state: {
|
|
184
|
+
exampleFile1: [fileState, fileState2]
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const items: DetailItem[] = [
|
|
189
|
+
testDetailItemField,
|
|
190
|
+
testDetailItemField2,
|
|
191
|
+
testDetailItemRepeat,
|
|
192
|
+
testDetailItemFile1
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
describe('Adapter v1 formatter', () => {
|
|
196
|
+
beforeEach(() => {
|
|
197
|
+
jest.clearAllMocks()
|
|
198
|
+
jest.useFakeTimers()
|
|
199
|
+
jest.setSystemTime(new Date('2024-01-15T10:30:00.000Z'))
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
afterEach(() => {
|
|
203
|
+
jest.useRealTimers()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should return the adapter v1 output with complete formMetadata', () => {
|
|
207
|
+
const formMetadata: FormMetadata = {
|
|
208
|
+
id: 'form-123',
|
|
209
|
+
slug: 'test-form',
|
|
210
|
+
title: 'Test Form',
|
|
211
|
+
notificationEmail: 'test@example.com'
|
|
212
|
+
} as FormMetadata
|
|
213
|
+
|
|
214
|
+
const formStatus = {
|
|
215
|
+
isPreview: false,
|
|
216
|
+
state: FormStatus.Live
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const body = format(
|
|
220
|
+
context,
|
|
221
|
+
items,
|
|
222
|
+
model,
|
|
223
|
+
submitResponse,
|
|
224
|
+
formStatus,
|
|
225
|
+
formMetadata
|
|
226
|
+
)
|
|
227
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
228
|
+
|
|
229
|
+
expect(parsedBody.meta).toEqual({
|
|
230
|
+
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
|
|
231
|
+
timestamp: '2024-01-15T10:30:00.000Z',
|
|
232
|
+
referenceNumber: 'foobar',
|
|
233
|
+
formName: definition.name,
|
|
234
|
+
formId: 'form-123',
|
|
235
|
+
formSlug: 'test-form',
|
|
236
|
+
status: FormStatus.Live,
|
|
237
|
+
isPreview: false,
|
|
238
|
+
notificationEmail: 'test@example.com'
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
expect(parsedBody.data).toEqual({
|
|
242
|
+
main: {
|
|
243
|
+
exampleField: 'hello world',
|
|
244
|
+
exampleField2: 'hello world'
|
|
245
|
+
},
|
|
246
|
+
repeaters: {
|
|
247
|
+
exampleRepeat: [
|
|
248
|
+
{
|
|
249
|
+
subItem1_1: 'hello world',
|
|
250
|
+
subItem1_2: 'hello world'
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
subItem2_1: 'hello world'
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
files: {
|
|
258
|
+
exampleFile1: [
|
|
259
|
+
{
|
|
260
|
+
fileId: '123-456-789',
|
|
261
|
+
userDownloadLink: 'https://forms-designer/file-download/123-456-789'
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
fileId: '456-789-123',
|
|
265
|
+
userDownloadLink: 'https://forms-designer/file-download/456-789-123'
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should handle preview form status correctly', () => {
|
|
273
|
+
const formMetadata: FormMetadata = {
|
|
274
|
+
id: 'form-123',
|
|
275
|
+
slug: 'test-form',
|
|
276
|
+
title: 'Test Form',
|
|
277
|
+
notificationEmail: 'test@example.com'
|
|
278
|
+
} as FormMetadata
|
|
279
|
+
|
|
280
|
+
const formStatus = {
|
|
281
|
+
isPreview: true,
|
|
282
|
+
state: FormStatus.Draft
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const body = format(
|
|
286
|
+
context,
|
|
287
|
+
items,
|
|
288
|
+
model,
|
|
289
|
+
submitResponse,
|
|
290
|
+
formStatus,
|
|
291
|
+
formMetadata
|
|
292
|
+
)
|
|
293
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
294
|
+
|
|
295
|
+
expect(parsedBody.meta.status).toBe(FormStatus.Draft)
|
|
296
|
+
expect(parsedBody.meta.isPreview).toBe(true)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should handle missing formMetadata with empty strings', () => {
|
|
300
|
+
const formStatus = {
|
|
301
|
+
isPreview: false,
|
|
302
|
+
state: FormStatus.Live
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
306
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
307
|
+
|
|
308
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
309
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
310
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
311
|
+
expect(parsedBody.meta.formName).toBe(definition.name)
|
|
312
|
+
expect(parsedBody.meta.referenceNumber).toBe('foobar')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should handle partial formMetadata', () => {
|
|
316
|
+
const formMetadata: FormMetadata = {
|
|
317
|
+
id: 'form-456',
|
|
318
|
+
slug: 'partial-form',
|
|
319
|
+
title: 'Partial Form'
|
|
320
|
+
} as FormMetadata
|
|
321
|
+
|
|
322
|
+
const formStatus = {
|
|
323
|
+
isPreview: true,
|
|
324
|
+
state: FormStatus.Draft
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const body = format(
|
|
328
|
+
context,
|
|
329
|
+
items,
|
|
330
|
+
model,
|
|
331
|
+
submitResponse,
|
|
332
|
+
formStatus,
|
|
333
|
+
formMetadata
|
|
334
|
+
)
|
|
335
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
336
|
+
|
|
337
|
+
expect(parsedBody.meta.formId).toBe('form-456')
|
|
338
|
+
expect(parsedBody.meta.formSlug).toBe('partial-form')
|
|
339
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
340
|
+
expect(parsedBody.meta.status).toBe(FormStatus.Draft)
|
|
341
|
+
expect(parsedBody.meta.isPreview).toBe(true)
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('should use correct schema version', () => {
|
|
345
|
+
const formStatus = {
|
|
346
|
+
isPreview: false,
|
|
347
|
+
state: FormStatus.Live
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
351
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
352
|
+
|
|
353
|
+
expect(parsedBody.meta.schemaVersion).toBe(
|
|
354
|
+
FormAdapterSubmissionSchemaVersion.V1
|
|
355
|
+
)
|
|
356
|
+
expect(parsedBody.meta.schemaVersion).toBe(1)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('should generate valid timestamp', () => {
|
|
360
|
+
const formStatus = {
|
|
361
|
+
isPreview: false,
|
|
362
|
+
state: FormStatus.Live
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
366
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
367
|
+
|
|
368
|
+
expect(parsedBody.meta.timestamp).toBe('2024-01-15T10:30:00.000Z')
|
|
369
|
+
expect(typeof parsedBody.meta.timestamp).toBe('string')
|
|
370
|
+
|
|
371
|
+
expect(new Date(parsedBody.meta.timestamp)).toEqual(
|
|
372
|
+
new Date('2024-01-15T10:30:00.000Z')
|
|
373
|
+
)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should handle empty items array', () => {
|
|
377
|
+
const formStatus = {
|
|
378
|
+
isPreview: false,
|
|
379
|
+
state: FormStatus.Live
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const body = format(context, [], model, submitResponse, formStatus)
|
|
383
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
384
|
+
|
|
385
|
+
expect(parsedBody.data.main).toEqual({})
|
|
386
|
+
expect(parsedBody.data.repeaters).toEqual({})
|
|
387
|
+
expect(parsedBody.data.files).toEqual({})
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should handle different form statuses', () => {
|
|
391
|
+
const testCases = [
|
|
392
|
+
{
|
|
393
|
+
isPreview: false,
|
|
394
|
+
state: FormStatus.Live,
|
|
395
|
+
expectedStatus: FormStatus.Live
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
isPreview: true,
|
|
399
|
+
state: FormStatus.Draft,
|
|
400
|
+
expectedStatus: FormStatus.Draft
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
isPreview: true,
|
|
404
|
+
state: FormStatus.Live,
|
|
405
|
+
expectedStatus: FormStatus.Draft
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
testCases.forEach(({ isPreview, state, expectedStatus }) => {
|
|
410
|
+
const formStatus = { isPreview, state }
|
|
411
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
412
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
413
|
+
|
|
414
|
+
expect(parsedBody.meta.status).toBe(expectedStatus)
|
|
415
|
+
expect(parsedBody.meta.isPreview).toBe(isPreview)
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('should return valid JSON string', () => {
|
|
420
|
+
const formStatus = {
|
|
421
|
+
isPreview: false,
|
|
422
|
+
state: FormStatus.Live
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
426
|
+
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
428
|
+
expect(() => JSON.parse(body)).not.toThrow()
|
|
429
|
+
expect(typeof body).toBe('string')
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should handle formMetadata with only id', () => {
|
|
433
|
+
const formMetadata: FormMetadata = {
|
|
434
|
+
id: 'only-id-form'
|
|
435
|
+
} as FormMetadata
|
|
436
|
+
|
|
437
|
+
const formStatus = {
|
|
438
|
+
isPreview: false,
|
|
439
|
+
state: FormStatus.Live
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const body = format(
|
|
443
|
+
context,
|
|
444
|
+
items,
|
|
445
|
+
model,
|
|
446
|
+
submitResponse,
|
|
447
|
+
formStatus,
|
|
448
|
+
formMetadata
|
|
449
|
+
)
|
|
450
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
451
|
+
|
|
452
|
+
expect(parsedBody.meta.formId).toBe('only-id-form')
|
|
453
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
454
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should handle formMetadata with only slug', () => {
|
|
458
|
+
const formMetadata: FormMetadata = {
|
|
459
|
+
slug: 'only-slug-form'
|
|
460
|
+
} as FormMetadata
|
|
461
|
+
|
|
462
|
+
const formStatus = {
|
|
463
|
+
isPreview: false,
|
|
464
|
+
state: FormStatus.Live
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const body = format(
|
|
468
|
+
context,
|
|
469
|
+
items,
|
|
470
|
+
model,
|
|
471
|
+
submitResponse,
|
|
472
|
+
formStatus,
|
|
473
|
+
formMetadata
|
|
474
|
+
)
|
|
475
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
476
|
+
|
|
477
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
478
|
+
expect(parsedBody.meta.formSlug).toBe('only-slug-form')
|
|
479
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('should handle formMetadata with only notificationEmail', () => {
|
|
483
|
+
const formMetadata: FormMetadata = {
|
|
484
|
+
notificationEmail: 'only-email@example.com'
|
|
485
|
+
} as FormMetadata
|
|
486
|
+
|
|
487
|
+
const formStatus = {
|
|
488
|
+
isPreview: false,
|
|
489
|
+
state: FormStatus.Live
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const body = format(
|
|
493
|
+
context,
|
|
494
|
+
items,
|
|
495
|
+
model,
|
|
496
|
+
submitResponse,
|
|
497
|
+
formStatus,
|
|
498
|
+
formMetadata
|
|
499
|
+
)
|
|
500
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
501
|
+
|
|
502
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
503
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
504
|
+
expect(parsedBody.meta.notificationEmail).toBe('only-email@example.com')
|
|
505
|
+
})
|
|
506
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FormMetadata,
|
|
3
|
+
type SubmitResponsePayload
|
|
4
|
+
} from '@defra/forms-model'
|
|
5
|
+
|
|
6
|
+
import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
|
|
7
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
8
|
+
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
|
|
9
|
+
import { format as machineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
|
|
10
|
+
import {
|
|
11
|
+
FormAdapterSubmissionSchemaVersion,
|
|
12
|
+
type FormAdapterSubmissionMessageData,
|
|
13
|
+
type FormAdapterSubmissionMessagePayload,
|
|
14
|
+
type FormContext
|
|
15
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
16
|
+
import { FormStatus } from '~/src/server/routes/types.js'
|
|
17
|
+
|
|
18
|
+
export function format(
|
|
19
|
+
context: FormContext,
|
|
20
|
+
items: DetailItem[],
|
|
21
|
+
model: FormModel,
|
|
22
|
+
submitResponse: SubmitResponsePayload,
|
|
23
|
+
formStatus: ReturnType<typeof checkFormStatus>,
|
|
24
|
+
formMetadata?: FormMetadata
|
|
25
|
+
): string {
|
|
26
|
+
const v2DataString = machineV2(
|
|
27
|
+
context,
|
|
28
|
+
items,
|
|
29
|
+
model,
|
|
30
|
+
submitResponse,
|
|
31
|
+
formStatus
|
|
32
|
+
)
|
|
33
|
+
const v2DataParsed = JSON.parse(v2DataString) as {
|
|
34
|
+
data: FormAdapterSubmissionMessageData
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const payload: FormAdapterSubmissionMessagePayload = {
|
|
38
|
+
meta: {
|
|
39
|
+
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
|
|
40
|
+
timestamp: new Date(),
|
|
41
|
+
referenceNumber: context.referenceNumber,
|
|
42
|
+
formName: model.name,
|
|
43
|
+
formId: formMetadata?.id ?? '',
|
|
44
|
+
formSlug: formMetadata?.slug ?? '',
|
|
45
|
+
status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
|
|
46
|
+
isPreview: formStatus.isPreview,
|
|
47
|
+
notificationEmail: formMetadata?.notificationEmail ?? ''
|
|
48
|
+
},
|
|
49
|
+
data: v2DataParsed.data
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return JSON.stringify(payload)
|
|
53
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FormMetadata,
|
|
3
|
+
type SubmitResponsePayload
|
|
4
|
+
} from '@defra/forms-model'
|
|
2
5
|
import { addDays, format as dateFormat } from 'date-fns'
|
|
3
6
|
|
|
4
7
|
import { config } from '~/src/config/index.js'
|
|
@@ -18,7 +21,8 @@ export function format(
|
|
|
18
21
|
items: DetailItem[],
|
|
19
22
|
model: FormModel,
|
|
20
23
|
submitResponse: SubmitResponsePayload,
|
|
21
|
-
formStatus: ReturnType<typeof checkFormStatus
|
|
24
|
+
formStatus: ReturnType<typeof checkFormStatus>,
|
|
25
|
+
_formMetadata?: FormMetadata
|
|
22
26
|
) {
|
|
23
27
|
const { files } = submitResponse.result
|
|
24
28
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FormMetadata,
|
|
3
|
+
type SubmitResponsePayload
|
|
4
|
+
} from '@defra/forms-model'
|
|
2
5
|
|
|
3
6
|
import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
|
|
4
7
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
5
8
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
|
|
9
|
+
import { format as formatAdapterV1 } from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
|
|
6
10
|
import { format as formatHumanV1 } from '~/src/server/plugins/engine/outputFormatters/human/v1.js'
|
|
7
11
|
import { format as formatMachineV1 } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'
|
|
8
12
|
import { format as formatMachineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
|
|
@@ -13,12 +17,13 @@ type Formatter = (
|
|
|
13
17
|
items: DetailItem[],
|
|
14
18
|
model: FormModel,
|
|
15
19
|
submitResponse: SubmitResponsePayload,
|
|
16
|
-
formStatus: ReturnType<typeof checkFormStatus
|
|
20
|
+
formStatus: ReturnType<typeof checkFormStatus>,
|
|
21
|
+
formMetadata?: FormMetadata
|
|
17
22
|
) => string
|
|
18
23
|
|
|
19
24
|
const formatters: Record<
|
|
20
25
|
string,
|
|
21
|
-
Record<string, Formatter | undefined> | undefined
|
|
26
|
+
Record<string, Formatter | typeof formatAdapterV1 | undefined> | undefined
|
|
22
27
|
> = {
|
|
23
28
|
human: {
|
|
24
29
|
'1': formatHumanV1
|
|
@@ -26,6 +31,9 @@ const formatters: Record<
|
|
|
26
31
|
machine: {
|
|
27
32
|
'1': formatMachineV1,
|
|
28
33
|
'2': formatMachineV2
|
|
34
|
+
},
|
|
35
|
+
adapter: {
|
|
36
|
+
'1': formatAdapterV1
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FormMetadata,
|
|
3
|
+
type SubmitResponsePayload
|
|
4
|
+
} from '@defra/forms-model'
|
|
2
5
|
|
|
3
6
|
import { config } from '~/src/config/index.js'
|
|
4
7
|
import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
|
|
@@ -19,7 +22,8 @@ export function format(
|
|
|
19
22
|
items: DetailItem[],
|
|
20
23
|
model: FormModel,
|
|
21
24
|
_submitResponse: SubmitResponsePayload,
|
|
22
|
-
_formStatus: ReturnType<typeof checkFormStatus
|
|
25
|
+
_formStatus: ReturnType<typeof checkFormStatus>,
|
|
26
|
+
_formMetadata?: FormMetadata
|
|
23
27
|
) {
|
|
24
28
|
const now = new Date()
|
|
25
29
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasComponentsEvenIfNoNext,
|
|
3
|
+
type FormMetadata,
|
|
3
4
|
type Page,
|
|
4
5
|
type SubmitPayload
|
|
5
6
|
} from '@defra/forms-model'
|
|
@@ -111,7 +112,8 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
111
112
|
const { getFormMetadata } = formsService
|
|
112
113
|
|
|
113
114
|
// Get the form metadata using the `slug` param
|
|
114
|
-
const
|
|
115
|
+
const formMetadata = await getFormMetadata(params.slug)
|
|
116
|
+
const { notificationEmail } = formMetadata
|
|
115
117
|
const { isPreview } = checkFormStatus(request.params)
|
|
116
118
|
const emailAddress = notificationEmail ?? this.model.def.outputEmail
|
|
117
119
|
|
|
@@ -120,7 +122,14 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
120
122
|
// Send submission email
|
|
121
123
|
if (emailAddress) {
|
|
122
124
|
const viewModel = this.getSummaryViewModel(request, context)
|
|
123
|
-
await submitForm(
|
|
125
|
+
await submitForm(
|
|
126
|
+
context,
|
|
127
|
+
request,
|
|
128
|
+
viewModel,
|
|
129
|
+
model,
|
|
130
|
+
emailAddress,
|
|
131
|
+
formMetadata
|
|
132
|
+
)
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
await cacheService.setConfirmationState(request, { confirmed: true })
|
|
@@ -150,7 +159,8 @@ async function submitForm(
|
|
|
150
159
|
request: FormRequestPayload,
|
|
151
160
|
summaryViewModel: SummaryViewModel,
|
|
152
161
|
model: FormModel,
|
|
153
|
-
emailAddress: string
|
|
162
|
+
emailAddress: string,
|
|
163
|
+
formMetadata: FormMetadata
|
|
154
164
|
) {
|
|
155
165
|
await extendFileRetention(model, context.state, emailAddress)
|
|
156
166
|
|
|
@@ -184,7 +194,8 @@ async function submitForm(
|
|
|
184
194
|
model,
|
|
185
195
|
emailAddress,
|
|
186
196
|
items,
|
|
187
|
-
submitResponse
|
|
197
|
+
submitResponse,
|
|
198
|
+
formMetadata
|
|
188
199
|
)
|
|
189
200
|
}
|
|
190
201
|
|