@defra/forms-engine-plugin 0.0.4 → 0.0.6

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.
Files changed (260) hide show
  1. package/.server/server/index.js +0 -4
  2. package/.server/server/index.js.map +1 -1
  3. package/.server/server/plugins/engine/helpers.js +3 -0
  4. package/.server/server/plugins/engine/helpers.js.map +1 -1
  5. package/.server/server/plugins/engine/index.js +27 -1
  6. package/.server/server/plugins/engine/index.js.map +1 -1
  7. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -4
  8. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  9. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +4 -10
  10. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  11. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +2 -3
  12. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  13. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -4
  14. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  15. package/.server/server/plugins/engine/plugin.js +65 -6
  16. package/.server/server/plugins/engine/plugin.js.map +1 -1
  17. package/.server/server/plugins/engine/types.js.map +1 -1
  18. package/.server/server/{views → plugins/engine/views}/components/service-banner/template.test.js +1 -1
  19. package/.server/server/plugins/engine/views/components/service-banner/template.test.js.map +1 -0
  20. package/.server/server/{views → plugins/engine/views}/components/tag-env/template.test.js +1 -1
  21. package/.server/server/plugins/engine/views/components/tag-env/template.test.js.map +1 -0
  22. package/.server/server/services/cacheService.js +5 -2
  23. package/.server/server/services/cacheService.js.map +1 -1
  24. package/.server/typings/hapi/index.d.js.map +1 -1
  25. package/README.md +215 -4
  26. package/package.json +3 -3
  27. package/src/client/javascripts/application.js +87 -0
  28. package/src/client/javascripts/file-upload.js +386 -0
  29. package/src/client/stylesheets/_code.scss +33 -0
  30. package/src/client/stylesheets/_govuk-frontend.scss +4 -0
  31. package/src/client/stylesheets/_prose.scss +56 -0
  32. package/src/client/stylesheets/_service-banner.scss +24 -0
  33. package/src/client/stylesheets/_summary-list.scss +28 -0
  34. package/src/client/stylesheets/_tag-env.scss +24 -0
  35. package/src/client/stylesheets/application.scss +14 -0
  36. package/src/common/cookies.js +58 -0
  37. package/src/common/cookies.test.js +23 -0
  38. package/src/common/types.js +5 -0
  39. package/src/config/index.ts +271 -0
  40. package/src/index.ts +31 -0
  41. package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
  42. package/src/server/common/helpers/logging/logger-options.ts +46 -0
  43. package/src/server/common/helpers/logging/logger.ts +7 -0
  44. package/src/server/common/helpers/logging/request-logger.ts +9 -0
  45. package/src/server/common/helpers/logging/request-tracing.js +10 -0
  46. package/src/server/common/helpers/redis-client.js +70 -0
  47. package/src/server/constants.js +1 -0
  48. package/src/server/forms/README.md +10 -0
  49. package/src/server/forms/components.json +1015 -0
  50. package/src/server/forms/report-a-terrorist.json +270 -0
  51. package/src/server/forms/runner-components-test.json +365 -0
  52. package/src/server/forms/test.json +581 -0
  53. package/src/server/index.test.ts +582 -0
  54. package/src/server/index.ts +135 -0
  55. package/src/server/plugins/blankie.test.ts +73 -0
  56. package/src/server/plugins/blankie.ts +48 -0
  57. package/src/server/plugins/crumb.ts +20 -0
  58. package/src/server/plugins/engine/README.md +87 -0
  59. package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
  60. package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
  61. package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
  62. package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
  63. package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
  64. package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
  65. package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
  66. package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
  67. package/src/server/plugins/engine/components/Details.test.ts +49 -0
  68. package/src/server/plugins/engine/components/Details.ts +30 -0
  69. package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
  70. package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
  71. package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
  72. package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
  73. package/src/server/plugins/engine/components/FormComponent.ts +249 -0
  74. package/src/server/plugins/engine/components/Html.test.ts +48 -0
  75. package/src/server/plugins/engine/components/Html.ts +29 -0
  76. package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
  77. package/src/server/plugins/engine/components/InsetText.ts +27 -0
  78. package/src/server/plugins/engine/components/List.test.ts +76 -0
  79. package/src/server/plugins/engine/components/List.ts +72 -0
  80. package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
  81. package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
  82. package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
  83. package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
  84. package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
  85. package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
  86. package/src/server/plugins/engine/components/NumberField.ts +163 -0
  87. package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
  88. package/src/server/plugins/engine/components/RadiosField.ts +24 -0
  89. package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
  90. package/src/server/plugins/engine/components/SelectField.ts +47 -0
  91. package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
  92. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
  93. package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
  94. package/src/server/plugins/engine/components/TextField.test.ts +489 -0
  95. package/src/server/plugins/engine/components/TextField.ts +96 -0
  96. package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
  97. package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
  98. package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
  99. package/src/server/plugins/engine/components/YesNoField.ts +31 -0
  100. package/src/server/plugins/engine/components/constants.ts +1 -0
  101. package/src/server/plugins/engine/components/helpers.ts +330 -0
  102. package/src/server/plugins/engine/components/index.ts +24 -0
  103. package/src/server/plugins/engine/components/types.ts +117 -0
  104. package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
  105. package/src/server/plugins/engine/helpers.test.ts +791 -0
  106. package/src/server/plugins/engine/helpers.ts +384 -0
  107. package/src/server/plugins/engine/index.ts +47 -0
  108. package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
  109. package/src/server/plugins/engine/models/FormModel.ts +443 -0
  110. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  111. package/src/server/plugins/engine/models/Section.ts +0 -0
  112. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
  113. package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
  114. package/src/server/plugins/engine/models/index.ts +2 -0
  115. package/src/server/plugins/engine/models/types.ts +114 -0
  116. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
  117. package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
  118. package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
  119. package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
  120. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
  121. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
  122. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
  123. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
  124. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1116 -0
  125. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +447 -0
  126. package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
  127. package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
  128. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
  129. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +565 -0
  130. package/src/server/plugins/engine/pageControllers/README.md +28 -0
  131. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
  132. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
  133. package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
  134. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +51 -0
  135. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +262 -0
  136. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
  137. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
  138. package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
  139. package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
  140. package/src/server/plugins/engine/pageControllers/index.ts +10 -0
  141. package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
  142. package/src/server/plugins/engine/plugin.ts +753 -0
  143. package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
  144. package/src/server/plugins/engine/services/formsService.js +46 -0
  145. package/src/server/plugins/engine/services/formsService.test.js +90 -0
  146. package/src/server/plugins/engine/services/index.js +3 -0
  147. package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
  148. package/src/server/plugins/engine/services/notifyService.ts +64 -0
  149. package/src/server/plugins/engine/services/uploadService.js +60 -0
  150. package/src/server/plugins/engine/types.ts +317 -0
  151. package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
  152. package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
  153. package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
  154. package/src/server/plugins/engine/views/components/debug/macro.njk +3 -0
  155. package/src/server/plugins/engine/views/components/debug/template.njk +13 -0
  156. package/src/server/plugins/engine/views/components/details.html +6 -0
  157. package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
  158. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
  159. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
  160. package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
  161. package/src/server/plugins/engine/views/components/html.html +3 -0
  162. package/src/server/plugins/engine/views/components/insettext.html +7 -0
  163. package/src/server/plugins/engine/views/components/list.html +36 -0
  164. package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
  165. package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
  166. package/src/server/plugins/engine/views/components/numberfield.html +5 -0
  167. package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
  168. package/src/server/plugins/engine/views/components/selectfield.html +5 -0
  169. package/src/server/plugins/engine/views/components/service-banner/macro.njk +3 -0
  170. package/src/server/plugins/engine/views/components/service-banner/template.njk +20 -0
  171. package/src/server/plugins/engine/views/components/service-banner/template.test.js +43 -0
  172. package/src/server/plugins/engine/views/components/tag-env/macro.njk +3 -0
  173. package/src/server/plugins/engine/views/components/tag-env/template.njk +30 -0
  174. package/src/server/plugins/engine/views/components/tag-env/template.test.js +66 -0
  175. package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
  176. package/src/server/plugins/engine/views/components/textfield.html +5 -0
  177. package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
  178. package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
  179. package/src/server/plugins/engine/views/confirmation.html +19 -0
  180. package/src/server/plugins/engine/views/file-upload.html +45 -0
  181. package/src/server/plugins/engine/views/index.html +39 -0
  182. package/src/server/plugins/engine/views/item-delete.html +56 -0
  183. package/src/server/plugins/engine/views/layout.html +199 -0
  184. package/src/server/plugins/engine/views/partials/components.html +6 -0
  185. package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
  186. package/src/server/plugins/engine/views/partials/debug.html +44 -0
  187. package/src/server/plugins/engine/views/partials/form.html +15 -0
  188. package/src/server/plugins/engine/views/partials/heading.html +16 -0
  189. package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
  190. package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
  191. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
  192. package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
  193. package/src/server/plugins/engine/views/summary.html +50 -0
  194. package/src/server/plugins/errorPages.ts +58 -0
  195. package/src/server/plugins/nunjucks/context.js +88 -0
  196. package/src/server/plugins/nunjucks/context.test.js +142 -0
  197. package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
  198. package/src/server/plugins/nunjucks/environment.js +116 -0
  199. package/src/server/plugins/nunjucks/filters/answer.js +27 -0
  200. package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
  201. package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
  202. package/src/server/plugins/nunjucks/filters/field.js +28 -0
  203. package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
  204. package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
  205. package/src/server/plugins/nunjucks/filters/href.js +30 -0
  206. package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
  207. package/src/server/plugins/nunjucks/filters/index.js +8 -0
  208. package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
  209. package/src/server/plugins/nunjucks/filters/page.js +24 -0
  210. package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
  211. package/src/server/plugins/nunjucks/index.js +3 -0
  212. package/src/server/plugins/nunjucks/plugin.js +40 -0
  213. package/src/server/plugins/nunjucks/render.js +42 -0
  214. package/src/server/plugins/nunjucks/types.js +40 -0
  215. package/src/server/plugins/pulse.ts +11 -0
  216. package/src/server/plugins/router.ts +201 -0
  217. package/src/server/plugins/session.ts +28 -0
  218. package/src/server/routes/health.js +13 -0
  219. package/src/server/routes/health.test.js +35 -0
  220. package/src/server/routes/index.test.ts +125 -0
  221. package/src/server/routes/index.ts +2 -0
  222. package/src/server/routes/public.ts +47 -0
  223. package/src/server/routes/types.ts +48 -0
  224. package/src/server/schemas/index.ts +34 -0
  225. package/src/server/secure-context.js +43 -0
  226. package/src/server/services/cacheService.test.ts +277 -0
  227. package/src/server/services/cacheService.ts +138 -0
  228. package/src/server/services/httpService.test.js +491 -0
  229. package/src/server/services/httpService.ts +50 -0
  230. package/src/server/services/index.ts +1 -0
  231. package/src/server/types.ts +54 -0
  232. package/src/server/utils/notify.test.ts +37 -0
  233. package/src/server/utils/notify.ts +50 -0
  234. package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
  235. package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
  236. package/src/server/utils/utils.js +24 -0
  237. package/src/server/utils/utils.test.js +54 -0
  238. package/src/server/views/404.html +16 -0
  239. package/src/server/views/500.html +19 -0
  240. package/src/server/views/help/accessibility-statement.html +58 -0
  241. package/src/server/views/help/cookie-preferences.html +57 -0
  242. package/src/server/views/help/cookies.html +71 -0
  243. package/src/server/views/help/get-support.html +37 -0
  244. package/src/server/views/help/privacy-notice.html +68 -0
  245. package/src/server/views/help/terms-and-conditions.html +83 -0
  246. package/src/typings/hapi/index.d.ts +87 -0
  247. package/src/typings/hapi-tracing/index.d.ts +6 -0
  248. package/src/typings/index.d.ts +3 -0
  249. package/src/typings/joi/index.d.ts +22 -0
  250. package/.server/server/views/components/service-banner/template.test.js.map +0 -1
  251. package/.server/server/views/components/tag-env/template.test.js.map +0 -1
  252. /package/.server/server/{views → plugins/engine/views}/components/debug/macro.njk +0 -0
  253. /package/.server/server/{views → plugins/engine/views}/components/debug/template.njk +0 -0
  254. /package/.server/server/{views → plugins/engine/views}/components/service-banner/macro.njk +0 -0
  255. /package/.server/server/{views → plugins/engine/views}/components/service-banner/template.njk +0 -0
  256. /package/.server/server/{views → plugins/engine/views}/components/tag-env/macro.njk +0 -0
  257. /package/.server/server/{views → plugins/engine/views}/components/tag-env/template.njk +0 -0
  258. /package/.server/server/{views → plugins/engine/views}/confirmation.html +0 -0
  259. /package/.server/server/{views → plugins/engine/views}/layout.html +0 -0
  260. /package/.server/server/{views → plugins/engine/views}/summary.html +0 -0
