@defra/forms-engine-plugin 0.0.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.server/server/index.js +0 -4
  2. package/.server/server/index.js.map +1 -1
  3. package/.server/server/plugins/engine/helpers.js +3 -0
  4. package/.server/server/plugins/engine/helpers.js.map +1 -1
  5. package/.server/server/plugins/engine/index.js +27 -1
  6. package/.server/server/plugins/engine/index.js.map +1 -1
  7. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -4
  8. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  9. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +4 -10
  10. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  11. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +2 -3
  12. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  13. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -4
  14. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  15. package/.server/server/plugins/engine/plugin.js +65 -6
  16. package/.server/server/plugins/engine/plugin.js.map +1 -1
  17. package/.server/server/plugins/engine/types.js.map +1 -1
  18. package/.server/server/{views → plugins/engine/views}/components/service-banner/template.test.js +1 -1
  19. package/.server/server/plugins/engine/views/components/service-banner/template.test.js.map +1 -0
  20. package/.server/server/{views → plugins/engine/views}/components/tag-env/template.test.js +1 -1
  21. package/.server/server/plugins/engine/views/components/tag-env/template.test.js.map +1 -0
  22. package/.server/server/services/cacheService.js +5 -2
  23. package/.server/server/services/cacheService.js.map +1 -1
  24. package/.server/typings/hapi/index.d.js.map +1 -1
  25. package/README.md +283 -5
  26. package/package.json +1 -2
  27. package/src/server/index.ts +0 -5
  28. package/src/server/plugins/engine/helpers.ts +6 -1
  29. package/src/server/plugins/engine/index.ts +41 -1
  30. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +20 -12
  31. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +2 -1
  32. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +7 -3
  33. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +2 -1
  34. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +3 -2
  35. package/src/server/plugins/engine/plugin.ts +84 -4
  36. package/src/server/plugins/engine/types.ts +2 -0
  37. package/src/server/services/cacheService.test.ts +1 -0
  38. package/src/server/services/cacheService.ts +9 -2
  39. package/src/typings/hapi/index.d.ts +3 -11
  40. package/.server/server/views/components/service-banner/template.test.js.map +0 -1
  41. package/.server/server/views/components/tag-env/template.test.js.map +0 -1
  42. /package/.server/server/{views → plugins/engine/views}/components/debug/macro.njk +0 -0
  43. /package/.server/server/{views → plugins/engine/views}/components/debug/template.njk +0 -0
  44. /package/.server/server/{views → plugins/engine/views}/components/service-banner/macro.njk +0 -0
  45. /package/.server/server/{views → plugins/engine/views}/components/service-banner/template.njk +0 -0
  46. /package/.server/server/{views → plugins/engine/views}/components/tag-env/macro.njk +0 -0
  47. /package/.server/server/{views → plugins/engine/views}/components/tag-env/template.njk +0 -0
  48. /package/.server/server/{views → plugins/engine/views}/confirmation.html +0 -0
  49. /package/.server/server/{views → plugins/engine/views}/layout.html +0 -0
  50. /package/.server/server/{views → plugins/engine/views}/summary.html +0 -0
  51. /package/src/server/{views → plugins/engine/views}/components/debug/macro.njk +0 -0
  52. /package/src/server/{views → plugins/engine/views}/components/debug/template.njk +0 -0
  53. /package/src/server/{views → plugins/engine/views}/components/service-banner/macro.njk +0 -0
  54. /package/src/server/{views → plugins/engine/views}/components/service-banner/template.njk +0 -0
  55. /package/src/server/{views → plugins/engine/views}/components/service-banner/template.test.js +0 -0
  56. /package/src/server/{views → plugins/engine/views}/components/tag-env/macro.njk +0 -0
  57. /package/src/server/{views → plugins/engine/views}/components/tag-env/template.njk +0 -0
  58. /package/src/server/{views → plugins/engine/views}/components/tag-env/template.test.js +0 -0
  59. /package/src/server/{views → plugins/engine/views}/confirmation.html +0 -0
  60. /package/src/server/{views → plugins/engine/views}/layout.html +0 -0
  61. /package/src/server/{views → plugins/engine/views}/summary.html +0 -0
package/README.md CHANGED
@@ -1,21 +1,242 @@
1
- # forms-engine
1
+ # @defra/forms-engine-plugin
2
2
 
