Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/server/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ describe('Model cache', () => {
const mockYar = {
flash: mockFlash
}
const mockRequest = /** @type {any} */ {
const mockRequest = {
params: {
slug: 'test-form'
},
Expand All @@ -511,6 +511,7 @@ describe('Model cache', () => {
expect(pluginObject).toBeDefined()
const saveAndExitFunc = pluginObject.options.saveAndExit
expect(saveAndExitFunc).toBeDefined()
// @ts-expect-error - partial mock objects for tests
saveAndExitFunc(mockRequest, mockH, undefined)
expect(mockFlash).toHaveBeenCalledWith(
'SAVE_AND_EXIT_PAYLOAD',
Expand Down
14 changes: 9 additions & 5 deletions src/server/messaging/formAdapterEventPublisher.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('formAdapterEventPublisher', () => {
/** @type {FormAdapterSubmissionMessagePayload} */
let mockPayload

/** @type {any} */
/** @type {{ send: jest.Mock }} */
let mockSnsClient

beforeEach(() => {
Expand Down Expand Up @@ -59,10 +59,14 @@ describe('formAdapterEventPublisher', () => {
}
})

mockSnsClient = {
send: jest.fn()
}
jest.mocked(getSNSClient).mockReturnValue(mockSnsClient)
mockSnsClient = { send: jest.fn() }
jest
.mocked(getSNSClient)
.mockReturnValue(
/** @type {import('@aws-sdk/client-sns').SNSClient} */ (
/** @type {unknown} */ (mockSnsClient)
)
)
})

describe('publishFormAdapterEvent', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/server/messaging/formAdapterEventPublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const snsAdapterTopicArn = config.get('snsAdapterTopicArn')
* Validate form adapter submission payload against schema
* @param submissionPayload - Form submission payload to validate
* @returns Validated payload
* @throws Error if validation fails
* @throws {Error} if validation fails
*/
function validateFormAdapterPayload(
submissionPayload: FormAdapterSubmissionMessagePayload
Expand Down
9 changes: 9 additions & 0 deletions src/server/models/FeedbackPageViewModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type FormPageViewModel } from '@defra/forms-engine-plugin/types'

/**
* Extends {@link FormPageViewModel} with template variables specific to the feedback page.
*/
export interface FeedbackPageViewModel extends FormPageViewModel {
hidePhaseBanner?: boolean
submitButtonText: string
}
10 changes: 10 additions & 0 deletions src/server/models/SummaryViewModelWithEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type SummaryViewModel } from '@defra/forms-engine-plugin/engine/models/SummaryViewModel.js'
import { type GovukField } from '@defra/forms-model'

/**
* Extends {@link SummaryViewModel} with an optional confirmation email field
* rendered on the summary page when the user opts in to receive a confirmation email.
*/
export interface SummaryViewModelWithEmail extends SummaryViewModel {
userConfirmationEmailField?: GovukField
}
14 changes: 8 additions & 6 deletions src/server/plugins/FeedbackPageController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ describe('FeedbackPageController', () => {
const response = {
code: jest.fn().mockImplementation(() => response)
}
const h: FormResponseToolkit = {
const h = {
redirect: jest.fn().mockReturnValue(response),
view: jest.fn()
}
view: jest.fn(),
continue: Symbol('continue')
} as unknown as FormResponseToolkit

beforeEach(() => {
model = new FormModel(definition, {
Expand Down Expand Up @@ -70,11 +71,12 @@ describe('FeedbackPageController', () => {
errors: [
{
name: 'PMPyjg',
path: '/feedback',
text: 'Select how you feel about this service'
path: ['feedback'],
text: 'Select how you feel about this service',
href: '#PMPyjg'
}
]
} as FormContext
} as unknown as FormContext

jest
.spyOn(controller as unknown as QuestionPageController, 'getState')
Expand Down
16 changes: 9 additions & 7 deletions src/server/plugins/FeedbackPageController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import {
type FormContextRequest
} from '@defra/forms-engine-plugin/engine/types.js'
import {
type FormPageViewModel,
type FormRequestPayload,
type FormResponseToolkit
} from '@defra/forms-engine-plugin/types'

import { type FeedbackPageViewModel } from '~/src/server/models/FeedbackPageViewModel.js'

export class FeedbackPageController extends QuestionPageController {
allowSaveAndExit = false

getViewModel(
request: FormContextRequest,
context: FormContext
): FormPageViewModel {
): FeedbackPageViewModel {
const viewModel = super.getViewModel(request, context)
return {
...viewModel,
hidePhaseBanner: true,
submitButtonText: 'Send feedback',
name: context.state.formName
name: context.state.formName as string | undefined
}
}

Expand Down Expand Up @@ -63,10 +64,11 @@ export class FeedbackPageController extends QuestionPageController {
// Save state
await this.setState(request, state)

const summary = new SummaryPageController(
model,
context.pageMap.get(context.paths[0])
)
const pageController = context.pageMap.get(context.paths[0])
if (!pageController) {
throw new Error('Summary page controller not found')
}
const summary = new SummaryPageController(model, pageController.pageDef)
return summary.handleFormSubmit(request, context, h)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ describe('SummaryPageWithConfirmationEmailController', () => {
const response = {
code: jest.fn().mockImplementation(() => response)
}
const h: FormResponseToolkit = {
const h = {
redirect: jest.fn().mockReturnValue(response),
view: jest.fn()
}
view: jest.fn(),
continue: Symbol('continue')
} as unknown as FormResponseToolkit

beforeEach(() => {
model = new FormModel(definition, {
Expand Down
12 changes: 8 additions & 4 deletions src/server/plugins/SummaryPageWithConfirmationEmailController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type PageController } from '@defra/forms-engine-plugin/controllers/PageController.js'
import { type QuestionPageController } from '@defra/forms-engine-plugin/controllers/QuestionPageController.js'
import { SummaryPageController } from '@defra/forms-engine-plugin/controllers/SummaryPageController.js'
import { type SummaryViewModel } from '@defra/forms-engine-plugin/engine/models/SummaryViewModel.js'
import {
type FormContext,
type FormContextRequest,
Expand All @@ -17,6 +16,8 @@ import {
import { type GovukField } from '@defra/forms-model'
import Joi from 'joi'

import { type SummaryViewModelWithEmail } from '~/src/server/models/SummaryViewModelWithEmail.js'

export const CONFIRMATION_EMAIL_FIELD_NAME = 'userConfirmationEmailAddress'

const schema = Joi.object().keys({
Expand All @@ -33,9 +34,12 @@ export class SummaryPageWithConfirmationEmailController extends SummaryPageContr
getSummaryViewModel(
request: FormContextRequest,
context: FormContext
): SummaryViewModel {
const viewModel = super.getSummaryViewModel(request, context)
const payerEmail = viewModel?.paymentState?.payerEmail
): SummaryViewModelWithEmail {
const viewModel = super.getSummaryViewModel(
request,
context
) as SummaryViewModelWithEmail
const payerEmail = viewModel.paymentState?.payerEmail
// Fill in user confirmation email, if supplied from payment journey
if (payerEmail) {
const payload = (request.payload ?? {}) as FormPayload
Expand Down
4 changes: 2 additions & 2 deletions src/server/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default {
options
})

server.route<{ Params: { slug: string } }>({
server.route({
method: 'get',
path: '/help/cookies/{slug}',
async handler(request, h) {
Expand All @@ -171,7 +171,7 @@ export default {

const state = await cacheService.getState(request)

const formId = state?.formId ?? ''
const formId = (state.formId ?? '') as string

return h.view('help/cookies', {
googleAnalyticsContainerId: config
Expand Down
3 changes: 2 additions & 1 deletion src/server/routes/save-and-exit-with-cache.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getCacheService } from '@defra/forms-engine-plugin/engine/helpers.js'
import { FormStatus } from '@defra/forms-model'
import { StatusCodes } from 'http-status-codes'

import { createServer } from '~/src/server/index.js'
Expand Down Expand Up @@ -82,7 +83,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
id: FORM_ID,
status: 'draft',
status: FormStatus.Draft,
isPreview: true
}
})
Expand Down
28 changes: 21 additions & 7 deletions src/server/routes/save-and-exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export default [
// (in case the current page wasn't yet validated and saved).
// The current page state may be invalid so we don't want to push into the cache as normal properties.
const cacheService = getCacheService(request.server)
const formState = await cacheService.getState(request)
const formState = await cacheService.getState(
/** @type {CacheRequest} */ (request)
)
const pagePayload = getPayloadFromFlash(request)
const currentPagePayload = Array.isArray(pagePayload)
? {}
Expand All @@ -92,7 +94,10 @@ export default [
mergeArrays: false
}
)
await cacheService.setState(request, combinedState)
await cacheService.setState(
/** @type {CacheRequest} */ (request),
combinedState
)
}

// Clear any previous save and exit session state
Expand Down Expand Up @@ -124,7 +129,9 @@ export default [
question: securityQuestion,
answer: securityAnswer
}
const state = await cacheService.getState(request)
const state = await cacheService.getState(
/** @type {CacheRequest} */ (request)
)

await publishSaveAndExitEvent(
metadata.id,
Expand All @@ -136,7 +143,7 @@ export default [
)

// Clear all form data
await cacheService.clearState(request)
await cacheService.clearState(/** @type {CacheRequest} */ (request))

// Add email to session for the confirmation page
request.yar.set(getKey(slug, status), email)
Expand Down Expand Up @@ -351,7 +358,10 @@ export default [
if (validatedLink.validPassword) {
// Restore state
const cacheService = getCacheService(request.server)
await cacheService.setState(request, validatedLink.state)
await cacheService.setState(
/** @type {CacheRequest} */ (request),
validatedLink.state
)

const { isPreview, status } = validatedLink.form

Expand Down Expand Up @@ -431,7 +441,10 @@ export default [
const { params } = request
const { slug, state } = params
const form = await getFormMetadata(slug)
const model = resumeSuccessViewModel(form, state)
const model = resumeSuccessViewModel(
form,
/** @type {FormStatus | undefined} */ (state)
)

return h.view(RESUME_SUCCESS, model)
},
Expand All @@ -450,6 +463,7 @@ export default [

/**
* @import { ServerRoute } from '@hapi/hapi'
* @import { FormPayload } from '@defra/forms-engine-plugin/engine/types.js'
* @import { CacheRequest, FormPayload } from '@defra/forms-engine-plugin/engine/types.js'
* @import { FormStatus } from '@defra/forms-model'
* @import { SaveAndExitParams, SaveAndExitPayload, SaveAndExitResumePasswordPayload, SaveAndExitResumePasswordParams } from '~/src/server/models/save-and-exit.js'
*/
11 changes: 6 additions & 5 deletions src/server/routes/save-and-exit.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FormStatus } from '@defra/forms-model'
import { StatusCodes } from 'http-status-codes'

import { createJoiError } from '~/src/server/helpers/error-helper.js'
Expand Down Expand Up @@ -41,7 +42,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
isPreview: true,
status: 'draft'
status: FormStatus.Draft
}
})

Expand All @@ -66,7 +67,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
isPreview: true,
status: 'draft'
status: FormStatus.Draft
}
})

Expand Down Expand Up @@ -160,7 +161,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
isPreview: true,
status: 'draft'
status: FormStatus.Draft
}
})

Expand All @@ -185,7 +186,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
isPreview: true,
status: 'draft'
status: FormStatus.Draft
}
})

Expand Down Expand Up @@ -427,7 +428,7 @@ describe('Save-and-exit check routes', () => {
// @ts-expect-error - allow partial objects for tests
form: {
isPreview: true,
status: 'draft'
status: FormStatus.Draft
}
})

Expand Down
Loading
Loading