@@ -0,0 +1,46 @@
1
+ import { config } from '~/src/config/index.js'
2
+ import { postJson } from '~/src/server/services/httpService.js'
3
+
4
+ const submissionUrl = config.get('submissionUrl')
5
+
6
+ /**
7
+ * Persist files by extending the time-to-live to 30 days
8
+ * @param {{fileId: string, initiatedRetrievalKey: string}[]} files - batch of files to persist
9
+ * @param {string} persistedRetrievalKey - final retrieval key when submitting
10
+ */
11
+ export async function persistFiles(files, persistedRetrievalKey) {
12
+ const postJsonByType = /** @type {typeof postJson<object>} */ (postJson)
13
+
14
+ const payload = {
15
+ files,
16
+ persistedRetrievalKey
17
+ }
18
+
19
+ const result = await postJsonByType(`${submissionUrl}/files/persist`, {
20
+ payload
21
+ })
22
+
23
+ return result
24
+ }
25
+
26
+ /**
27
+ * Submit form
28
+ * @param {SubmitPayload} data - submission data
29
+ */
30
+ export async function submit(data) {
31
+ const postJsonByType = /** @type {typeof postJson<SubmitResponsePayload>} */ (
32
+ postJson
33
+ )
34
+
35
+ const payload = data
36
+
37
+ const result = await postJsonByType(`${submissionUrl}/submit`, {
38
+ payload
39
+ })
40
+
41
+ return result.payload
42
+ }
43
+
44
+ /**
45
+ * @import { SubmitPayload, SubmitResponsePayload } from '@defra/forms-model'
46
+ */
@@ -0,0 +1,46 @@
1
+ import { formMetadataSchema } from '@defra/forms-model'
2
+
3
+ import { config } from '~/src/config/index.js'
4
+ import { FormStatus } from '~/src/server/routes/types.js'
5
+ import { getJson } from '~/src/server/services/httpService.js'
6
+
7
+ /**
8
+ * Retrieves a form definition from the form manager for a given slug
9
+ * @param {string} slug - the slug of the form
10
+ */
11
+ export async function getFormMetadata(slug) {
12
+ const getJsonByType = /** @type {typeof getJson<FormMetadata>} */ (getJson)
13
+
14
+ const { payload: metadata } = await getJsonByType(
15
+ `${config.get('managerUrl')}/forms/slug/${slug}`
16
+ )
17
+
18
+ // Run it through the schema to coerce dates
19
+ const result = formMetadataSchema.validate(metadata)
20
+
21
+ if (result.error) {
22
+ throw result.error
23
+ }
24
+
25
+ return result.value
26
+ }
27
+
28
+ /**
29
+ * Retrieves a form definition from the form manager for a given id
30
+ * @param {string} id - the id of the form
31
+ * @param {FormStatus} state - the state of the form
32
+ */
33
+ export async function getFormDefinition(id, state) {
34
+ const getJsonByType = /** @type {typeof getJson<FormDefinition>} */ (getJson)
35
+
36
+ const suffix = state === FormStatus.Draft ? `/${state}` : ''
37
+ const { payload: definition } = await getJsonByType(
38
+ `${config.get('managerUrl')}/forms/${id}/definition${suffix}`
39
+ )
40
+
41
+ return definition
42
+ }
43
+
44
+ /**
45
+ * @import { FormDefinition, FormMetadata } from '@defra/forms-model'
46
+ */
@@ -0,0 +1,90 @@
1
+ import { StatusCodes } from 'http-status-codes'
2
+
3
+ import {
4
+ getFormDefinition,
5
+ getFormMetadata
6
+ } from '~/src/server/plugins/engine/services/formsService.js'
7
+ import { FormStatus } from '~/src/server/routes/types.js'
8
+ import { getJson } from '~/src/server/services/httpService.js'
9
+ import * as fixtures from '~/test/fixtures/index.js'
10
+
11
+ const { MANAGER_URL } = process.env
12
+
13
+ jest.mock('~/src/server/services/httpService')
14
+
15
+ describe('Forms service', () => {
16
+ const { definition, metadata } = fixtures.form
17
+
18
+ describe('getFormMetadata', () => {
19
+ beforeEach(() => {
20
+ jest.mocked(getJson).mockResolvedValue({
21
+ res: /** @type {IncomingMessage} */ ({
22
+ statusCode: StatusCodes.OK
23
+ }),
24
+ payload: metadata
25
+ })
26
+ })
27
+
28
+ it('requests JSON via form slug', async () => {
29
+ await getFormMetadata(metadata.slug)
30
+
31
+ expect(getJson).toHaveBeenCalledWith(
32
+ `${MANAGER_URL}/forms/slug/${metadata.slug}`
33
+ )
34
+ })
35
+
36
+ it('coerces timestamps from string to Date', async () => {
37
+ const payload = {
38
+ ...structuredClone(metadata),
39
+
40
+ // JSON payload uses string dates in transit
41
+ createdAt: metadata.createdAt.toISOString(),
42
+ updatedAt: metadata.updatedAt.toISOString()
43
+ }
44
+
45
+ jest.mocked(getJson).mockResolvedValue({
46
+ res: /** @type {IncomingMessage} */ ({
47
+ statusCode: StatusCodes.OK
48
+ }),
49
+ payload
50
+ })
51
+
52
+ await expect(getFormMetadata(metadata.slug)).resolves.toEqual({
53
+ ...metadata,
54
+ createdAt: expect.any(Date),
55
+ updatedAt: expect.any(Date)
56
+ })
57
+ })
58
+ })
59
+
60
+ describe('getFormDefinition', () => {
61
+ beforeEach(() => {
62
+ jest.mocked(getJson).mockResolvedValue({
63
+ res: /** @type {IncomingMessage} */ ({
64
+ statusCode: StatusCodes.OK
65
+ }),
66
+ payload: definition
67
+ })
68
+ })
69
+
70
+ it('requests JSON via form ID (draft)', async () => {
71
+ await getFormDefinition(metadata.id, FormStatus.Draft)
72
+
73
+ expect(getJson).toHaveBeenCalledWith(
74
+ `${MANAGER_URL}/forms/${metadata.id}/definition/draft`
75
+ )
76
+ })
77
+
78
+ it('requests JSON via form ID (live)', async () => {
79
+ await getFormDefinition(metadata.id, FormStatus.Live)
80
+
81
+ expect(getJson).toHaveBeenCalledWith(
82
+ `${MANAGER_URL}/forms/${metadata.id}/definition`
83
+ )
84
+ })
85
+ })
86
+ })
87
+
88
+ /**
89
+ * @import { IncomingMessage } from 'node:http'
90
+ */
@@ -0,0 +1,3 @@
1
+ export * as formsService from '~/src/server/plugins/engine/services/formsService.js'
2
+ export * as formSubmissionService from '~/src/server/plugins/engine/services/formSubmissionService.js'
3
+ export * as outputService from '~/src/server/plugins/engine/services/notifyService.js'
@@ -0,0 +1,132 @@
1
+ import { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
2
+ import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
3
+ import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
4
+ import { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'
5
+ import { submit } from '~/src/server/plugins/engine/services/notifyService.js'
6
+ import {
7
+ FormStatus,
8
+ type FormRequestPayload
9
+ } from '~/src/server/routes/types.js'
10
+ import { sendNotification } from '~/src/server/utils/notify.js'
11
+
12
+ jest.mock('~/src/server/utils/notify')
13
+ jest.mock('~/src/server/plugins/engine/helpers')
14
+ jest.mock('~/src/server/plugins/engine/outputFormatters/index')
15
+
16
+ describe('notifyService', () => {
17
+ const submitResponse = {
18
+ message: 'Submit completed',
19
+ result: {
20
+ files: {
21
+ main: '00000000-0000-0000-0000-000000000000',
22
+ repeaters: {
23
+ pizza: '11111111-1111-1111-1111-111111111111'
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ const items: DetailItem[] = []
30
+
31
+ const mockRequest: FormRequestPayload = jest.mocked<FormRequestPayload>({
32
+ path: 'test',
33
+ logger: {
34
+ info: jest.fn()
35
+ }
36
+ } as unknown as FormRequestPayload)
37
+ let model: FormModel
38
+ const sendNotificationMock = jest.mocked(sendNotification)
39
+
40
+ beforeEach(() => {
41
+ jest.resetAllMocks()
42
+ })
43
+
44
+ it('creates a subject line for real forms', async () => {
45
+ model = {
46
+ name: 'foobar',
47
+ def: {
48
+ output: {
49
+ audience: 'human',
50
+ version: '1'
51
+ }
52
+ }
53
+ } as FormModel
54
+
55
+ jest.mocked(checkFormStatus).mockReturnValue({
56
+ isPreview: false,
57
+ state: FormStatus.Draft
58
+ })
59
+ jest.mocked(getFormatter).mockReturnValue(() => 'dummy-live')
60
+
61
+ await submit(mockRequest, model, 'test@defra.gov.uk', items, submitResponse)
62
+
63
+ expect(sendNotificationMock).toHaveBeenCalledWith(
64
+ expect.objectContaining({
65
+ personalisation: {
66
+ subject: `Form submission: foobar`,
67
+ body: 'dummy-live'
68
+ }
69
+ })
70
+ )
71
+ })
72
+
73
+ it('creates a subject line for preview forms', async () => {
74
+ model = {
75
+ name: 'foobar',
76
+ def: {
77
+ output: {
78
+ audience: 'human',
79
+ version: '1'
80
+ }
81
+ }
82
+ } as FormModel
83
+
84
+ jest.mocked(checkFormStatus).mockReturnValue({
85
+ isPreview: true,
86
+ state: FormStatus.Draft
87
+ })
88
+ jest.mocked(getFormatter).mockReturnValue(() => 'dummy-preview')
89
+
90
+ await submit(mockRequest, model, 'test@defra.gov.uk', items, submitResponse)
91
+
92
+ expect(sendNotificationMock).toHaveBeenCalledWith(
93
+ expect.objectContaining({
94
+ personalisation: {
95
+ subject: `TEST FORM SUBMISSION: foobar`,
96
+ body: 'dummy-preview'
97
+ }
98
+ })
99
+ )
100
+ })
101
+
102
+ it('base64 encodes form data when aimed at machines', async () => {
103
+ model = {
104
+ name: 'foobar',
105
+ def: {
106
+ output: {
107
+ audience: 'machine',
108
+ version: '1'
109
+ }
110
+ }
111
+ } as FormModel
112
+
113
+ jest.mocked(checkFormStatus).mockReturnValue({
114
+ isPreview: true,
115
+ state: FormStatus.Draft
116
+ })
117
+ jest
118
+ .mocked(getFormatter)
119
+ .mockReturnValue(() => 'dummy-preview " Hello world \' !@/')
120
+
121
+ await submit(mockRequest, model, 'test@defra.gov.uk', items, submitResponse)
122
+
123
+ expect(sendNotificationMock).toHaveBeenCalledWith(
124
+ expect.objectContaining({
125
+ personalisation: {
126
+ subject: `TEST FORM SUBMISSION: foobar`,
127
+ body: 'ZHVtbXktcHJldmlldyAiIEhlbGxvIHdvcmxkICcgIUAv'
128
+ }
129
+ })
130
+ )
131
+ })
132
+ })
@@ -0,0 +1,64 @@
1
+ import { type SubmitResponsePayload } from '@defra/forms-model'
2
+
3
+ import { config } from '~/src/config/index.js'
4
+ import { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers.js'
5
+ import { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
6
+ import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
7
+ import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
8
+ import { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'
9
+ import { type FormRequestPayload } from '~/src/server/routes/types.js'
10
+ import { sendNotification } from '~/src/server/utils/notify.js'
11
+
12
+ const templateId = config.get('notifyTemplateId')
13
+
14
+ export async function submit(
15
+ request: FormRequestPayload,
16
+ model: FormModel,
17
+ emailAddress: string,
18
+ items: DetailItem[],
19
+ submitResponse: SubmitResponsePayload
20
+ ) {
21
+ const logTags = ['submit', 'email']
22
+ const { path } = request
23
+ const formStatus = checkFormStatus(path)
24
+
25
+ // Get submission email personalisation
26
+ request.logger.info(logTags, 'Getting personalisation data')
27
+
28
+ const formName = escapeMarkdown(model.name)
29
+ const subject = formStatus.isPreview
30
+ ? `TEST FORM SUBMISSION: ${formName}`
31
+ : `Form submission: ${formName}`
32
+
33
+ const outputAudience = model.def.output?.audience ?? 'human'
34
+ const outputVersion = model.def.output?.version ?? '1'
35
+
36
+ const outputFormatter = getFormatter(outputAudience, outputVersion)
37
+ let body = outputFormatter(items, model, submitResponse, formStatus)
38
+
39
+ // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload
40
+ // This is logic specific to Notify, so we include the logic here rather than in the formatter
41
+ if (outputAudience === 'machine') {
42
+ body = Buffer.from(body).toString('base64')
43
+ }
44
+
45
+ request.logger.info(logTags, 'Sending email')
46
+
47
+ try {
48
+ // Send submission email
49
+ await sendNotification({
50
+ templateId,
51
+ emailAddress,
52
+ personalisation: {
53
+ subject,
54
+ body
55
+ }
56
+ })
57
+
58
+ request.logger.info(logTags, 'Email sent successfully')
59
+ } catch (err) {
60
+ request.logger.error(logTags, 'Error sending email', err)
61
+
62
+ throw err
63
+ }
64
+ }
@@ -0,0 +1,60 @@
1
+ import { config } from '~/src/config/index.js'
2
+ import { getJson, postJson } from '~/src/server/services/httpService.js'
3
+
4
+ const uploaderUrl = config.get('uploaderUrl')
5
+ const submissionUrl = config.get('submissionUrl')
6
+ const uploaderBucketName = config.get('uploaderBucketName')
7
+ const stagingPrefix = config.get('stagingPrefix')
8
+
9
+ /**
10
+ * Initiates a CDP file upload
11
+ * @param {string} path - the path of the page in the form
12
+ * @param {string} retrievalKey - the retrieval key for the files
13
+ * @param {string} [mimeTypes] - the csv string of accepted mimeTypes
14
+ */
15
+ export async function initiateUpload(path, retrievalKey, mimeTypes) {
16
+ const postJsonByType =
17
+ /** @type {typeof postJson<UploadInitiateResponse>} */ (postJson)
18
+
19
+ const payload = {
20
+ redirect: path,
21
+ callback: `${submissionUrl}/file`,
22
+ s3Bucket: uploaderBucketName,
23
+ s3Path: stagingPrefix,
24
+ metadata: {
25
+ retrievalKey
26
+ },
27
+ mimeTypes: mimeTypes
28
+ ?.split(',')
29
+ .map((type) => type.trim())
30
+ .filter((type) => type !== '')
31
+ // maxFileSize: 25 * 1000 * 1000
32
+ }
33
+
34
+ const { payload: initiate } = await postJsonByType(
35
+ `${uploaderUrl}/initiate`,
36
+ { payload }
37
+ )
38
+
39
+ return initiate
40
+ }
41
+
42
+ /**
43
+ * Get the status of a CDP file upload
44
+ * @param {string} uploadId - the ID of the upload
45
+ */
46
+ export async function getUploadStatus(uploadId) {
47
+ const getJsonByType = /** @type {typeof getJson<UploadStatusResponse>} */ (
48
+ getJson
49
+ )
50
+
51
+ const { payload: status } = await getJsonByType(
52
+ `${uploaderUrl}/status/${uploadId}`
53
+ )
54
+
55
+ return status
56
+ }
57
+
58
+ /**
59
+ * @import { UploadInitiateResponse, UploadStatusResponse } from '~/src/server/plugins/engine/types.js'
60
+ */