3
- Form hapi-plugin
3
+ The `@defra/forms-engine-plugin` is a [plugin](https://hapi.dev/tutorials/plugins/?lang=en_US) for [hapi](https://hapi.dev/) used to serve GOV.UK-based form journeys.
4
4
 
5
- ...
5
+ It is designed to be embedded in the frontend of a digital service and provide a convenient, configuration driven approach to building forms that are aligned to [GDS Design System](https://design-system.service.gov.uk/) guidelines.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Dependencies](#dependencies)
11
+ - [Setup](#setup)
12
+ - [Form Config](#form-config)
13
+ - [Static Assets and Styles](#static-assets-and-styles)
14
+ - [Example](#example)
15
+ - [Environment Variables](#environment-variables)
16
+ - [Options](#options)
17
+ - [Services](#services)
18
+ - [Custom Controllers](#custom-controllers)
19
+ - [Custom Filters](#custom-filters)
20
+ - [Custom Cache](#custom-cache)
21
+ - [Exemplar](#exemplar)
22
+ - [Templates](#templates)
23
+ - [Template Data](#template-data)
24
+ - [Liquid Filters](#liquid-filters)
25
+ - [Examples](#examples)
26
+ - [Templates and Views: Extending the Default Layout](#templates-and-views-extending-the-default-layout)
27
+ - [Publishing the Package](#publishing-the-package)
28
+ - [Semantic Versioning Control](#semantic-versioning-control)
29
+ - [Major-Version Release Branches](#major-version-release-branches)
30
+ - [Manual Workflow Triggers](#manual-workflow-triggers)
31
+ - [Workflow Triggers](#workflow-triggers)
32
+ - [Safety and Consistency](#safety-and-consistency)
33
+
34
+ ## Installation
35
+
36
+ `npm install @defra/forms-engine-plugin --save`
37
+
38
+ ## Dependencies
39
+
40
+ The following are [plugin dependencies](<https://hapi.dev/api/?v=21.4.0#server.dependency()>) that are required to be registered with hapi:
41
+
42
+ `npm install hapi-pino @hapi/crumb @hapi/yar @hapi/vision --save`
43
+
44
+ - [hapi-pino](https://github.com/hapijs/hapi-pino) - [Pino](https://github.com/pinojs/pino) logger for hapi
45
+ - [@hapi/crumb](https://github.com/hapijs/crumb) - CSRF crumb generation and validation
46
+ - [@hapi/yar](https://github.com/hapijs/yar) - Session manager
47
+ - [@hapi/vision](https://github.com/hapijs/vision) - Template rendering support
48
+
49
+ Additional npm dependencies that you will need are:
50
+
51
+ `npm install nunjucks govuk-frontend --save`
52
+
53
+ - [nunjucks](https://www.npmjs.com/package/nunjucks) - [templating engine](https://mozilla.github.io/nunjucks/) used by GOV.UK design system
54
+ - [govuk-frontend](https://www.npmjs.com/package/govuk-frontend) - [code](https://github.com/alphagov/govuk-frontend) you need to build a user interface for government platforms and services
55
+
56
+ Optional dependencies
57
+
58
+ `npm install @hapi/inert --save`
59
+
60
+ - [@hapi/inert](https://www.npmjs.com/package/@hapi/inert) - static file and directory handlers for serving GOV.UK assets and styles
61
+
62
+ ## Setup
63
+
64
+ ### Form config
65
+
66
+ The `form-engine-plugin` uses JSON configuration files to serve form journeys.
67
+ These files are called `Form definitions` and are built up of:
68
+
69
+ - `pages` - includes a `path`, `title`
70
+ - `components` - one or more questions on a page
71
+ - `conditions` - used to conditionally show and hide pages and
72
+ - `lists` - data used to in selection fields like [Select](https://design-system.service.gov.uk/components/select/), [Checkboxes](https://design-system.service.gov.uk/components/checkboxes/) and [Radios](https://design-system.service.gov.uk/components/radios/)
73
+
74
+ The [types](https://github.com/DEFRA/forms-designer/blob/main/model/src/form/form-definition/types.ts), `joi` [schema](https://github.com/DEFRA/forms-designer/blob/main/model/src/form/form-definition/index.ts) and the [examples](test/form/definitions) folder are a good place to learn about the structure of these files.
75
+
76
+ TODO - Link to wiki for `Form metadata`
77
+ TODO - Link to wiki for `Form definition`
78
+
79
+ #### Providing form config to the engine
80
+
81
+ The engine plugin registers several [routes](https://hapi.dev/tutorials/routing/?lang=en_US) on the hapi server.
82
+
83
+ They look like this:
84
+
85
+ ```
86
+ GET /{slug}/{path}
87
+ POST /{slug}/{path}
88
+ ```
89
+
90
+ A unique `slug` is used to route the user to the correct form, and the `path` used to identify the correct page within the form to show.
91
+ The [plugin registration options](#options) have a `services` setting to provide a `formsService` that is responsible for returning `form definition` data.
92
+
93
+ WARNING: This below is subject to change
94
+
95
+ A `formsService` has two methods, one for returning `formMetadata` and another to return `formDefinition`s.
96
+
97
+ ```
98
+ const formsService = {
99
+ getFormMetadata: async function (slug) {
100
+ // Returns the metadata for the slug
101
+ },
102
+ getFormDefinition: async function (id, state) {
103
+ // Returns the form definition for the given id
104
+ }
105
+ }
106
+ ```
107
+
108
+ The reason for the two separate methods is caching.
109
+ `formMetadata` is a lightweight record designed to give top level information about a form.
110
+ This method is invoked for every page request.
111
+
112
+ Only when the `formMetadata` indicates that the definition has changed is a call to `getFormDefinition` is made.
113
+ The response from this can be quite big as it contains the entire form definition.
114
+
115
+ See [example](#example) below for more detail
116
+
117
+ ### Static assets and styles
118
+
119
+ TODO
120
+
121
+ ## Example
122
+
123
+ ```
124
+ import hapi from '@hapi/hapi'
125
+ import yar from '@hapi/yar'
126
+ import crumb from '@hapi/crumb'
127
+ import inert from '@hapi/inert'
128
+ import pino from 'hapi-pino'
129
+ import plugin from '@defra/forms-engine-plugin'
130
+
131
+ const server = hapi.server({
132
+ port: 3000
133
+ })
134
+
135
+ // Register the dependent plugins
136
+ await server.register(pino)
137
+ await server.register(inert)
138
+ await server.register(crumb)
139
+ await server.register({
140
+ plugin: yar,
141
+ options: {
142
+ cookieOptions: {
143
+ password: 'ENTER_YOUR_SESSION_COOKIE_PASSWORD_HERE' // Must be > 32 chars
144
+ }
145
+ }
146
+ })
147
+
148
+ // Register the `forms-engine-plugin`
149
+ await server.register({
150
+ plugin
151
+ })
152
+
153
+ await server.start()
154
+ ```
155
+
156
+ ## Environment variables
157
+
158
+ ## Options
159
+
160
+ The forms plugin is configured with [registration options](https://hapi.dev/api/?v=21.4.0#plugins)
161
+
162
+ - `services` (optional) - object containing `formsService`, `formSubmissionService` and `outputService`
163
+ - `formsService` - used to load `formMetadata` and `formDefinition`
164
+ - `formSubmissionService` - used prepare the form during submission (ignore - subject to change)
165
+ - `outputService` - used to save the submission
166
+ - `controllers` (optional) - Object map of custom page controllers used to override the default. See [custom controllers](#custom-controllers)
167
+ - `filters` (optional) - A map of custom template filters to include
168
+ - `cacheName` (optional) - The cache name to use. Defaults to hapi's [default server cache]. Recommended for production. See [here]
169
+ (#custom-cache) for more details
170
+ - `pluginPath` (optional) - The location of the plugin (defaults to `node_modules/@defra/forms-engine-plugin`)
171
+
172
+ ### Services
173
+
174
+ TODO
175
+
176
+ ### Custom controllers
177
+
178
+ TODO
179
+
180
+ ### Custom filters
181
+
182
+ Use the `filter` plugin option to provide custom template filters.
183
+ Filters are available in both [nunjucks](https://mozilla.github.io/nunjucks/templating.html#filters) and [liquid](https://liquidjs.com/filters/overview.html) templates.
184
+
185
+ ```
186
+ const formatter = new Intl.NumberFormat('en-GB')
187
+
188
+ await server.register({
189
+ plugin,
190
+ options: {
191
+ filters: {
192
+ money: value => formatter.format(value),
193
+ upper: value => typeof value === 'string' ? value.toUpperCase() : value
194
+ }
195
+ }
196
+ })
197
+ ```
198
+
199
+ ### Custom cache
200
+
201
+ The plugin will use the [default server cache](https://hapi.dev/api/?v=21.4.0#-serveroptionscache) to store form answers on the server.
202
+ This is just an in-memory cache which is fine for development.
203
+
204
+ In production you should create a custom cache one of the available `@hapi/catbox` adapters.
205
+
206
+ E.g. [Redis](https://github.com/hapijs/catbox-redis)
207
+
208
+ ```
209
+ import { Engine as CatboxRedis } from '@hapi/catbox-redis'
210
+
211
+ const server = new Hapi.Server({
212
+ cache : [
213
+ {
214
+ name: 'my_cache',
215
+ provider: {
216
+ constructor: CatboxRedis,
217
+ options: {}
218
+ }
219
+ }
220
+ ]
221
+ })
222
+ ```
223
+
224
+ ## Exemplar
225
+
226
+ TODO: Link to CDP exemplar
6
227
 
7
228
  ## Templates
8
229
 
9
230
  The following elements support [LiquidJS templates](https://liquidjs.com/):
10
231
 
11
232
  - Page **title**
12
- - Form component **titles**
233
+ - Form component **title**
13
234
  - Support for fieldset legend text or label text
14
235
  - This includes when the title is used in **error messages**
15
236
  - Html (guidance) component **content**
16
237
  - Summary component **row** key title (check answers and repeater summary)
17
238
 
18
- ### Template data
239
+ ### Template Data
19
240
 
20
241
  The data the templates are evaluated against is the raw answers the user has provided up to the page they're currently on.
21
242
  For example, given a YesNoField component called `TKsWbP`, the template `{{ TKsWbP }}` would render "true" or "false" depending on how the user answered the question.
@@ -85,3 +306,60 @@ There are a number of `LiquidJS` filters available to you from within the templa
85
306
  }
86
307
  ]
87
308
  ```
309
+
310
+ ## Templates and views: Extending the default layout
311
+
312
+ TODO
313
+
314
+ To override the default page template, vision and nunjucks both need to be configured to search in the `forms-engine-plugin` views directory when looking for template files.
315
+
316
+ For vision this is done through the `path` [plugin option](https://github.com/hapijs/vision/blob/master/API.md#options)
317
+ For nunjucks it is configured through the environment [configure options](https://mozilla.github.io/nunjucks/api.html#configure).
318
+
319
+ The `forms-engine-plugin` path to add can be imported from:
320
+
321
+ `import { VIEW_PATH } from '@defra/forms-engine-plugin'`
322
+
323
+ Which can then be appended to the `node_modules` path `node_modules/@defra/forms-engine`.
324
+
325
+ The main template layout is `govuk-frontend`'s `template.njk` file, this also needs to be added to the `path`s that nunjucks can look in.
326
+
327
+ ## Publishing the Package
328
+
329
+ Our GitHub Actions workflow (`publish.yml`) is set up to make publishing a breeze, using semantic versioning and a variety of release strategies. Here's how you can make the most of it:
330
+
331
+ ### Semantic Versioning Control
332
+
333
+ - **Patch Versioning**: This happens automatically whenever you merge code changes into `main` or any release branch.
334
+ - **Minor and Major Bumps**: You can control these by making empty commits with specific hashtags:
335
+ - Use `#minor` for a minor version bump.
336
+ - Use `#major` for a major version bump.
337
+
338
+ **Example Commands**:
339
+
340
+ ```bash
341
+ git commit --allow-empty -m "chore(release): #minor" # Minor bump
342
+ git commit --allow-empty -m "chore(release): #major" # Major bump
343
+ ```
344
+
345
+ ### Major-Version Release Branches
346
+
347
+ - **Branch Naming**: Stick to `release/vX` (like `release/v1`, `release/v2`).
348
+ - **Independent Versioning**: Each branch has its own versioning and publishes to npm with a unique dist-tag (like `2x` for `release/v2`).
349
+
350
+ ### Manual Workflow Triggers
351
+
352
+ - **Customizable Options**: You can choose the type of version bump, specify custom npm tags, and use dry run mode for testing. Dry-run is enabled by default.
353
+ - **Special Releases**: Perfect for beta releases or when you need to publish outside the usual process.
354
+
355
+ ### Workflow Triggers
356
+
357
+ 1. **Standard Development Flow**: Merging PRs to `main` automatically triggers a patch bump and publishes with the `beta` tag.
358
+ 2. **Backporting**: Cherry-pick fixes to release branches for patch bumps with specific tags (like `2x`).
359
+ 3. **Version Bumps**: Use empty commits for minor or major bumps.
360
+ 4. **Manual Releases**: Trigger these manually for special cases like beta or release candidates.
361
+
362
+ ### Safety and Consistency
363
+
364
+ - **Build Process**: Every publishing scenario includes a full build to ensure everything is in top shape, except for files like tests and lint rules.
365
+ - **Tagging Safety**: We prevent overwriting the `beta` tag by enforcing custom tags for non-standard branches. The default is set to `beta`. For release branches, the tag will be picked up from the release branch itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "main": ".server/server/plugins/engine/index.js",
@@ -60,7 +60,6 @@
60
60
  "@hapi/vision": "^7.0.3",
61
61
  "@hapi/wreck": "^18.1.0",
62
62
  "@hapi/yar": "^11.0.2",
63
- "@hapipal/schmervice": "^3.0.0",
64
63
  "@types/humanize-duration": "^3.27.4",
65
64
  "accessible-autocomplete": "^3.0.1",
66
65
  "atob": "^2.1.2",
@@ -8,7 +8,6 @@ import hapi, {
8
8
  import inert from '@hapi/inert'
9
9
  import Scooter from '@hapi/scooter'
10
10
  import Wreck from '@hapi/wreck'
11
- import Schmervice from '@hapipal/schmervice'
12
11
  import blipp from 'blipp'
13
12
  import { ProxyAgent } from 'proxy-agent'
14
13
 
@@ -25,7 +24,6 @@ import pluginPulse from '~/src/server/plugins/pulse.js'
25
24
  import pluginRouter from '~/src/server/plugins/router.js'
26
25
  import pluginSession from '~/src/server/plugins/session.js'
27
26
  import { prepareSecureContext } from '~/src/server/secure-context.js'
28
- import { CacheService } from '~/src/server/services/index.js'
29
27
  import { type RouteConfig } from '~/src/server/types.js'
30
28
 
31
29
  const proxyAgent = new ProxyAgent()
@@ -94,9 +92,6 @@ export async function createServer(routeConfig?: RouteConfig) {
94
92
  await server.register(Scooter)
95
93
  await server.register(pluginBlankie)
96
94
  await server.register(pluginCrumb)
97
- await server.register(Schmervice)
98
-
99
- server.registerService(CacheService)
100
95
 
101
96
  server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {
102
97
  const { response } = request
@@ -5,7 +5,7 @@ import {
5
5
  type Page
6
6
  } from '@defra/forms-model'
7
7
  import Boom from '@hapi/boom'
8
- import { type ResponseToolkit } from '@hapi/hapi'
8
+ import { type ResponseToolkit, type Server } from '@hapi/hapi'
9
9
  import { format, parseISO } from 'date-fns'
10
10
  import { StatusCodes } from 'http-status-codes'
11
11
  import { type Schema, type ValidationErrorItem } from 'joi'
@@ -362,6 +362,7 @@ export function getExponentialBackoffDelay(depth: number): number {
362
362
  const delay = BASE_DELAY_MS * 2 ** (depth - 1)
363
363
  return Math.min(delay, CAP_DELAY_MS)
364
364
  }
365
+
365
366
  export function evaluateTemplate(
366
367
  template: string,
367
368
  context: FormContext
@@ -377,3 +378,7 @@ export function evaluateTemplate(
377
378
  globals
378
379
  })
379
380
  }
381
+
382
+ export function getCacheService(server: Server) {
383
+ return server.plugins['forms-engine-plugin'].cacheService
384
+ }
@@ -1,7 +1,47 @@
1
+ import { type Environment } from 'nunjucks'
2
+
3
+ import { engine } from '~/src/server/plugins/engine/helpers.js'
1
4
  import { plugin } from '~/src/server/plugins/engine/plugin.js'
5
+ import { type FilterFunction } from '~/src/server/plugins/engine/types.js'
6
+ import {
7
+ checkComponentTemplates,
8
+ checkErrorTemplates,
9
+ evaluate
10
+ } from '~/src/server/plugins/nunjucks/environment.js'
11
+ import * as filters from '~/src/server/plugins/nunjucks/filters/index.js'
2
12
 
3
13
  export { getPageHref } from '~/src/server/plugins/engine/helpers.js'
4
14
  export { configureEnginePlugin } from '~/src/server/plugins/engine/configureEnginePlugin.js'
5
- export { CacheService } from '~/src/server/services/index.js'
15
+ export { context } from '~/src/server/plugins/nunjucks/context.js'
16
+
17
+ const globals = {
18
+ checkComponentTemplates,
19
+ checkErrorTemplates,
20
+ evaluate
21
+ }
22
+
23
+ export const VIEW_PATH = 'src/server/plugins/engine/views'
24
+ export const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'
25
+
26
+ export const prepareNunjucksEnvironment = function (
27
+ env: Environment,
28
+ additionalFilters?: Record<string, FilterFunction>
29
+ ) {
30
+ for (const [name, nunjucksFilter] of Object.entries(filters)) {
31
+ env.addFilter(name, nunjucksFilter)
32
+ }
33
+
34
+ for (const [name, nunjucksGlobal] of Object.entries(globals)) {
35
+ env.addGlobal(name, nunjucksGlobal)
36
+ }
37
+
38
+ // Apply any additional filters to both the liquid and nunjucks engines
39
+ if (additionalFilters) {
40
+ for (const [name, filter] of Object.entries(additionalFilters)) {
41
+ env.addFilter(name, filter)
42
+ engine.registerFilter(name, filter)
43
+ }
44
+ }
45
+ }
6
46
 
7
47
  export default plugin
@@ -4,7 +4,10 @@ import { type ResponseToolkit } from '@hapi/hapi'
4
4
  import { type ValidationErrorItem, type ValidationResult } from 'joi'
5
5
 
6
6
  import { tempItemSchema } from '~/src/server/plugins/engine/components/FileUploadField.js'
7
- import { getError } from '~/src/server/plugins/engine/helpers.js'
7
+ import {
8
+ getCacheService,
9
+ getError
10
+ } from '~/src/server/plugins/engine/helpers.js'
8
11
  import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
9
12
  import {
10
13
  FileUploadPageController,
@@ -71,14 +74,18 @@ describe('FileUploadPageController', () => {
71
74
  trace: jest.fn(),
72
75
  level: 'info'
73
76
  },
74
- services: jest.fn().mockReturnValue({
75
- cacheService: {
76
- setFlash: jest.fn(),
77
- setState: jest
78
- .fn()
79
- .mockImplementation((req, updated) => Promise.resolve(updated))
77
+ server: {
78
+ plugins: {
79
+ 'forms-engine-plugin': {
80
+ cacheService: {
81
+ setFlash: jest.fn(),
82
+ setState: jest
83
+ .fn()
84
+ .mockImplementation((req, updated) => Promise.resolve(updated))
85
+ }
86
+ }
80
87
  }
81
- }),
88
+ },
82
89
  query: {}
83
90
  } as unknown as FormRequest
84
91
  })
@@ -316,7 +323,7 @@ describe('FileUploadPageController', () => {
316
323
  )
317
324
  initiateSpy.mockResolvedValue(state as never)
318
325
 
319
- const { cacheService } = request.services([])
326
+ const cacheService = getCacheService(request.server)
320
327
  await controller['checkUploadStatus'](request, state, 1)
321
328
 
322
329
  expect(cacheService.setFlash).toHaveBeenCalledWith(request, {
@@ -608,7 +615,8 @@ describe('FileUploadPageController', () => {
608
615
  Promise.resolve(Object.assign({}, s, { newUpload: true }))
609
616
  )
610
617
 
611
- const { cacheService } = request.services([])
618
+ const cacheService = getCacheService(request.server)
619
+
612
620
  await controller['checkUploadStatus'](request, state, 1)
613
621
 
614
622
  expect(cacheService.setFlash).toHaveBeenCalledWith(request, {
@@ -670,7 +678,7 @@ describe('FileUploadPageController', () => {
670
678
 
671
679
  initiateSpy.mockResolvedValue(state)
672
680
 
673
- const { cacheService } = request.services([])
681
+ const cacheService = getCacheService(request.server)
674
682
  await controller['checkUploadStatus'](request, state, 1)
675
683
 
676
684
  expect(cacheService.setFlash).toHaveBeenCalledWith(request, {
@@ -729,7 +737,7 @@ describe('FileUploadPageController', () => {
729
737
 
730
738
  initiateSpy.mockResolvedValue(state)
731
739
 
732
- const { cacheService } = request.services([])
740
+ const cacheService = getCacheService(request.server)
733
741
 
734
742
  await controller['checkUploadStatus'](request, state, 1)
735
743
 
@@ -9,6 +9,7 @@ import {
9
9
  type FileUploadField
10
10
  } from '~/src/server/plugins/engine/components/FileUploadField.js'
11
11
  import {
12
+ getCacheService,
12
13
  getError,
13
14
  getExponentialBackoffDelay
14
15
  } from '~/src/server/plugins/engine/helpers.js'
@@ -364,7 +365,7 @@ export class FileUploadPageController extends QuestionPageController {
364
365
  } else {
365
366
  // Flash the error message.
366
367
  const { fileUpload } = this
367
- const { cacheService } = request.services([])
368
+ const cacheService = getCacheService(request.server)
368
369
  const name = fileUpload.name
369
370
  const text = file.errorMessage ?? 'Unknown error'
370
371
  const errors: FormSubmissionError[] = [
@@ -15,6 +15,7 @@ import { ComponentCollection } from '~/src/server/plugins/engine/components/Comp
15
15
  import { optionalText } from '~/src/server/plugins/engine/components/constants.js'
16
16
  import { type BackLink } from '~/src/server/plugins/engine/components/types.js'
17
17
  import {
18
+ getCacheService,
18
19
  getErrors,
19
20
  normalisePath,
20
21
  proceed
@@ -298,7 +299,8 @@ export class QuestionPageController extends PageController {
298
299
  return {}
299
300
  }
300
301
 
301
- const { cacheService } = request.services([])
302
+ const cacheService = getCacheService(request.server)
303
+
302
304
  return cacheService.getState(request)
303
305
  }
304
306
 
@@ -313,7 +315,8 @@ export class QuestionPageController extends PageController {
313
315
  return state
314
316
  }
315
317
 
316
- const { cacheService } = request.services([])
318
+ const cacheService = getCacheService(request.server)
319
+
317
320
  return cacheService.setState(request, state)
318
321
  }
319
322
 
@@ -332,7 +335,8 @@ export class QuestionPageController extends PageController {
332
335
  return updated
333
336
  }
334
337
 
335
- const { cacheService } = request.services([])
338
+ const cacheService = getCacheService(request.server)
339
+
336
340
  return cacheService.setState(request, updated)
337
341
  }
338
342
 
@@ -1,6 +1,7 @@
1
1
  import { type PageStatus } from '@defra/forms-model'
2
2
  import { type ResponseToolkit } from '@hapi/hapi'
3
3
 
4
+ import { getCacheService } from '~/src/server/plugins/engine/helpers.js'
4
5
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
5
6
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
6
7
  import { type FormContext } from '~/src/server/plugins/engine/types.js'
@@ -26,7 +27,7 @@ export class StatusPageController extends QuestionPageController {
26
27
  ) => {
27
28
  const { viewModel, viewName } = this
28
29
 
29
- const { cacheService } = request.services([])
30
+ const cacheService = getCacheService(request.server)
30
31
  const confirmationState = await cacheService.getConfirmationState(request)
31
32
 
32
33
  // If there's no confirmation state, then
@@ -6,7 +6,8 @@ import { FileUploadField } from '~/src/server/plugins/engine/components/FileUplo
6
6
  import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
7
7
  import {
8
8
  checkEmailAddressForLiveFormSubmission,
9
- checkFormStatus
9
+ checkFormStatus,
10
+ getCacheService
10
11
  } from '~/src/server/plugins/engine/helpers.js'
11
12
  import {
12
13
  SummaryViewModel,
@@ -88,8 +89,8 @@ export class SummaryPageController extends QuestionPageController {
88
89
  const { model } = this
89
90
  const { params } = request
90
91
  const { state } = context
92
+ const cacheService = getCacheService(request.server)
91
93
 
92
- const { cacheService } = request.services([])
93
94
  const { formsService } = this.model.services
94
95
  const { getFormMetadata } = formsService
95
96