Skip to content

Commit

Permalink
Merge pull request #95 from DEFRA/601
Browse files Browse the repository at this point in the history
[601] destination summary page
  • Loading branch information
eoin-corr-git authored Dec 20, 2024
2 parents a0a8d5f + a56b5dd commit 69f3156
Show file tree
Hide file tree
Showing 35 changed files with 503 additions and 184 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/check-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ jobs:
run: |
docker compose up --wait-timeout 300 -d --quiet-pull
- name: SonarCloud Scan
if: github.actor != 'dependabot[bot]'
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- name: Journey tests
run: |
npm run user-journey-test
- name: cleanup
if: always()
run: docker compose down

- name: SonarCloud Scan
if: github.actor != 'dependabot[bot]'
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export class QuestionPageController {
if (nextPage instanceof ExitPage) {
return h.redirect(nextPage.urlPath)
} else {
if (nextPage.overrideRedirects) {
return h.redirect(nextPage.urlPath)
}

return h.redirect(calculateNextPage(payload.nextPage, nextPage.urlPath))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const questionView =
const sectionKey = 'section-key'
const questionUrl = '/question-url'
const nextQuestionUrl = '/next-question-url'
const overriddenQuestionUrl = '/dummy/overridden-question-url'
const questionValue = 'question-value'
const questionElementSelector = '#questionId'
const validationSpy = jest
Expand All @@ -26,21 +27,41 @@ const redirectUri = '/redirect-uri'

class TestAnswer extends AnswerModel {
toState() {
return this._data?.questionKey
return this._data?.[questionKey]
}

static fromState(state) {
return new TestAnswer(
state !== undefined ? { questionKey: state } : undefined
state !== undefined ? { [questionKey]: state } : undefined
)
}

get value() {
return this._data?.questionKey
return this._data?.[questionKey]
}

_extractFields({ questionKey }) {
return { questionKey }
_extractFields(data) {
return {
[questionKey]: data[questionKey]
}
}

validate() {
if (!this.value || this.value?.includes('ERROR')) {
return {
isValid: false,
errors: {
questionKey: { text: 'There is a problem' }
}
}
}

return {
isValid: true,
errors: {
questionKey: { text: 'There is no problem' }
}
}
}
}

Expand All @@ -57,12 +78,14 @@ class TestPage extends QuestionPage {

Answer = TestAnswer

// eslint-disable-next-line @typescript-eslint/no-unused-vars
nextPage(_answer) {
if (_answer.value === 'exit') {
return new TestExitPage()
} else {
return new NextTestPage()
nextPage(answer) {
switch (answer.value) {
case 'exit':
return new TestExitPage()
case 'block-redirect':
return new RedirectBlockPage()
default:
return new NextTestPage()
}
}
}
Expand All @@ -71,6 +94,11 @@ class NextTestPage extends TestPage {
urlPath = nextQuestionUrl
}

class RedirectBlockPage extends TestPage {
urlPath = overriddenQuestionUrl
overrideRedirects = true
}

const controller = new QuestionPageController(new TestPage())

describe('QuestionPageController', () => {
Expand Down Expand Up @@ -107,7 +135,7 @@ describe('QuestionPageController', () => {

it('should repopulate the form from state', async () => {
await session.setState(sectionKey, {
questionKey: questionValue
[questionKey]: questionValue
})
const { payload, statusCode } = await server.inject(
withCsrfProtection(
Expand Down Expand Up @@ -142,7 +170,7 @@ describe('QuestionPageController', () => {
method: 'POST',
url: questionUrl,
payload: {
questionKey: questionValue
[questionKey]: questionValue
}
},
{
Expand All @@ -155,7 +183,7 @@ describe('QuestionPageController', () => {
expect(headers.location).toBe(nextQuestionUrl)

const state = await session.getState(sectionKey)
expect(state.questionKey).toBe(questionValue)
expect(state[questionKey]).toBe(questionValue)
expect(state.someOtherQuestion).toBe('some-other-answer')
})

Expand All @@ -166,7 +194,7 @@ describe('QuestionPageController', () => {
method: 'POST',
url: questionUrl,
payload: {
questionKey: 'exit',
[questionKey]: 'exit',
nextPage: redirectUri
}
},
Expand All @@ -180,7 +208,7 @@ describe('QuestionPageController', () => {
expect(headers.location).toBe('/exit')

const state = await session.getState(sectionKey)
expect(state.questionKey).toBe('exit')
expect(state[questionKey]).toBe('exit')
})

it('should set the next page to redirect_uri if one exists', async () => {
Expand All @@ -207,7 +235,7 @@ describe('QuestionPageController', () => {
method: 'POST',
url: questionUrl,
payload: {
questionKey: questionValue,
[questionKey]: questionValue,
nextPage: redirectUri
}
})
Expand Down Expand Up @@ -244,7 +272,7 @@ describe('QuestionPageController', () => {

it('should clear the session state *for this question only* if the user encounters an error', async () => {
await session.setState(sectionKey, {
questionKey: questionValue,
[questionKey]: questionValue,
someOtherQuestion: 'some-other-answer'
})
const { payload } = await server.inject(
Expand All @@ -263,7 +291,7 @@ describe('QuestionPageController', () => {
expect(document.title).toBe(`Error: ${question}`)

const state = await session.getState(sectionKey)
expect(state.questionKey).toBeUndefined()
expect(state[questionKey]).toBeUndefined()
expect(state.someOtherQuestion).toBe('some-other-answer')
})

Expand All @@ -289,4 +317,66 @@ describe('QuestionPageController', () => {
})
})
})

describe('next page tests', () => {
it('should return the TestExitPage', () => {
expect(
controller.page.nextPage(new TestAnswer({ [questionKey]: 'exit' }))
).toBeInstanceOf(TestExitPage)
})

it('should return the NextTestPage', () => {
expect(
controller.page.nextPage(new TestAnswer({ [questionKey]: 'continue' }))
).toBeInstanceOf(NextTestPage)
})

it('should go to redirected page on post', async () => {
const redirectUrl = '/dummy/incorrect-url'
const { statusCode, headers } = await server.inject(
withCsrfProtection(
{
method: 'POST',
url: `${questionUrl}`,
payload: {
nextPage: redirectUrl,
[questionKey]: 'continue'
}
},
{
Cookie: session.sessionID
}
)
)

expect(statusCode).toBe(statusCodes.redirect)
expect(headers.location).not.toBe(nextQuestionUrl)
expect(headers.location).toBe(redirectUrl)
})

it('should not go to redirected page on post', async () => {
await session.setState(sectionKey, { [questionKey]: 'block-redirect' })

const redirectUrl = '/dummy/incorrect-url'
const { statusCode, headers } = await server.inject(
withCsrfProtection(
{
method: 'POST',
url: `${questionUrl}`,
payload: {
nextPage: redirectUrl,
[questionKey]: 'block-redirect'
}
},
{
Cookie: session.sessionID
}
)
)

expect(statusCode).toBe(statusCodes.redirect)
expect(headers.location).not.toBe(redirectUrl)
expect(headers.location).toBe(overriddenQuestionUrl)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@ export class SummaryPageController {
const { isValid, firstInvalidPage } = section.validate()
if (!isValid) {
return res.redirect(
`${firstInvalidPage?.urlPath}?redirect_uri=/${this.page.urlKey ?? this.page.sectionKey}/check-answers`
`${firstInvalidPage?.urlPath}?redirect_uri=${this.urlPath}`
)
}

return res.view(this.indexView, {
pageTitle: this.page.pageTitle,
heading: this.page.pageHeading,
summary: sectionToSummary(
section,
`/${this.page.urlKey ?? this.page.sectionKey}/check-answers`
)
summary: sectionToSummary(section, this.urlPath)
})
}

Expand Down
3 changes: 3 additions & 0 deletions src/server/common/model/page/page-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export class Page {
/** @type {string} */
pageTitle

/** @type {boolean} */
overrideRedirects = false

/** @returns {string} */
get heading() {
return this.pageHeading
Expand Down
4 changes: 2 additions & 2 deletions src/server/destination/exit-page/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Page } from '../../common/model/page/page-model.js'
import { ExitPage } from '../../common/model/page/exit-page-model.js'

export class DestinationExitPage extends Page {
export class DestinationExitPage extends ExitPage {
urlPath = '/destination/can-not-use-service'
}
export const destinationExitPage = new DestinationExitPage()
8 changes: 8 additions & 0 deletions src/server/destination/exit-page/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ExitPage } from '../../common/model/page/exit-page-model.js'
import { destinationExitPage } from './index.js'

describe('DestinationExitPage', () => {
it('should be an exit page', () => {
expect(destinationExitPage).toBeInstanceOf(ExitPage)
})
})
2 changes: 2 additions & 0 deletions src/server/destination/general-licence/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class DestinationGeneralLicencePage extends Page {
pageTitle = 'Check if you have a general licence'
pageHeading = 'Check if you have a general licence'

overrideRedirects = true

nextPage() {
return new DestinationSummaryPage()
}
Expand Down
2 changes: 1 addition & 1 deletion src/server/destination/general-licence/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('DestinationGeneralLicencePage', () => {
})

it('should know the next page is the summary page', () => {
expect(page.nextPage()).toEqual(nextPage)
expect(page.nextPage().urlPath).toBe(nextPage.urlPath)
})

it('should be able ot calculate the next page URL as a string for the template', () => {
Expand Down
7 changes: 6 additions & 1 deletion src/server/destination/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { destinationType } from './destination-type/index.js'
import { generalLicence } from './general-licence/index.js'
import { destinationSummary } from './summary/index.js'

/**
* @satisfies {ServerRegisterPluginObject<void>}
Expand All @@ -8,7 +9,11 @@ export const destination = {
plugin: {
name: 'destination',
async register(server) {
await server.register([destinationType, generalLicence])
await server.register([
destinationType,
generalLicence,
destinationSummary
])
}
}
}
Expand Down
21 changes: 20 additions & 1 deletion src/server/destination/summary/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import SummaryPage from '../../common/model/page/summary-page/SummaryPageModel.js'
import { SummaryPageController } from '../../common/controller/summary-page-controller/SummaryPageController.js'

import { DestinationSection } from '~/src/server/common/model/section/destination/destination.js'

export class DestinationSummaryPage extends SummaryPage {
urlPath = '/destination/check-answers'
pageTitle = 'Check your answers before you continue your application'
pageHeading = 'Check your answers before you continue your application'
sectionKey = 'destination'
urlPath = `/${this.sectionKey}/check-answers`
sectionFactory = (data) => DestinationSection.fromState(data)
}

export const destinationSummaryPage = new DestinationSummaryPage()

/**
* @satisfies {ServerRegisterPluginObject<void>}
*/
export const destinationSummary = new SummaryPageController(
new DestinationSummaryPage()
).plugin()

/**
* @import { ServerRegisterPluginObject } from '@hapi/hapi'
*/
Loading

0 comments on commit 69f3156

Please sign in to comment.