@defra/forms-engine-plugin 4.0.33 → 4.0.35
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/constants.d.ts +1 -0
- package/.server/server/constants.js +1 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +0 -1
- package/.server/server/forms/simple-form.yaml +64 -0
- package/.server/server/plugins/engine/beta/form-context.js +1 -2
- package/.server/server/plugins/engine/beta/form-context.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.d.ts +4 -3
- package/.server/server/plugins/engine/components/FileUploadField.js +38 -0
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.d.ts +9 -7
- package/.server/server/plugins/engine/components/FormComponent.js +3 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +5 -0
- package/.server/server/plugins/engine/helpers.js +7 -0
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/index.d.ts +2 -0
- package/.server/server/plugins/engine/index.js +2 -0
- package/.server/server/plugins/engine/index.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +4 -0
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +6 -2
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +8 -3
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +4 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +33 -35
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js +9 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +15 -0
- package/.server/server/plugins/engine/pageControllers/errors.js +25 -0
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -0
- package/.server/server/plugins/engine/pageControllers/helpers/state.d.ts +13 -1
- package/.server/server/plugins/engine/pageControllers/helpers/state.js +33 -0
- package/.server/server/plugins/engine/pageControllers/helpers/state.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.js +6 -0
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/plugins/engine/views/index.html +1 -1
- package/.server/server/plugins/nunjucks/context.test.js +9 -1
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/types.d.ts +4 -0
- package/.server/server/plugins/nunjucks/types.js +1 -0
- package/.server/server/plugins/nunjucks/types.js.map +1 -1
- package/.server/server/services/cacheService.d.ts +1 -0
- package/.server/server/services/cacheService.js +10 -0
- package/.server/server/services/cacheService.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +1 -1
- package/src/server/constants.js +1 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +0 -1
- package/src/server/forms/simple-form.yaml +64 -0
- package/src/server/plugins/engine/beta/form-context.test.ts +4 -3
- package/src/server/plugins/engine/beta/form-context.ts +4 -3
- package/src/server/plugins/engine/components/FileUploadField.test.ts +203 -2
- package/src/server/plugins/engine/components/FileUploadField.ts +61 -2
- package/src/server/plugins/engine/components/FormComponent.ts +17 -1
- package/src/server/plugins/engine/helpers.ts +8 -0
- package/src/server/plugins/engine/index.ts +3 -0
- package/src/server/plugins/engine/models/FormModel.ts +4 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +9 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +11 -4
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +12 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.test.ts +3 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +55 -46
- package/src/server/plugins/engine/pageControllers/__stubs__/request.ts +14 -4
- package/src/server/plugins/engine/pageControllers/errors.test.ts +63 -0
- package/src/server/plugins/engine/pageControllers/errors.ts +30 -0
- package/src/server/plugins/engine/pageControllers/helpers/state.test.ts +75 -1
- package/src/server/plugins/engine/pageControllers/helpers/state.ts +50 -1
- package/src/server/plugins/engine/services/localFormsService.js +7 -0
- package/src/server/plugins/engine/views/index.html +1 -1
- package/src/server/plugins/nunjucks/context.test.js +10 -2
- package/src/server/plugins/nunjucks/types.js +1 -0
- package/src/server/services/cacheService.ts +16 -0
- package/src/typings/hapi/index.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n await loader.addForm('src/server/forms/simple-form.yaml', {\n ...metadata,\n id: 'a1b2c3d4-e5f6-7890-abcd-ef0123456789',\n title: 'Simple Form',\n slug: 'simple-form'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,aAAa;IACpBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
|
|
@@ -79,6 +79,10 @@ describe('Nunjucks context', () => {
|
|
|
79
79
|
path: '/test',
|
|
80
80
|
url: {
|
|
81
81
|
search: ''
|
|
82
|
+
},
|
|
83
|
+
yar: {
|
|
84
|
+
flash: jest.fn().mockReturnValue([]),
|
|
85
|
+
commit: jest.fn()
|
|
82
86
|
}
|
|
83
87
|
// state intentionally omitted to test real malformed requests
|
|
84
88
|
};
|
|
@@ -112,7 +116,11 @@ describe('Nunjucks context', () => {
|
|
|
112
116
|
url: {
|
|
113
117
|
search: ''
|
|
114
118
|
},
|
|
115
|
-
state: {}
|
|
119
|
+
state: {},
|
|
120
|
+
yar: {
|
|
121
|
+
flash: jest.fn().mockReturnValue([]),
|
|
122
|
+
commit: jest.fn()
|
|
123
|
+
}
|
|
116
124
|
};
|
|
117
125
|
const {
|
|
118
126
|
crumb
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","toBeUndefined","not","toHaveBeenCalled","mockCrumb","validRequest","
|
|
1
|
+
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","yar","flash","mockReturnValue","commit","toBeUndefined","not","toHaveBeenCalled","mockCrumb","validRequest","state","toHaveBeenCalledWith"],"sources":["../../../../src/server/plugins/nunjucks/context.test.js"],"sourcesContent":["import { tmpdir } from 'node:os'\n\nimport {\n context,\n devtoolContext\n} from '~/src/server/plugins/nunjucks/context.js'\n\ndescribe('Nunjucks context', () => {\n beforeEach(() => jest.resetModules())\n\n describe('Asset path', () => {\n it(\"should include 'assetPath' for GOV.UK Frontend icons\", () => {\n const { assetPath } = devtoolContext(null)\n expect(assetPath).toBe('/assets')\n })\n })\n\n describe('Asset helper', () => {\n it(\"should locate 'assets-manifest.json' assets\", () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('example.scss')).toBe(\n '/stylesheets/example.xxxxxxx.min.css'\n )\n\n expect(getDxtAssetPath('example.mjs')).toBe(\n '/javascripts/example.xxxxxxx.min.js'\n )\n })\n\n it(\"should return path when 'assets-manifest.json' is missing\", async () => {\n await jest.isolateModulesAsync(async () => {\n const { config } = await import('~/src/config/index.js')\n\n // Import when isolated to avoid cache\n const { devtoolContext } = await import(\n '~/src/server/plugins/nunjucks/context.js'\n )\n\n // Update config for missing manifest\n config.set('publicDir', tmpdir())\n const { getDxtAssetPath } = devtoolContext(null)\n\n // Uses original paths when missing\n expect(getDxtAssetPath('example.scss')).toBe('/example.scss')\n expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs')\n })\n })\n\n it('should return path to unknown assets', () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('')).toBe('/')\n expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')\n expect(getDxtAssetPath('example.gif')).toBe('/example.gif')\n })\n })\n\n describe('Config', () => {\n it('should include environment, phase tag and service info', async () => {\n await expect(context(null)).rejects.toThrow(\n 'context called before plugin registered'\n )\n })\n })\n\n describe('Crumb', () => {\n it('should handle malformed requests with missing state', async () => {\n // While state should always exist in a valid Hapi request (it holds cookies),\n // we've seen malformed requests in production where it's missing\n const malformedRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn()\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n yar: {\n flash: jest.fn().mockReturnValue([]),\n commit: jest.fn()\n }\n // state intentionally omitted to test real malformed requests\n })\n )\n\n const { crumb } = await context(malformedRequest)\n expect(crumb).toBeUndefined()\n expect(\n malformedRequest.server.plugins.crumb.generate\n ).not.toHaveBeenCalled()\n })\n\n it('should generate crumb when state exists', async () => {\n const mockCrumb = 'generated-crumb-value'\n const validRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn().mockReturnValue(mockCrumb)\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n state: {},\n yar: {\n flash: jest.fn().mockReturnValue([]),\n commit: jest.fn()\n }\n })\n )\n\n const { crumb } = await context(validRequest)\n expect(crumb).toBe(mockCrumb)\n expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(\n validRequest\n )\n })\n })\n})\n\n/**\n * @import { FormRequest } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @property {string} [currentPath] - Current path
|
|
18
18
|
* @property {string} [previewMode] - Preview mode
|
|
19
19
|
* @property {string} [slug] - Form slug
|
|
20
|
+
* @property {string} [error] - Error message for temporary error messages (not related to form state)
|
|
20
21
|
* @property {FormContext} [context] - the current form context
|
|
21
22
|
*/
|
|
22
23
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} [baseLayoutPath] - Base layout path\n * @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token\n * @property {string} [cspNonce] - Content Security Policy (CSP) nonce\n * @property {string} [currentPath] - Current path\n * @property {string} [previewMode] - Preview mode\n * @property {string} [slug] - Form slug\n * @property {FormContext} [context] - the current form context\n */\n\n/**\n * @typedef NunjucksContext\n * @property {ViewContext} ctx - the current nunjucks view context\n */\n\n/**\n * @import { FormContext } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} [baseLayoutPath] - Base layout path\n * @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token\n * @property {string} [cspNonce] - Content Security Policy (CSP) nonce\n * @property {string} [currentPath] - Current path\n * @property {string} [previewMode] - Preview mode\n * @property {string} [slug] - Form slug\n * @property {string} [error] - Error message for temporary error messages (not related to form state)\n * @property {FormContext} [context] - the current form context\n */\n\n/**\n * @typedef NunjucksContext\n * @property {ViewContext} ctx - the current nunjucks view context\n */\n\n/**\n * @import { FormContext } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA","ignoreList":[]}
|
|
@@ -27,6 +27,7 @@ export declare class CacheService {
|
|
|
27
27
|
setFlash(request: AnyFormRequest, message: {
|
|
28
28
|
errors: FormSubmissionError[];
|
|
29
29
|
}): void;
|
|
30
|
+
resetComponentStates(request: AnyFormRequest, componentNames: string[]): Promise<FormSubmissionState>;
|
|
30
31
|
/**
|
|
31
32
|
* The key used to store user session data against.
|
|
32
33
|
* If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`
|
|
@@ -61,6 +61,16 @@ export class CacheService {
|
|
|
61
61
|
const key = this.Key(request);
|
|
62
62
|
request.yar.flash(key.id, message);
|
|
63
63
|
}
|
|
64
|
+
async resetComponentStates(request, componentNames) {
|
|
65
|
+
const state = await this.getState(request);
|
|
66
|
+
for (const componentName of componentNames) {
|
|
67
|
+
if (componentName in state) {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
69
|
+
delete state[componentName];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return this.setState(request, state);
|
|
73
|
+
}
|
|
64
74
|
|
|
65
75
|
/**
|
|
66
76
|
* The key used to store user session data against.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormConfirmationState,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<FormConfirmationState> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: FormConfirmationState\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0)\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","resetComponentStates","componentNames","componentName","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormConfirmationState,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<FormConfirmationState> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: FormConfirmationState\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0)\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n async resetComponentStates(\n request: AnyFormRequest,\n componentNames: string[]\n ) {\n const state = await this.getState(request)\n\n for (const componentName of componentNames) {\n if (componentName in state) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete state[componentName]\n }\n }\n\n return this.setState(request, state)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
package/src/server/constants.js
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Page events
|
|
3
|
+
engine: V2
|
|
4
|
+
schema: 2
|
|
5
|
+
startPage: '/summary'
|
|
6
|
+
pages:
|
|
7
|
+
- title: Your name
|
|
8
|
+
path: '/your-name'
|
|
9
|
+
components:
|
|
10
|
+
- type: TextField
|
|
11
|
+
title: What is your first name?
|
|
12
|
+
name: applicantFirstName
|
|
13
|
+
shortDescription: Your first name
|
|
14
|
+
hint: ''
|
|
15
|
+
options:
|
|
16
|
+
required: true
|
|
17
|
+
schema: {}
|
|
18
|
+
id: 1fb8e182-c709-4792-8f83-e01d8b1fee1a
|
|
19
|
+
- type: TextField
|
|
20
|
+
title: What is your last name?
|
|
21
|
+
name: applicantLastName
|
|
22
|
+
shortDescription: Your last name
|
|
23
|
+
hint: ''
|
|
24
|
+
options:
|
|
25
|
+
required: true
|
|
26
|
+
schema: {}
|
|
27
|
+
id: b68df7f1-d4f4-4c17-83c8-402f584906c9
|
|
28
|
+
next: []
|
|
29
|
+
id: 622a35ec-3795-418a-81f3-a45746959045
|
|
30
|
+
- title: Upload a copy of your passport
|
|
31
|
+
controller: FileUploadPageController
|
|
32
|
+
path: '/upload-passport'
|
|
33
|
+
components:
|
|
34
|
+
- type: FileUploadField
|
|
35
|
+
title: Please upload a copy of your passport
|
|
36
|
+
name: passportUpload
|
|
37
|
+
shortDescription: Upload passport
|
|
38
|
+
hint: ''
|
|
39
|
+
options:
|
|
40
|
+
required: true
|
|
41
|
+
schema: {}
|
|
42
|
+
id: 987c1234-56d7-89e0-1234-56789abcdef0
|
|
43
|
+
id: 23456789-0abc-def1-2345-67890abcdef1
|
|
44
|
+
- title: ''
|
|
45
|
+
path: '/date-of-birth'
|
|
46
|
+
components:
|
|
47
|
+
- type: DatePartsField
|
|
48
|
+
title: When is {{ applicantFirstName }} {{ applicantLastName }}'s birthday?
|
|
49
|
+
name: dateOfBirth
|
|
50
|
+
shortDescription: Your birthday
|
|
51
|
+
hint: ''
|
|
52
|
+
options:
|
|
53
|
+
required: true
|
|
54
|
+
schema: {}
|
|
55
|
+
id: '00738799-3489-4ab2-a57b-542eecb31bfa'
|
|
56
|
+
next: []
|
|
57
|
+
id: da0fbdb4-a2de-4650-be16-9ba552af135f
|
|
58
|
+
- id: 449a45f6-4541-4a46-91bd-8b8931b07b50
|
|
59
|
+
title: ''
|
|
60
|
+
path: '/summary'
|
|
61
|
+
controller: SummaryPageController
|
|
62
|
+
conditions: []
|
|
63
|
+
sections: []
|
|
64
|
+
lists: []
|
|
@@ -209,11 +209,12 @@ describe('getFormModel helper', () => {
|
|
|
209
209
|
|
|
210
210
|
describe('resolveFormModel helper', () => {
|
|
211
211
|
const slug = 'tb-origin'
|
|
212
|
-
const definition = { pages: []
|
|
212
|
+
const definition = { pages: [] }
|
|
213
213
|
const metadata = {
|
|
214
214
|
id: 'metadata-123',
|
|
215
215
|
live: { updatedAt: new Date('2024-10-15T10:00:00Z') },
|
|
216
|
-
versions: [{ versionNumber: 9 }]
|
|
216
|
+
versions: [{ versionNumber: 9 }],
|
|
217
|
+
notificationEmail: 'enrique.chase@defra.gov.uk'
|
|
217
218
|
}
|
|
218
219
|
let server: Request['server']
|
|
219
220
|
let formModelInstance: { id: string }
|
|
@@ -265,7 +266,7 @@ describe('resolveFormModel helper', () => {
|
|
|
265
266
|
expect(FormModel).toHaveBeenCalledTimes(2)
|
|
266
267
|
expect(mockFormsService.getFormDefinition).toHaveBeenCalledTimes(2)
|
|
267
268
|
expect(mockCheckEmailAddressForLiveFormSubmission).toHaveBeenCalledWith(
|
|
268
|
-
|
|
269
|
+
undefined,
|
|
269
270
|
true
|
|
270
271
|
)
|
|
271
272
|
expect(FormModel).toHaveBeenCalledWith(
|
|
@@ -174,9 +174,10 @@ export async function resolveFormModel(
|
|
|
174
174
|
)
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
checkEmailAddressForLiveFormSubmission(
|
|
178
|
+
metadata.notificationEmail,
|
|
179
|
+
isPreview
|
|
180
|
+
)
|
|
180
181
|
|
|
181
182
|
const routePrefix =
|
|
182
183
|
options.routePrefix ?? server.realm.modifiers.route.prefix
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ComponentType,
|
|
3
|
-
type FileUploadFieldComponent
|
|
3
|
+
type FileUploadFieldComponent,
|
|
4
|
+
type FormMetadata
|
|
4
5
|
} from '@defra/forms-model'
|
|
6
|
+
import Boom from '@hapi/boom'
|
|
5
7
|
|
|
6
8
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
FileUploadField,
|
|
11
|
+
tempItemSchema
|
|
12
|
+
} from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
8
13
|
import {
|
|
9
14
|
getAnswer,
|
|
10
15
|
type Field
|
|
11
16
|
} from '~/src/server/plugins/engine/components/helpers/components.js'
|
|
12
17
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
18
|
+
import { InvalidComponentStateError } from '~/src/server/plugins/engine/pageControllers/errors.js'
|
|
13
19
|
import {
|
|
14
20
|
createPage,
|
|
15
21
|
type PageControllerClass
|
|
16
22
|
} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
17
23
|
import { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
24
|
+
import { type Services } from '~/src/server/plugins/engine/types/index.js'
|
|
18
25
|
import {
|
|
19
26
|
FileStatus,
|
|
20
27
|
UploadStatus,
|
|
28
|
+
type FormContext,
|
|
21
29
|
type UploadState
|
|
22
30
|
} from '~/src/server/plugins/engine/types.js'
|
|
31
|
+
import { type FormRequestPayload } from '~/src/server/routes/types.js'
|
|
23
32
|
import definition from '~/test/form/definitions/file-upload-basic.js'
|
|
24
33
|
import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
|
|
25
34
|
|
|
@@ -828,4 +837,196 @@ describe('FileUploadField', () => {
|
|
|
828
837
|
)
|
|
829
838
|
})
|
|
830
839
|
})
|
|
840
|
+
|
|
841
|
+
describe('onSubmit', () => {
|
|
842
|
+
let fileUploadField: FileUploadField
|
|
843
|
+
let mockRequest: FormRequestPayload
|
|
844
|
+
let mockMetadata: FormMetadata
|
|
845
|
+
let mockContext: FormContext
|
|
846
|
+
let mockPersistFiles: jest.Mock
|
|
847
|
+
|
|
848
|
+
beforeEach(() => {
|
|
849
|
+
// Create a FileUploadField instance
|
|
850
|
+
const componentDef: FileUploadFieldComponent = {
|
|
851
|
+
name: 'fileUpload',
|
|
852
|
+
title: 'Upload something',
|
|
853
|
+
type: ComponentType.FileUploadField,
|
|
854
|
+
options: {},
|
|
855
|
+
schema: {}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const page = model.pages.find((p) => p.path === '/file-upload-component')
|
|
859
|
+
fileUploadField = new FileUploadField(componentDef, {
|
|
860
|
+
model,
|
|
861
|
+
page
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
// Mock persistFiles
|
|
865
|
+
mockPersistFiles = jest.fn().mockResolvedValue(undefined)
|
|
866
|
+
|
|
867
|
+
// Mock request
|
|
868
|
+
mockRequest = {
|
|
869
|
+
app: {
|
|
870
|
+
model: {
|
|
871
|
+
services: {
|
|
872
|
+
formSubmissionService: {
|
|
873
|
+
persistFiles: mockPersistFiles
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
} as unknown as FormRequestPayload
|
|
879
|
+
|
|
880
|
+
// Mock metadata
|
|
881
|
+
mockMetadata = {
|
|
882
|
+
notificationEmail: 'test@example.com'
|
|
883
|
+
} as FormMetadata
|
|
884
|
+
|
|
885
|
+
// Mock context with state
|
|
886
|
+
mockContext = {
|
|
887
|
+
state: {
|
|
888
|
+
fileUpload: validState
|
|
889
|
+
}
|
|
890
|
+
} as unknown as FormContext
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
afterEach(() => {
|
|
894
|
+
jest.clearAllMocks()
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
it('should successfully persist files', async () => {
|
|
898
|
+
await fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
899
|
+
|
|
900
|
+
expect(mockPersistFiles).toHaveBeenCalledTimes(1)
|
|
901
|
+
expect(mockPersistFiles).toHaveBeenCalledWith(
|
|
902
|
+
[
|
|
903
|
+
{
|
|
904
|
+
fileId: 'fcb4f0f8-6862-4836-86dc-f56ff900b0ff',
|
|
905
|
+
initiatedRetrievalKey: 'enrique.chase@defra.gov.uk'
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
fileId: 'e1d6cf98-35a7-4f97-8a28-cdd2b115d8fa',
|
|
909
|
+
initiatedRetrievalKey: 'enrique.chase@defra.gov.uk'
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
fileId: '71fb359c-dee7-4c2e-8701-239eb892765a',
|
|
913
|
+
initiatedRetrievalKey: 'enrique.chase@defra.gov.uk'
|
|
914
|
+
}
|
|
915
|
+
],
|
|
916
|
+
'test@example.com'
|
|
917
|
+
)
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
it('should fail when notificationEmail is not set', async () => {
|
|
921
|
+
mockMetadata.notificationEmail = undefined
|
|
922
|
+
|
|
923
|
+
await expect(
|
|
924
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
925
|
+
).rejects.toThrow('Unexpected missing notificationEmail in metadata')
|
|
926
|
+
})
|
|
927
|
+
|
|
928
|
+
it('should fail when notificationEmail is empty string', async () => {
|
|
929
|
+
mockMetadata.notificationEmail = ''
|
|
930
|
+
|
|
931
|
+
await expect(
|
|
932
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
933
|
+
).rejects.toThrow('Unexpected missing notificationEmail in metadata')
|
|
934
|
+
})
|
|
935
|
+
|
|
936
|
+
it('should not call persistFiles when no files in state', async () => {
|
|
937
|
+
mockContext.state = {}
|
|
938
|
+
|
|
939
|
+
await fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
940
|
+
|
|
941
|
+
expect(mockPersistFiles).not.toHaveBeenCalled()
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
it('should not call persistFiles when empty array in state', async () => {
|
|
945
|
+
mockContext.state = { fileUpload: [] }
|
|
946
|
+
|
|
947
|
+
await fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
948
|
+
|
|
949
|
+
expect(mockPersistFiles).not.toHaveBeenCalled()
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
it('should throw Error when formSubmissionService is not available', async () => {
|
|
953
|
+
if (!mockRequest.app.model) {
|
|
954
|
+
throw new Error('Invalid test setup')
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
mockRequest.app.model.services = {} as unknown as Services
|
|
958
|
+
|
|
959
|
+
await expect(
|
|
960
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
961
|
+
).rejects.toThrow('No form submission service available in app model')
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it('should throw InvalidComponentStateError when persistFiles throws 403 Forbidden', async () => {
|
|
965
|
+
const forbiddenError = Boom.forbidden('Invalid retrieval key')
|
|
966
|
+
mockPersistFiles.mockRejectedValue(forbiddenError)
|
|
967
|
+
|
|
968
|
+
await expect(
|
|
969
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
970
|
+
).rejects.toThrow(InvalidComponentStateError)
|
|
971
|
+
|
|
972
|
+
const error = await fileUploadField
|
|
973
|
+
.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
974
|
+
.catch((e: unknown) => e)
|
|
975
|
+
|
|
976
|
+
expect(error).toBeInstanceOf(InvalidComponentStateError)
|
|
977
|
+
expect((error as InvalidComponentStateError).component).toBe(
|
|
978
|
+
fileUploadField
|
|
979
|
+
)
|
|
980
|
+
expect((error as InvalidComponentStateError).userMessage).toBe(
|
|
981
|
+
'There was a problem with your uploaded files. Re-upload them before submitting the form again.'
|
|
982
|
+
)
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
it('should throw InvalidComponentStateError when persistFiles throws 410 Gone', async () => {
|
|
986
|
+
const goneError = Boom.resourceGone('File has expired')
|
|
987
|
+
mockPersistFiles.mockRejectedValue(goneError)
|
|
988
|
+
|
|
989
|
+
await expect(
|
|
990
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
991
|
+
).rejects.toThrow(InvalidComponentStateError)
|
|
992
|
+
|
|
993
|
+
const error = await fileUploadField
|
|
994
|
+
.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
995
|
+
.catch((e: unknown) => e)
|
|
996
|
+
|
|
997
|
+
expect(error).toBeInstanceOf(InvalidComponentStateError)
|
|
998
|
+
expect((error as InvalidComponentStateError).component).toBe(
|
|
999
|
+
fileUploadField
|
|
1000
|
+
)
|
|
1001
|
+
expect((error as InvalidComponentStateError).userMessage).toBe(
|
|
1002
|
+
'There was a problem with your uploaded files. Re-upload them before submitting the form again.'
|
|
1003
|
+
)
|
|
1004
|
+
})
|
|
1005
|
+
|
|
1006
|
+
it('should re-throw other Boom errors without wrapping', async () => {
|
|
1007
|
+
const serverError = Boom.internal('Internal server error')
|
|
1008
|
+
mockPersistFiles.mockRejectedValue(serverError)
|
|
1009
|
+
|
|
1010
|
+
await expect(
|
|
1011
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1012
|
+
).rejects.toThrow(serverError)
|
|
1013
|
+
|
|
1014
|
+
await expect(
|
|
1015
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1016
|
+
).rejects.not.toThrow(InvalidComponentStateError)
|
|
1017
|
+
})
|
|
1018
|
+
|
|
1019
|
+
it('should re-throw non-Boom errors without wrapping', async () => {
|
|
1020
|
+
const genericError = new Error('Something went wrong')
|
|
1021
|
+
mockPersistFiles.mockRejectedValue(genericError)
|
|
1022
|
+
|
|
1023
|
+
await expect(
|
|
1024
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1025
|
+
).rejects.toThrow(genericError)
|
|
1026
|
+
|
|
1027
|
+
await expect(
|
|
1028
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1029
|
+
).rejects.not.toThrow(InvalidComponentStateError)
|
|
1030
|
+
})
|
|
1031
|
+
})
|
|
831
1032
|
})
|