diff --git a/src/server/plugins/engine/helpers.test.ts b/src/server/plugins/engine/helpers.test.ts index 6d8abc603..b5761a1b2 100644 --- a/src/server/plugins/engine/helpers.test.ts +++ b/src/server/plugins/engine/helpers.test.ts @@ -165,7 +165,7 @@ describe('Helpers', () => { } ) - it.each([ + const returnUrlTests = [ { href: '/test/full-name', @@ -178,21 +178,6 @@ describe('Helpers', () => { statusCode: StatusCodes.MOVED_TEMPORARILY } satisfies Partial }, - { - href: '/test/full-name', - - request: { - method: 'post', - payload: { - action: FormAction.Validate - }, - query: { returnUrl: 'https://www.gov.uk/help/privacy-notice' } - } satisfies Partial, - - redirect: { - statusCode: StatusCodes.SEE_OTHER - } satisfies Partial - }, { href: '/test/repeater/example', @@ -212,13 +197,36 @@ describe('Helpers', () => { statusCode: StatusCodes.MOVED_TEMPORARILY } satisfies Partial } - ])( - "should not redirect to the 'returnUrl' query param provided (other paths)", + ] + + it.each(returnUrlTests)( + 'should redirect to the path provided and add the returnUrl query param provided in the request', ({ href, ...options }) => { request = { ...request, ...options.request } proceed(request, h, href) - expect(h.redirect).not.toHaveBeenCalledWith(request.query.returnUrl) + expect(h.redirect).toHaveBeenCalledWith(expect.stringContaining(href)) + expect(h.redirect).toHaveBeenCalledWith( + expect.stringContaining( + `returnUrl=${encodeURIComponent(request.query.returnUrl ?? '')}` + ) + ) + } + ) + + it.each(returnUrlTests)( + 'should redirect to the path provided and add the returnUrl provided as an argument', + ({ href, ...options }) => { + const newReturnUrl = '/another-url' + request = { ...request, ...options.request } + + proceed(request, h, href, { returnUrl: newReturnUrl }) + expect(h.redirect).toHaveBeenCalledWith(expect.stringContaining(href)) + expect(h.redirect).toHaveBeenCalledWith( + expect.stringContaining( + `returnUrl=${encodeURIComponent(newReturnUrl)}` + ) + ) } ) }) diff --git a/src/server/plugins/engine/helpers.ts b/src/server/plugins/engine/helpers.ts index b57c893cb..516f8874f 100644 --- a/src/server/plugins/engine/helpers.ts +++ b/src/server/plugins/engine/helpers.ts @@ -118,10 +118,11 @@ engine.registerFilter('answer', function (name: string) { export function proceed( request: Pick, h: FormResponseToolkit, - nextUrl: string + nextUrl: string, + queryOverrides: FormQuery = {} ) { const { method, payload, query } = request - const { returnUrl } = query + const redirectQuery = { ...query, ...queryOverrides } const isReturnAllowed = payload && 'action' in payload @@ -131,9 +132,9 @@ export function proceed( // Redirect to return location (optional) const response = - isReturnAllowed && isPathRelative(returnUrl) - ? h.redirect(returnUrl) - : h.redirect(redirectPath(nextUrl)) + isReturnAllowed && isPathRelative(redirectQuery.returnUrl) + ? h.redirect(redirectQuery.returnUrl) + : h.redirect(redirectPath(nextUrl, redirectQuery)) // Redirect POST to GET to avoid resubmission return method === 'post' diff --git a/src/server/plugins/engine/routes/index.test.ts b/src/server/plugins/engine/routes/index.test.ts index 45cb49128..56943a401 100644 --- a/src/server/plugins/engine/routes/index.test.ts +++ b/src/server/plugins/engine/routes/index.test.ts @@ -1,3 +1,4 @@ +import { SchemaVersion } from '@defra/forms-model' import Boom from '@hapi/boom' import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi' @@ -7,17 +8,31 @@ import { getPage, proceed } from '~/src/server/plugins/engine/helpers.js' -import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js' +import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js' +import { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/TerminalPageController.js' import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js' +import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js' import { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js' import { type AnyFormRequest, type OnRequestCallback } from '~/src/server/plugins/engine/types.js' import { type FormResponseToolkit } from '~/src/server/routes/types.js' +import definition from '~/test/form/definitions/basic.js' jest.mock('~/src/server/plugins/engine/helpers') +const page = definition.pages[0] +const model: FormModel = new FormModel(definition, { + basePath: 'test' +}) +const terminalController: TerminalPageController = new TerminalPageController( + model, + page +) +const questionPageController: QuestionPageController = + new QuestionPageController(model, page) + describe('redirectOrMakeHandler', () => { const mockServer = {} as unknown as Parameters< typeof redirectOrMakeHandler @@ -47,7 +62,8 @@ describe('redirectOrMakeHandler', () => { getFormContext: jest.fn().mockReturnValue({ isForceAccess: false, data: {} - }) + }), + schemaVersion: SchemaVersion.V2 } as unknown as FormModel const mockMakeHandler = jest @@ -226,18 +242,9 @@ describe('redirectOrMakeHandler', () => { }) it('should redirect when page is not relevant', async () => { - const testPage = { - getState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - mergeState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - getSummaryPath: jest.fn().mockReturnValue('/summary'), - getHref: jest.fn().mockReturnValue('/test-href'), - getRelevantPath: jest.fn().mockReturnValue('/other-path'), - path: '/test-path' - } as unknown as PageControllerClass + const testPage = Object.assign({}, mockPage, { + getRelevantPath: jest.fn().mockReturnValue('/other-path') + }) as unknown as PageControllerClass ;(getPage as jest.Mock).mockReturnValue(testPage) await redirectOrMakeHandler( @@ -247,28 +254,21 @@ describe('redirectOrMakeHandler', () => { mockMakeHandler ) - expect(proceed).toHaveBeenCalledWith(mockRequest, mockH, '/test-href') + expect(proceed).toHaveBeenCalledWith(mockRequest, mockH, '/test-href', {}) expect(mockMakeHandler).not.toHaveBeenCalled() }) - it('should set returnUrl when redirecting and next pages exist', async () => { - const testPage = { - getState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - mergeState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - getSummaryPath: jest.fn().mockReturnValue('/summary'), + it('should set returnUrl when redirecting from summary page and next page is not an exit page', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/summary', getRelevantPath: jest.fn().mockReturnValue('/other-path'), - path: '/test-path', getHref: jest .fn() .mockReturnValueOnce('/summary-href') // First call: for summaryPath (returnUrl) .mockReturnValueOnce('/relevant-path-href') // Second call: for relevantPath (redirect) - } as unknown as PageControllerClass + }) as unknown as PageControllerClass ;(getPage as jest.Mock).mockReturnValue(testPage) - ;(findPage as jest.Mock).mockReturnValue({ next: ['next-page'] }) + ;(findPage as jest.Mock).mockReturnValue(questionPageController) await redirectOrMakeHandler( mockRequest, @@ -277,30 +277,40 @@ describe('redirectOrMakeHandler', () => { mockMakeHandler ) - expect(mockRequest.query.returnUrl).toBe('/summary-href') expect(proceed).toHaveBeenCalledWith( mockRequest, mockH, - '/relevant-path-href' + '/relevant-path-href', + { returnUrl: '/summary-href' } ) }) - it('should not set returnUrl when redirecting and no next pages exist', async () => { - const testPage = { - getState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - mergeState: jest - .fn() - .mockResolvedValue({ $$__referenceNumber: 'REF-123' }), - getSummaryPath: jest.fn().mockReturnValue('/summary'), + it('should not set returnUrl when redirecting from summary and next page is an exit page', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/summary', getHref: jest.fn().mockReturnValue('/test-href'), - getRelevantPath: jest.fn().mockReturnValue('/other-path'), - path: '/test-path' - } as unknown as PageControllerClass + getRelevantPath: jest.fn().mockReturnValue('/other-path') + }) as unknown as PageControllerClass ;(getPage as jest.Mock).mockReturnValue(testPage) - const returnUrlBefore = mockRequest.query.returnUrl - ;(findPage as jest.Mock).mockReturnValue({ next: [] }) + ;(findPage as jest.Mock).mockReturnValue(terminalController) + await redirectOrMakeHandler( + mockRequest, + mockH, + undefined, + mockMakeHandler + ) + + expect(proceed).toHaveBeenCalledWith(mockRequest, mockH, '/test-href', {}) + }) + + it('should not set returnUrl when redirecting from a page that is not summary', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/not-summary', + getHref: jest.fn().mockReturnValue('/test-href'), + getRelevantPath: jest.fn().mockReturnValue('/other-path') + }) as unknown as PageControllerClass + ;(getPage as jest.Mock).mockReturnValue(testPage) + ;(findPage as jest.Mock).mockReturnValue(questionPageController) await redirectOrMakeHandler( mockRequest, @@ -309,9 +319,91 @@ describe('redirectOrMakeHandler', () => { mockMakeHandler ) - // returnUrl should not be set if next pages don't exist - expect(mockRequest.query.returnUrl).toBe(returnUrlBefore) - expect(proceed).toHaveBeenCalledWith(mockRequest, mockH, '/test-href') + expect(proceed).toHaveBeenCalledWith(mockRequest, mockH, '/test-href', {}) + }) + + describe('when using v1 schema', () => { + beforeEach(() => { + const mockModelV1: FormModel = Object.assign({}, mockModel, { + schemaVersion: SchemaVersion.V1 + }) as unknown as FormModel + mockRequest.app = { model: mockModelV1 } + }) + + it('should set returnUrl when redirecting from summary and next pages exist', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/summary', + getRelevantPath: jest.fn().mockReturnValue('/other-path'), + getHref: jest + .fn() + .mockReturnValueOnce('/summary-href') // First call: for summaryPath (returnUrl) + .mockReturnValueOnce('/relevant-path-href') // Second call: for relevantPath (redirect) + }) as unknown as PageControllerClass + ;(getPage as jest.Mock).mockReturnValue(testPage) + ;(findPage as jest.Mock).mockReturnValue({ next: ['next-page'] }) + + await redirectOrMakeHandler( + mockRequest, + mockH, + undefined, + mockMakeHandler + ) + + expect(proceed).toHaveBeenCalledWith( + mockRequest, + mockH, + '/relevant-path-href', + { returnUrl: '/summary-href' } + ) + }) + + it('should not set returnUrl when redirecting and no next pages exist', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/summary', + getHref: jest.fn().mockReturnValue('/test-href'), + getRelevantPath: jest.fn().mockReturnValue('/other-path') + }) as unknown as PageControllerClass + ;(getPage as jest.Mock).mockReturnValue(testPage) + ;(findPage as jest.Mock).mockReturnValue({ next: [] }) + + await redirectOrMakeHandler( + mockRequest, + mockH, + undefined, + mockMakeHandler + ) + + expect(proceed).toHaveBeenCalledWith( + mockRequest, + mockH, + '/test-href', + {} + ) + }) + + it('should not set returnUrl when not redirecting from summary', async () => { + const testPage = Object.assign({}, mockPage, { + path: '/not-summary', + getHref: jest.fn().mockReturnValue('/test-href'), + getRelevantPath: jest.fn().mockReturnValue('/other-path') + }) as unknown as PageControllerClass + ;(getPage as jest.Mock).mockReturnValue(testPage) + ;(findPage as jest.Mock).mockReturnValue({ next: ['next-page'] }) + + await redirectOrMakeHandler( + mockRequest, + mockH, + undefined, + mockMakeHandler + ) + + expect(proceed).toHaveBeenCalledWith( + mockRequest, + mockH, + '/test-href', + {} + ) + }) }) }) }) diff --git a/src/server/plugins/engine/routes/index.ts b/src/server/plugins/engine/routes/index.ts index 9cb681c2c..6ecce3347 100644 --- a/src/server/plugins/engine/routes/index.ts +++ b/src/server/plugins/engine/routes/index.ts @@ -1,3 +1,4 @@ +import { SchemaVersion } from '@defra/forms-model' import Boom from '@hapi/boom' import { type ResponseObject, @@ -25,6 +26,7 @@ import { proceed } from '~/src/server/plugins/engine/helpers.js' import { FormModel } from '~/src/server/plugins/engine/models/index.js' +import { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/TerminalPageController.js' import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js' import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js' import * as defaultServices from '~/src/server/plugins/engine/services/index.js' @@ -38,6 +40,7 @@ import { type PluginOptions } from '~/src/server/plugins/engine/types.js' import { + type FormQuery, type FormRequest, type FormResponseToolkit } from '~/src/server/routes/types.js' @@ -100,12 +103,19 @@ export async function redirectOrMakeHandler( // Redirect back to last relevant page const redirectTo = findPage(model, relevantPath) + const overrideQueryParams: FormQuery = {} + // Set the return URL unless an exit page - if (redirectTo?.next.length) { - request.query.returnUrl = page.getHref(summaryPath) + if ( + page.path === summaryPath && + ((model.schemaVersion === SchemaVersion.V1 && redirectTo?.next.length) || + (model.schemaVersion === SchemaVersion.V2 && + !(redirectTo instanceof TerminalPageController))) + ) { + overrideQueryParams.returnUrl = page.getHref(summaryPath) } - return proceed(request, h, page.getHref(relevantPath)) + return proceed(request, h, page.getHref(relevantPath), overrideQueryParams) } async function importExternalComponentState( diff --git a/test/form/definitions/return-url.js b/test/form/definitions/return-url.js new file mode 100644 index 000000000..466aa9fed --- /dev/null +++ b/test/form/definitions/return-url.js @@ -0,0 +1,152 @@ +import { + ComponentType, + ConditionType, + ControllerType, + Engine, + OperatorName +} from '@defra/forms-model' + +/** + * Pages: + * - /age (Are you over 18?) + * - IF NOT OVER 18 --> /unavailable-service-page (Unavailable service page - terminal) --> EXIT + * - IF OVER 18 --> /pizza (Do you like pizza?) + * - IF LIKES PIZZA --> /favourite-pizza (What is your favourite pizza?) + * - /favourite-food (What is your favourite food?) + * - /summary (Summary) + */ + +export default /** @satisfies {FormDefinition} */ ({ + name: 'Return URL tests', + engine: Engine.V2, + schema: 2, + startPage: '/age', + pages: [ + { + title: 'Are you over 18?', + path: '/age', + components: [ + { + type: ComponentType.YesNoField, + title: 'Are you over 18?', + name: 'isOverEighteen', + id: 'c977e76e-49ab-4443-b93e-e19e8d9c81ac', + options: { required: true } + } + ], + id: '7be18dec-0680-4c41-9981-357aa085429d', + next: [] + }, + { + title: 'Unavailable service page', + controller: ControllerType.Terminal, + path: '/unavailable-service-page', + components: [ + { + title: 'Unavailable service page', + name: 'serviceNotAvailable', + type: ComponentType.Markdown, + content: 'Unavailable service message content.', + options: {} + } + ], + next: [], + id: '53bd4fca-becb-4681-b91a-09132f3500bb', + condition: 'd1f9fcc7-f098-47e7-9d31-4f5ee57ba985' + }, + { + title: 'Do you like pizza?', + path: '/pizza', + components: [ + { + type: ComponentType.YesNoField, + title: 'Do you like pizza?', + name: 'likesPizza', + id: '48287b43-c54f-4084-86ad-00b2a979e78d', + options: { required: true } + } + ], + id: '12345678-90ab-cdef-1234-567890abcdef', + next: [] + }, + { + title: 'What is your favourite pizza?', + path: '/favourite-pizza', + components: [ + { + type: ComponentType.TextField, + title: 'What is your favourite pizza?', + name: 'favouritePizza', + shortDescription: 'favourite pizza', + options: { + required: true + }, + schema: {}, + id: 'dadadada-4838-4c28-a0ef-7cace4a11c0f' + } + ], + next: [], + id: 'efcd83c3-6ad4-4c54-8c39-ef87f79101ef', + condition: 'e53fa1ef-a101-4c8c-81f1-e78b466818d8' + }, + { + title: 'What is your favourite food?', + path: '/favourite-food', + components: [ + { + type: ComponentType.TextField, + title: 'What is your favourite food?', + name: 'favouriteFood', + shortDescription: 'favourite food', + options: { + required: true + }, + schema: {}, + id: 'bbefe85d-4838-4c28-a0ef-7cace4a11c0f' + } + ], + next: [], + id: 'adadadad-6ad4-4c54-8c39-ef87f79101ef' + }, + { + id: '449a45f6-4541-4a46-91bd-8b8931b07b50', + title: 'Summary', + path: '/summary', + controller: ControllerType.Summary + } + ], + conditions: [ + { + items: [ + { + id: 'c833b177-0cba-49de-b670-a297c6db45b8', + componentId: 'c977e76e-49ab-4443-b93e-e19e8d9c81ac', + operator: OperatorName.Is, + value: false, + type: ConditionType.BooleanValue + } + ], + displayName: 'is over 18', + id: 'd1f9fcc7-f098-47e7-9d31-4f5ee57ba985' + }, + { + items: [ + { + id: '48287b43-c54f-4084-86ad-00b2a979e78d', + componentId: '48287b43-c54f-4084-86ad-00b2a979e78d', + operator: OperatorName.Is, + value: true, + type: ConditionType.BooleanValue + } + ], + displayName: 'likes pizza', + id: 'e53fa1ef-a101-4c8c-81f1-e78b466818d8' + } + ], + sections: [], + lists: [] +}) + +/** + * @import { FormDefinition } from '@defra/forms-model' + */ diff --git a/test/form/exit-page.test.js b/test/form/exit-page.test.js index f37c76ca7..981db684b 100644 --- a/test/form/exit-page.test.js +++ b/test/form/exit-page.test.js @@ -10,6 +10,7 @@ import { renderResponse } from '~/test/helpers/component-helpers.js' import { getCookie, getCookieHeader } from '~/test/utils/get-cookie.js' const basePath = `${FORM_PREFIX}/demo-cph-number` +const returnUrlQueryString = `?returnUrl=%2Fdemo-cph-number%2Fsummary` jest.mock('~/src/server/utils/notify.ts') jest.mock('~/src/server/plugins/engine/services/formsService.js') @@ -199,7 +200,9 @@ describe('Exit pages', () => { // Redirect back to relevant page (with return URL) expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) - expect(response.headers.location).toBe(`${basePath}${paths.next}`) + expect(response.headers.location).toBe( + `${basePath}${paths.next}${returnUrlQueryString}` + ) }) } ) diff --git a/test/form/journey-basic.test.js b/test/form/journey-basic.test.js index b1801ac3a..d668c757c 100644 --- a/test/form/journey-basic.test.js +++ b/test/form/journey-basic.test.js @@ -13,6 +13,7 @@ import { renderResponse } from '~/test/helpers/component-helpers.js' import { getCookie, getCookieHeader } from '~/test/utils/get-cookie.js' const basePath = `${FORM_PREFIX}/basic` +const returnUrlQueryString = `?returnUrl=%2Fbasic%2Fsummary` jest.mock('~/src/server/utils/notify.ts') jest.mock('~/src/server/plugins/engine/services/formsService.js') @@ -436,7 +437,9 @@ describe('Form journey', () => { // Redirect back to start expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) - expect(response.headers.location).toBe(`${basePath}/licence`) + expect(response.headers.location).toBe( + `${basePath}/licence${returnUrlQueryString}` + ) }) }) }) diff --git a/test/form/return-url.test.js b/test/form/return-url.test.js new file mode 100644 index 000000000..fdaf54b70 --- /dev/null +++ b/test/form/return-url.test.js @@ -0,0 +1,184 @@ +import { join } from 'node:path' + +import { StatusCodes } from 'http-status-codes' + +import { FORM_PREFIX } from '~/src/server/constants.js' +import { createServer } from '~/src/server/index.js' +import { getFormMetadata } from '~/src/server/plugins/engine/services/formsService.js' +import * as fixtures from '~/test/fixtures/index.js' +import { getCookie, getCookieHeader } from '~/test/utils/get-cookie.js' + +const basePath = `${FORM_PREFIX}/return-url` +const returnUrlQueryString = `?returnUrl=${encodeURIComponent('/return-url/summary')}` + +jest.mock('~/src/server/plugins/engine/services/formsService.js') + +describe('Return URL tests', () => { + /** @type {Server} */ + let server + + /** @type {string} */ + let csrfToken + + /** @type {ReturnType} */ + let headers + + // Create server before each test + beforeAll(async () => { + server = await createServer({ + formFileName: 'return-url.js', + formFilePath: join(import.meta.dirname, 'definitions'), + enforceCsrf: true + }) + + await server.initialize() + + // Navigate to start + const response = await server.inject({ + url: `${basePath}/age` + }) + + // Extract the session cookie + csrfToken = getCookie(response, 'crumb') + headers = getCookieHeader(response, ['session', 'crumb']) + }) + + beforeEach(() => { + jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata) + }) + + afterAll(async () => { + await server.stop() + }) + + describe('Return URL testing', () => { + it('should go to first invalid page and include returnUrl when loading summary page with empty state', async () => { + const response = await server.inject({ + url: `${basePath}/summary` + }) + + expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) + expect(response.headers.location).toBe( + `${basePath}/age${returnUrlQueryString}` + ) + }) + + it('should go to first invalid page and include returnUrl when loading summary page without full state', async () => { + // set context with age but without pizza answer + const payload = { + isOverEighteen: true + } + await server.inject({ + url: `${basePath}/age`, + method: 'POST', + headers, + payload: { ...payload, crumb: csrfToken } + }) + + // trying to load favourite pizza page without pizza answer in context + const response = await server.inject({ + method: 'GET', + url: `${basePath}/summary`, + headers + }) + + expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) + expect(response.headers.location).toBe( + `${basePath}/pizza${returnUrlQueryString}` + ) + }) + + it('should go to first invalid AND relevant page and include returnUrl when loading summary page without full state', async () => { + // set context with age, with pizza answer as no + const agePayload = { + isOverEighteen: true + } + await server.inject({ + url: `${basePath}/age`, + method: 'POST', + headers, + payload: { ...agePayload, crumb: csrfToken } + }) + const pizzaPayload = { + likesPizza: false + } + await server.inject({ + url: `${basePath}/pizza`, + method: 'POST', + headers, + payload: { ...pizzaPayload, crumb: csrfToken } + }) + + const response = await server.inject({ + method: 'GET', + url: `${basePath}/summary`, + headers + }) + + expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) + expect(response.headers.location).toBe( + `${basePath}/favourite-food${returnUrlQueryString}` + ) + }) + + it('should redirect to exit page (without returnUrl) when loading summary page with age not over 18', async () => { + const payload = { + isOverEighteen: false + } + await server.inject({ + url: `${basePath}/age`, + method: 'POST', + headers, + payload: { ...payload, crumb: csrfToken } + }) + + const response = await server.inject({ + method: 'GET', + url: `${basePath}/summary`, + headers + }) + + expect(response.statusCode).toBe(StatusCodes.MOVED_TEMPORARILY) + expect(response.headers.location).toBe( + `${basePath}/unavailable-service-page` + ) + }) + + it('should redirect to summary after POST with full state', async () => { + const agePayload = { + isOverEighteen: true + } + await server.inject({ + url: `${basePath}/age`, + method: 'POST', + headers, + payload: { ...agePayload, crumb: csrfToken } + }) + const pizzaPayload = { + likesPizza: false + } + await server.inject({ + url: `${basePath}/pizza`, + method: 'POST', + headers, + payload: { ...pizzaPayload, crumb: csrfToken } + }) + const foodPayload = { + favouriteFood: 'Lasagna' + } + const response = await server.inject({ + url: `${basePath}/favourite-food`, + method: 'POST', + headers, + payload: { ...foodPayload, crumb: csrfToken } + }) + + expect(response.statusCode).toBe(StatusCodes.SEE_OTHER) + expect(response.headers.location).toBe(`${basePath}/summary`) + }) + }) +}) + +/** + * @import { Server } from '@hapi/hapi' + */