@defra/forms-engine-plugin 4.5.0 → 4.5.2
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/config/index.js +1 -1
- package/.server/config/index.js.map +1 -1
- 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/plugins/engine/beta/form-context.d.ts +0 -1
- package/.server/server/plugins/engine/beta/form-context.js +4 -3
- package/.server/server/plugins/engine/beta/form-context.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +2 -0
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +10 -8
- package/.server/server/plugins/engine/helpers.js +8 -23
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +2 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.js +2 -1
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.js +1 -2
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.test.js +0 -36
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/package.json +3 -3
- package/src/config/index.ts +1 -1
- package/src/server/constants.js +1 -0
- package/src/server/plugins/engine/beta/form-context.test.ts +22 -8
- package/src/server/plugins/engine/beta/form-context.ts +7 -6
- package/src/server/plugins/engine/components/FileUploadField.test.ts +21 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +1 -0
- package/src/server/plugins/engine/helpers.test.ts +0 -74
- package/src/server/plugins/engine/helpers.ts +17 -27
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +54 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +7 -5
- package/src/server/plugins/engine/routes/questions.ts +1 -1
- package/src/server/plugins/nunjucks/context.js +1 -3
- package/src/server/plugins/nunjucks/context.test.js +0 -37
- package/src/server/routes/dummy-api.test.ts +3 -1
|
@@ -24,7 +24,8 @@ async function handleHttpEvent(request, page, context, event, model, preparePage
|
|
|
24
24
|
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
25
25
|
const payload = format(context, items, model, undefined, undefined);
|
|
26
26
|
const opts = {
|
|
27
|
-
payload
|
|
27
|
+
payload,
|
|
28
|
+
timeout: 5000
|
|
28
29
|
};
|
|
29
30
|
if (preparePageEventRequestOptions) {
|
|
30
31
|
preparePageEventRequestOptions(opts, event, page, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","response","postJson","Object","assign","data","makeGetHandler","onRequest","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: AnyFormRequest,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts = { payload }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad?.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n }\n )\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (events?.onSave?.type === 'http' && isSuccessful(response)) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n }\n )\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","timeout","response","postJson","Object","assign","data","makeGetHandler","onRequest","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: AnyFormRequest,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts: httpService.RequestOptions = { payload, timeout: 5000 }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad?.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n }\n )\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (events?.onSave?.type === 'http' && isSuccessful(response)) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n }\n )\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -4,7 +4,7 @@ import Boom from '@hapi/boom';
|
|
|
4
4
|
import { StatusCodes } from 'http-status-codes';
|
|
5
5
|
import { config } from "../../../config/index.js";
|
|
6
6
|
import { createLogger } from "../../common/helpers/logging/logger.js";
|
|
7
|
-
import { checkFormStatus, encodeUrl
|
|
7
|
+
import { checkFormStatus, encodeUrl } from "../engine/helpers.js";
|
|
8
8
|
const logger = createLogger();
|
|
9
9
|
|
|
10
10
|
/** @type {Record<string, string> | undefined} */
|
|
@@ -43,7 +43,6 @@ export async function context(request) {
|
|
|
43
43
|
// take consumers props first so we can override it
|
|
44
44
|
...consumerViewContext,
|
|
45
45
|
baseLayoutPath: pluginStorage.baseLayoutPath,
|
|
46
|
-
crumb: safeGenerateCrumb(request),
|
|
47
46
|
currentPath: `${request.path}${request.url.search}`,
|
|
48
47
|
previewMode: isPreviewMode ? formState : undefined,
|
|
49
48
|
slug: isResponseOK ? params?.slug : undefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","
|
|
1
|
+
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {AnyFormRequest | null} request\n */\nexport async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n * @param {AnyFormRequest | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,YAAY,QAAQ,SAAS;AACtC,SAASC,QAAQ,EAAEC,IAAI,QAAQ,WAAW;AAE1C,OAAOC,IAAI,MAAM,YAAY;AAC7B,SAASC,WAAW,QAAQ,mBAAmB;AAE/C,SAASC,MAAM;AACf,SAASC,YAAY;AACrB,SACEC,eAAe,EACfC,SAAS;AAGX,MAAMC,MAAM,GAAGH,YAAY,CAAC,CAAC;;AAE7B;AACA,IAAII,eAAe;;AAEnB;AACA;AACA;AACA,OAAO,eAAeC,OAAOA,CAACC,OAAO,EAAE;EACrC,MAAM;IAAEC,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,IAAI,CAAC,CAAC;EAE1C,MAAM;IAAEG,SAAS,EAAEC,aAAa;IAAEC,KAAK,EAAEC;EAAU,CAAC,GAAGX,eAAe,CAACM,MAAM,CAAC;;EAE9E;EACA;EACA,MAAMM,YAAY,GAChB,CAAChB,IAAI,CAACiB,MAAM,CAACN,QAAQ,CAAC,IAAIA,QAAQ,EAAEO,UAAU,KAAKjB,WAAW,CAACkB,EAAE;EAEnE,MAAMC,aAAa,GAAGX,OAAO,EAAEY,MAAM,CAACC,OAAO,CAAC,qBAAqB,CAAC;EAEpE,IAAIC,mBAAmB,GAAG,CAAC,CAAC;EAE5B,IAAI,CAACH,aAAa,EAAE;IAClB,MAAMI,KAAK,CAAC,yCAAyC,CAAC;EACxD;EAEA,IAAI,CAACJ,aAAa,CAACK,cAAc,EAAE;IACjC,MAAMD,KAAK,CAAC,mDAAmD,CAAC;EAClE;EAEA,IAAI,OAAOJ,aAAa,CAACM,WAAW,KAAK,UAAU,EAAE;IACnDH,mBAAmB,GAAG,MAAMH,aAAa,CAACM,WAAW,CAACjB,OAAO,CAAC;EAChE;;EAEA;EACA,MAAMkB,GAAG,GAAG;IACV;IACA,GAAGJ,mBAAmB;IACtBE,cAAc,EAAEL,aAAa,CAACK,cAAc;IAC5CG,WAAW,EAAE,GAAGnB,OAAO,CAACoB,IAAI,GAAGpB,OAAO,CAACqB,GAAG,CAACC,MAAM,EAAE;IACnDC,WAAW,EAAEnB,aAAa,GAAGE,SAAS,GAAGkB,SAAS;IAClDC,IAAI,EAAElB,YAAY,GAAGN,MAAM,EAAEwB,IAAI,GAAGD;EACtC,CAAC;EAED,OAAON,GAAG;AACZ;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,cAAcA,CAACC,QAAQ,EAAE;EACvC,MAAMC,YAAY,GAAGtC,IAAI,CAACG,MAAM,CAACoC,GAAG,CAAC,WAAW,CAAC,EAAE,sBAAsB,CAAC;EAE1E,IAAI,CAAC/B,eAAe,EAAE;IACpB,IAAI;MACF;MACAA,eAAe,GAAGgC,IAAI,CAACC,KAAK,CAAC3C,YAAY,CAACwC,YAAY,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC,CAAC,MAAM;MACN/B,MAAM,CAACmC,IAAI,CACT,oCAAoC3C,QAAQ,CAACuC,YAAY,CAAC,6CAC5D,CAAC;IACH;EACF;EAEA,OAAO;IACLnC,MAAM,EAAE;MACNwC,cAAc,EAAExC,MAAM,CAACoC,GAAG,CAAC,gBAAgB,CAAC;MAC5CK,WAAW,EAAEzC,MAAM,CAACoC,GAAG,CAAC,aAAa,CAAC;MACtCM,YAAY,EAAEvC,SAAS,CAACH,MAAM,CAACoC,GAAG,CAAC,cAAc,CAAC,CAAC;MACnDO,QAAQ,EAAE3C,MAAM,CAACoC,GAAG,CAAC,UAAU,CAAC;MAChCQ,WAAW,EAAE5C,MAAM,CAACoC,GAAG,CAAC,aAAa,CAAC;MACtCS,cAAc,EAAE7C,MAAM,CAACoC,GAAG,CAAC,gBAAgB;IAC7C,CAAC;IACDU,SAAS,EAAE,SAAS;IACpBC,eAAe,EAAEA,CAACC,KAAK,GAAG,EAAE,KAAK;MAC/B,OAAO,IAAI3C,eAAe,GAAG2C,KAAK,CAAC,IAAIA,KAAK,EAAE;IAChD;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA","ignoreList":[]}
|
|
@@ -92,42 +92,6 @@ describe('Nunjucks context', () => {
|
|
|
92
92
|
expect(crumb).toBeUndefined();
|
|
93
93
|
expect(malformedRequest.server.plugins.crumb.generate).not.toHaveBeenCalled();
|
|
94
94
|
});
|
|
95
|
-
it('should generate crumb when state exists', async () => {
|
|
96
|
-
const mockCrumb = 'generated-crumb-value';
|
|
97
|
-
const validRequest = /** @type {FormRequest} */
|
|
98
|
-
/** @type {unknown} */{
|
|
99
|
-
server: {
|
|
100
|
-
plugins: {
|
|
101
|
-
crumb: {
|
|
102
|
-
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
103
|
-
},
|
|
104
|
-
'forms-engine-plugin': {
|
|
105
|
-
baseLayoutPath: 'randomValue'
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
plugins: {},
|
|
110
|
-
route: {
|
|
111
|
-
settings: {
|
|
112
|
-
plugins: {}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
path: '/test',
|
|
116
|
-
url: {
|
|
117
|
-
search: ''
|
|
118
|
-
},
|
|
119
|
-
state: {},
|
|
120
|
-
yar: {
|
|
121
|
-
flash: jest.fn().mockReturnValue([]),
|
|
122
|
-
commit: jest.fn()
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
const {
|
|
126
|
-
crumb
|
|
127
|
-
} = await context(validRequest);
|
|
128
|
-
expect(crumb).toBe(mockCrumb);
|
|
129
|
-
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(validRequest);
|
|
130
|
-
});
|
|
131
95
|
});
|
|
132
96
|
});
|
|
133
97
|
|
|
@@ -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","yar","flash","mockReturnValue","commit","toBeUndefined","not","toHaveBeenCalled"
|
|
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"],"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 } =\n await import('~/src/server/plugins/nunjucks/context.js')\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})\n\n/**\n * @import { FormRequest } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"blipp": "^4.0.2",
|
|
110
110
|
"btoa": "^1.2.1",
|
|
111
111
|
"chokidar": "3.6.0",
|
|
112
|
-
"convict": "^6.2.
|
|
112
|
+
"convict": "^6.2.5",
|
|
113
113
|
"date-fns": "^4.1.0",
|
|
114
114
|
"dotenv": "^17.2.3",
|
|
115
115
|
"expr-eval-fork": "^3.0.0",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
141
141
|
"@babel/preset-env": "^7.28.5",
|
|
142
142
|
"@babel/preset-typescript": "^7.28.5",
|
|
143
|
-
"@defra/docusaurus-theme-govuk": "^0.0.
|
|
143
|
+
"@defra/docusaurus-theme-govuk": "^0.0.13-alpha",
|
|
144
144
|
"@docusaurus/core": "^3.9.2",
|
|
145
145
|
"@docusaurus/plugin-content-docs": "^3.9.2",
|
|
146
146
|
"@easyops-cn/docusaurus-search-local": "^0.55.0",
|
package/src/config/index.ts
CHANGED
package/src/server/constants.js
CHANGED
|
@@ -44,7 +44,7 @@ jest.mock('../pageControllers/index.ts', () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
jest.mock('../helpers.ts', () => ({
|
|
47
|
-
|
|
47
|
+
...jest.requireActual('../helpers.ts'),
|
|
48
48
|
getCacheService: (...args: unknown[]) => mockGetCacheService(...args),
|
|
49
49
|
checkEmailAddressForLiveFormSubmission: (...args: unknown[]) =>
|
|
50
50
|
mockCheckEmailAddressForLiveFormSubmission(...args)
|
|
@@ -134,10 +134,17 @@ describe('getFormModel helper', () => {
|
|
|
134
134
|
class CustomController extends PageController {}
|
|
135
135
|
const controllers = { CustomController }
|
|
136
136
|
const metadata = {
|
|
137
|
-
id: 'form-meta-123'
|
|
138
|
-
|
|
137
|
+
id: 'form-meta-123'
|
|
138
|
+
}
|
|
139
|
+
const definition = {
|
|
140
|
+
pages: [{ path: '/start' }],
|
|
141
|
+
metadata: {
|
|
142
|
+
$$__formVersion: {
|
|
143
|
+
versionNumber: 17,
|
|
144
|
+
createdAt: new Date('2024-10-15T10:00:00Z')
|
|
145
|
+
}
|
|
146
|
+
}
|
|
139
147
|
}
|
|
140
|
-
const definition = { pages: [{ path: '/start' }] }
|
|
141
148
|
let formsService: FormsService
|
|
142
149
|
let services: Services
|
|
143
150
|
let formModelInstance: { id: string }
|
|
@@ -176,7 +183,7 @@ describe('getFormModel helper', () => {
|
|
|
176
183
|
definition,
|
|
177
184
|
{
|
|
178
185
|
basePath: slug,
|
|
179
|
-
versionNumber:
|
|
186
|
+
versionNumber: 17,
|
|
180
187
|
ordnanceSurveyApiKey: undefined,
|
|
181
188
|
formId: metadata.id
|
|
182
189
|
},
|
|
@@ -210,11 +217,18 @@ describe('getFormModel helper', () => {
|
|
|
210
217
|
|
|
211
218
|
describe('resolveFormModel helper', () => {
|
|
212
219
|
const slug = 'tb-origin'
|
|
213
|
-
const definition = {
|
|
220
|
+
const definition = {
|
|
221
|
+
pages: [],
|
|
222
|
+
metadata: {
|
|
223
|
+
$$__formVersion: {
|
|
224
|
+
versionNumber: 9,
|
|
225
|
+
createdAt: new Date('2024-10-15T10:00:00Z')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
214
229
|
const metadata = {
|
|
215
230
|
id: 'metadata-123',
|
|
216
231
|
live: { updatedAt: new Date('2024-10-15T10:00:00Z') },
|
|
217
|
-
versions: [{ versionNumber: 9 }],
|
|
218
232
|
notificationEmail: 'enrique.chase@defra.gov.uk'
|
|
219
233
|
}
|
|
220
234
|
let server: Request['server']
|
|
@@ -274,7 +288,7 @@ describe('resolveFormModel helper', () => {
|
|
|
274
288
|
definition,
|
|
275
289
|
expect.objectContaining({
|
|
276
290
|
basePath: 'forms/preview/live/tb-origin',
|
|
277
|
-
versionNumber:
|
|
291
|
+
versionNumber: 9,
|
|
278
292
|
ordnanceSurveyApiKey: 'os-api-key',
|
|
279
293
|
formId: metadata.id
|
|
280
294
|
}),
|
|
@@ -5,7 +5,8 @@ import { isEqual } from 'date-fns'
|
|
|
5
5
|
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
6
6
|
import {
|
|
7
7
|
checkEmailAddressForLiveFormSubmission,
|
|
8
|
-
getCacheService
|
|
8
|
+
getCacheService,
|
|
9
|
+
getFormVersion
|
|
9
10
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
10
11
|
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
11
12
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
@@ -27,7 +28,6 @@ export interface FormModelOptions {
|
|
|
27
28
|
services?: Services
|
|
28
29
|
controllers?: Record<string, typeof PageController>
|
|
29
30
|
basePath?: string
|
|
30
|
-
versionNumber?: number
|
|
31
31
|
ordnanceSurveyApiKey?: string
|
|
32
32
|
formId?: string
|
|
33
33
|
routePrefix?: string
|
|
@@ -53,8 +53,6 @@ export async function getFormModel(
|
|
|
53
53
|
const formState = resolveState(state)
|
|
54
54
|
|
|
55
55
|
const metadata = await formsService.getFormMetadata(slug)
|
|
56
|
-
const versionNumber =
|
|
57
|
-
options.versionNumber ?? metadata.versions?.[0]?.versionNumber
|
|
58
56
|
|
|
59
57
|
const definition = await formsService.getFormDefinition(
|
|
60
58
|
metadata.id,
|
|
@@ -67,6 +65,8 @@ export async function getFormModel(
|
|
|
67
65
|
)
|
|
68
66
|
}
|
|
69
67
|
|
|
68
|
+
const versionNumber = getFormVersion(definition)?.versionNumber
|
|
69
|
+
|
|
70
70
|
return new FormModel(
|
|
71
71
|
definition,
|
|
72
72
|
{
|
|
@@ -182,14 +182,15 @@ export async function resolveFormModel(
|
|
|
182
182
|
const routePrefix =
|
|
183
183
|
options.routePrefix ?? server.realm.modifiers.route.prefix
|
|
184
184
|
|
|
185
|
+
const versionNumber = getFormVersion(definition)?.versionNumber
|
|
186
|
+
|
|
185
187
|
const model = new FormModel(
|
|
186
188
|
definition,
|
|
187
189
|
{
|
|
188
190
|
basePath:
|
|
189
191
|
options.basePath ??
|
|
190
192
|
buildBasePath(routePrefix, slug, formState, isPreview),
|
|
191
|
-
versionNumber
|
|
192
|
-
options.versionNumber ?? metadata.versions?.[0]?.versionNumber,
|
|
193
|
+
versionNumber,
|
|
193
194
|
ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,
|
|
194
195
|
formId: options.formId ?? metadata.id
|
|
195
196
|
},
|
|
@@ -1029,6 +1029,27 @@ describe('FileUploadField', () => {
|
|
|
1029
1029
|
)
|
|
1030
1030
|
})
|
|
1031
1031
|
|
|
1032
|
+
it('should throw InvalidComponentStateError when persistFiles throws 404 Not Found', async () => {
|
|
1033
|
+
const notFoundError = Boom.notFound('File not found')
|
|
1034
|
+
mockPersistFiles.mockRejectedValue(notFoundError)
|
|
1035
|
+
|
|
1036
|
+
await expect(
|
|
1037
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1038
|
+
).rejects.toThrow(InvalidComponentStateError)
|
|
1039
|
+
|
|
1040
|
+
const error = await fileUploadField
|
|
1041
|
+
.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1042
|
+
.catch((e: unknown) => e)
|
|
1043
|
+
|
|
1044
|
+
expect(error).toBeInstanceOf(InvalidComponentStateError)
|
|
1045
|
+
expect((error as InvalidComponentStateError).component).toBe(
|
|
1046
|
+
fileUploadField
|
|
1047
|
+
)
|
|
1048
|
+
expect((error as InvalidComponentStateError).userMessage).toBe(
|
|
1049
|
+
'There was a problem with your uploaded files. Re-upload them before submitting the form again.'
|
|
1050
|
+
)
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1032
1053
|
it('should re-throw other Boom errors without wrapping', async () => {
|
|
1033
1054
|
const serverError = Boom.internal('Internal server error')
|
|
1034
1055
|
mockPersistFiles.mockRejectedValue(serverError)
|
|
@@ -339,6 +339,7 @@ export class FileUploadField extends FormComponent {
|
|
|
339
339
|
if (
|
|
340
340
|
Boom.isBoom(error) &&
|
|
341
341
|
(error.output.statusCode === 403 || // Forbidden - retrieval key invalid
|
|
342
|
+
error.output.statusCode === 404 || // Not Found - file not found
|
|
342
343
|
error.output.statusCode === 410) // Gone - file expired (took to long to submit, etc)
|
|
343
344
|
) {
|
|
344
345
|
// Failed to persist files. We can't recover from this, the only real way we can recover the submissions is
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
getExponentialBackoffDelay,
|
|
19
19
|
getPageHref,
|
|
20
20
|
proceed,
|
|
21
|
-
safeGenerateCrumb,
|
|
22
21
|
setPageTitles,
|
|
23
22
|
type GlobalScope
|
|
24
23
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
@@ -36,7 +35,6 @@ import {
|
|
|
36
35
|
import {
|
|
37
36
|
FormAction,
|
|
38
37
|
FormStatus,
|
|
39
|
-
type FormRequest,
|
|
40
38
|
type FormResponseToolkit
|
|
41
39
|
} from '~/src/server/routes/types.js'
|
|
42
40
|
import definition from '~/test/form/definitions/basic.js'
|
|
@@ -493,78 +491,6 @@ describe('Helpers', () => {
|
|
|
493
491
|
})
|
|
494
492
|
})
|
|
495
493
|
|
|
496
|
-
describe('safeGenerateCrumb', () => {
|
|
497
|
-
it('should return undefined when request.state is missing (malformed request)', () => {
|
|
498
|
-
const malformedRequest = {
|
|
499
|
-
server: {
|
|
500
|
-
plugins: {
|
|
501
|
-
crumb: {
|
|
502
|
-
generate: jest.fn()
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
plugins: {},
|
|
507
|
-
route: { settings: { plugins: {} } },
|
|
508
|
-
path: '/test',
|
|
509
|
-
url: { search: '' }
|
|
510
|
-
// state intentionally omitted
|
|
511
|
-
} as unknown as FormRequest
|
|
512
|
-
|
|
513
|
-
const crumbToken = safeGenerateCrumb(malformedRequest)
|
|
514
|
-
expect(crumbToken).toBeUndefined()
|
|
515
|
-
expect(
|
|
516
|
-
malformedRequest.server.plugins.crumb.generate
|
|
517
|
-
).not.toHaveBeenCalled()
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
it('should return undefined if crumb is disabled in route settings', () => {
|
|
521
|
-
const requestWithDisabledCrumb = {
|
|
522
|
-
server: {
|
|
523
|
-
plugins: {
|
|
524
|
-
crumb: {
|
|
525
|
-
generate: jest.fn().mockReturnValue('test-token')
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
plugins: {},
|
|
530
|
-
route: { settings: { plugins: { crumb: false } } },
|
|
531
|
-
path: '/test',
|
|
532
|
-
url: { search: '' },
|
|
533
|
-
state: {}
|
|
534
|
-
} as unknown as FormRequest
|
|
535
|
-
|
|
536
|
-
const crumbToken = safeGenerateCrumb(requestWithDisabledCrumb)
|
|
537
|
-
expect(crumbToken).toBeUndefined()
|
|
538
|
-
expect(
|
|
539
|
-
requestWithDisabledCrumb.server.plugins.crumb.generate
|
|
540
|
-
).not.toHaveBeenCalled()
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
it('should generate crumb when state exists and crumb plugin is available', () => {
|
|
544
|
-
const mockCrumb = 'generated-crumb-value'
|
|
545
|
-
const validRequest = {
|
|
546
|
-
server: {
|
|
547
|
-
plugins: {
|
|
548
|
-
crumb: {
|
|
549
|
-
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
|
-
plugins: {},
|
|
554
|
-
route: { settings: { plugins: {} } },
|
|
555
|
-
path: '/test',
|
|
556
|
-
url: { search: '' },
|
|
557
|
-
state: {}
|
|
558
|
-
} as unknown as FormRequest
|
|
559
|
-
|
|
560
|
-
const crumbToken = safeGenerateCrumb(validRequest)
|
|
561
|
-
expect(crumbToken).toBe(mockCrumb)
|
|
562
|
-
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(
|
|
563
|
-
validRequest
|
|
564
|
-
)
|
|
565
|
-
})
|
|
566
|
-
})
|
|
567
|
-
|
|
568
494
|
describe('getExponentialBackoffDelay', () => {
|
|
569
495
|
it.each([
|
|
570
496
|
{ depth: 1, expected: 2000 },
|
|
@@ -16,6 +16,7 @@ import { type Schema, type ValidationErrorItem } from 'joi'
|
|
|
16
16
|
import { Liquid } from 'liquidjs'
|
|
17
17
|
|
|
18
18
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
19
|
+
import { FORM_VERSION_METADATA_KEY } from '~/src/server/constants.js'
|
|
19
20
|
import {
|
|
20
21
|
getAnswer,
|
|
21
22
|
type Field
|
|
@@ -24,7 +25,6 @@ import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
|
24
25
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
25
26
|
import { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
26
27
|
import {
|
|
27
|
-
type AnyFormRequest,
|
|
28
28
|
type FormContext,
|
|
29
29
|
type FormContextRequest,
|
|
30
30
|
type FormSubmissionError
|
|
@@ -335,32 +335,6 @@ export function createError(componentName: string, message: string) {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
/**
|
|
339
|
-
* A small helper to safely generate a crumb token.
|
|
340
|
-
* Checks that the crumb plugin is available, that crumb
|
|
341
|
-
* is not disabled on the current route, and that cookies/state are present.
|
|
342
|
-
*/
|
|
343
|
-
export function safeGenerateCrumb(
|
|
344
|
-
request: AnyFormRequest | null
|
|
345
|
-
): string | undefined {
|
|
346
|
-
// no request or no .state
|
|
347
|
-
if (!request?.state) {
|
|
348
|
-
return undefined
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// crumb plugin or its generate method doesn't exist
|
|
352
|
-
if (!request.server.plugins.crumb.generate) {
|
|
353
|
-
return undefined
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// crumb is explicitly disabled for this route
|
|
357
|
-
if (request.route.settings.plugins?.crumb === false) {
|
|
358
|
-
return undefined
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return request.server.plugins.crumb.generate(request)
|
|
362
|
-
}
|
|
363
|
-
|
|
364
338
|
/**
|
|
365
339
|
* Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,
|
|
366
340
|
* using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).
|
|
@@ -416,6 +390,22 @@ export function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {
|
|
|
416
390
|
* If the page doesn't have a title, set it from the title of the first form component
|
|
417
391
|
* @param def - the form definition
|
|
418
392
|
*/
|
|
393
|
+
export interface FormVersionMetadata {
|
|
394
|
+
versionNumber: number
|
|
395
|
+
createdAt: Date
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Extracts form version metadata from a form definition
|
|
400
|
+
*/
|
|
401
|
+
export function getFormVersion(
|
|
402
|
+
definition: Pick<FormDefinition, 'metadata'>
|
|
403
|
+
): FormVersionMetadata | undefined {
|
|
404
|
+
return definition.metadata?.[FORM_VERSION_METADATA_KEY] as
|
|
405
|
+
| FormVersionMetadata
|
|
406
|
+
| undefined
|
|
407
|
+
}
|
|
408
|
+
|
|
419
409
|
export function setPageTitles(def: FormDefinition) {
|
|
420
410
|
def.pages.forEach((page) => {
|
|
421
411
|
if (!page.title) {
|