Skip to content

Commit

Permalink
Merge branch 'main' into RA-243/add-log-prisoner-details-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
madebyzak committed Feb 10, 2025
2 parents 5ab3449 + 1ba25b6 commit 96a481c
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 12 deletions.
49 changes: 49 additions & 0 deletions integration_tests/e2e/swap-vos-pin-credit-details.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Page from '../pages/page'
import SwapVosPinCreditDetailsPage from '../pages/swapVosPinCreditDetailsPage'

context('Swap VOs for PIN Credit Details Page', () => {
let page: SwapVosPinCreditDetailsPage
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.signIn()
cy.visit('/log/swap-vos-pin-credit-details')

// Navigate through the pages
cy.contains('Swap visiting orders (VOs) for PIN credit').click()
cy.contains('button', 'Continue').click() // Click the Continue button on the 'Select application type' page
cy.contains('button', 'Continue').click() // Click the Continue button on the 'Log prisoner details' page
page = Page.verifyOnPage(SwapVosPinCreditDetailsPage)
})

it('should direct the user to the correct page', () => {
Page.verifyOnPage(SwapVosPinCreditDetailsPage)
})
it('should display the correct page title', () => {
page.pageTitle().should('include', 'Log swap VOs for PIN credit details')
})
it('should render the page heading correctly', () => {
page.checkOnPage()
})
it('should render the back link with correct text and href', () => {
page.backLink().should('have.text', 'Back').and('have.attr', 'href', '/log/prisoner-details')
})
it('should render the correct app type title', () => {
page.appTypeTitle().should('have.text', 'Swap VOs for PIN credit')
})
it('should render the correct form label for the textarea', () => {
page.formLabel().should('contain.text', 'Details (optional)')
})
it('should display the hint text correctly', () => {
page.hintText().should('contain.text', 'Add a brief summary, for example, if this person is a Foreign National')
})
it('should contain a textarea with the correct ID', () => {
page.textArea().should('have.attr', 'id', 'swap-vos-pin-credit-details')
})
it('should include a hidden CSRF token input field', () => {
page.csrfToken().should('exist')
})
it('should render a Continue button with the correct text', () => {
page.continueButton().should('contain.text', 'Continue')
})
})
23 changes: 23 additions & 0 deletions integration_tests/pages/swapVosPinCreditDetailsPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Page from './page'

export default class SwapVosPinCreditDetailsPage extends Page {
constructor() {
super('Log details')
}

backLink = () => cy.get('.govuk-back-link')

appTypeTitle = () => cy.get('.govuk-caption-xl')

pageTitle = () => cy.title()

hintText = () => cy.get('#swap-vos-pin-credit-details-hint')

formLabel = () => cy.get('label[for="swap-vos-pin-credit-details"]')

textArea = () => cy.get('#swap-vos-pin-credit-details')

csrfToken = () => cy.get('input[name="_csrf"]')

continueButton = () => cy.get('.govuk-button--primary')
}
26 changes: 19 additions & 7 deletions server/@types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { HmppsUser } from '../../interfaces/hmppsUser'

export declare module 'express-session' {
// Declare that the session will potentially contain these additional fields
interface SessionData {
returnTo: string
nowInMinutes: number
applicationData?: {
type: {
value: string
name: string
}
}
applicationData?: ApplicationData
}

interface ApplicationData {
type?: ApplicationType
prisonerName?: string
date?: Date
additionalData?: AdditionalApplicationData
}

interface ApplicationType {
value: string
name: string
}

type AdditionalApplicationData = SwapVOsForPinCreditDetails

interface SwapVOsForPinCreditDetails {
swapVOsToPinCreditDetails: string
}
}

Expand Down
5 changes: 2 additions & 3 deletions server/routes/applications/applicationTypeRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express'
import { APPLICATION_TYPES } from '../../constants/applicationTypes'
import asyncMiddleware from '../../middleware/asyncMiddleware'
import AuditService, { Page } from '../../services/auditService'
import { updateSessionData } from '../../utils/session'

export default function applicationTypeRoutes({ auditService }: { auditService: AuditService }): Router {
const router = Router()
Expand Down Expand Up @@ -36,9 +37,7 @@ export default function applicationTypeRoutes({ auditService }: { auditService:
return
}

req.session.applicationData = {
type: selectedAppType,
}
updateSessionData(req, { type: selectedAppType })

res.redirect(`/log/prisoner-details`)
}),
Expand Down
11 changes: 11 additions & 0 deletions server/routes/applications/prisonerDetailsRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Request, Response, Router } from 'express'
import asyncMiddleware from '../../middleware/asyncMiddleware'
import AuditService, { Page } from '../../services/auditService'
import { updateSessionData } from '../../utils/session'

export default function prisonerDetailsRoutes({ auditService }: { auditService: AuditService }): Router {
const router = Router()
Expand All @@ -27,6 +28,16 @@ export default function prisonerDetailsRoutes({ auditService }: { auditService:
router.post(
'/log/prisoner-details',
asyncMiddleware(async (req: Request, res: Response) => {
if (!req.session.applicationData) {
res.status(400).send('Application data is missing')
return
}

updateSessionData(req, {
prisonerName: req.body.prisonerName,
date: req.body.date,
})

res.redirect(`/log/swap-vos-pin-credit-details`)
}),
)
Expand Down
39 changes: 38 additions & 1 deletion server/routes/applications/swapVosPinCreditDetailsRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Request, Response, Router } from 'express'
import { APPLICATION_TYPES } from '../../constants/applicationTypes'
import asyncMiddleware from '../../middleware/asyncMiddleware'
import AuditService, { Page } from '../../services/auditService'
import { APPLICATION_TYPES } from '../../constants/applicationTypes'
import { updateSessionData } from '../../utils/session'

export default function swapVosPinCreditDetailsRoutes({ auditService }: { auditService: AuditService }): Router {
const router = Router()
Expand All @@ -27,5 +28,41 @@ export default function swapVosPinCreditDetailsRoutes({ auditService }: { auditS
}),
)

router.post(
'/log/swap-vos-pin-credit-details',
asyncMiddleware(async (req: Request, res: Response) => {
updateSessionData(req, {
additionalData: {
...req.session.applicationData?.additionalData,
swapVOsToPinCreditDetails: req.body.swapVosPinCreditDetails,
},
})

res.redirect(`/log/swap-vos-pin-credit-details/confirm`)
}),
)

router.get(
'/log/swap-vos-pin-credit-details/confirm',
asyncMiddleware(async (req: Request, res: Response) => {
await auditService.logPageView(Page.CONFIRM_SWAP_VOS_PIN_CREDIT_DETAILS_PAGE, {
who: res.locals.user.username,
correlationId: req.id,
})

const selectedAppType = APPLICATION_TYPES.find(type => type.value === req.session.applicationData?.type.value)

if (!selectedAppType) {
return res.redirect('/log/application-type')
}

return res.render('pages/log/confirm-swap-vos-pin-credit-details', {
title: 'Check details',
appTypeTitle: 'Swap VOs for PIN credit',
session: req.session,
})
}),
)

return router
}
1 change: 1 addition & 0 deletions server/services/auditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum Page {
LOG_APPLICATION_TYPE_PAGE = 'LOG_APPLICATION_TYPE_PAGE',
LOG_PRISONER_DETAILS_PAGE = 'LOG_PRISONER_DETAILS_PAGE',
LOG_SWAP_VOS_PIN_CREDIT_DETAILS_PAGE = 'LOG_SWAP_VOS_PIN_CREDIT_DETAILS_PAGE',
CONFIRM_SWAP_VOS_PIN_CREDIT_DETAILS_PAGE = 'CONFIRM_SWAP_VOS_PIN_CREDIT_DETAILS_PAGE',
}

export interface PageViewEventDetails {
Expand Down
71 changes: 71 additions & 0 deletions server/utils/session.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Request } from 'express'
import { SessionData } from 'express-session'
import { updateSessionData } from './session'

describe(updateSessionData.name, () => {
let req: Request

beforeEach(() => {
req = {
session: {} as SessionData,
} as Request
})

it('should initialize applicationData if it does not exist', () => {
updateSessionData(req, { prisonerName: 'John Doe' })

expect(req.session.applicationData).toEqual({
prisonerName: 'John Doe',
})
})

it('should update an existing field without removing other fields', () => {
req.session.applicationData = {
type: { name: 'Swap VOs', value: 'swap-vos' },
prisonerName: 'Jane Doe',
}

updateSessionData(req, { prisonerName: 'John Doe' })

expect(req.session.applicationData).toEqual({
type: { name: 'Swap VOs', value: 'swap-vos' },
prisonerName: 'John Doe',
})
})

it('should add new fields while keeping existing ones', () => {
req.session.applicationData = {
type: { name: 'Swap VOs', value: 'swap-vos' },
}

updateSessionData(req, { prisonerName: 'John Doe', date: new Date('2024-02-05') })

expect(req.session.applicationData).toEqual({
type: { name: 'Swap VOs', value: 'swap-vos' },
prisonerName: 'John Doe',
date: new Date('2024-02-05'),
})
})

it('should not overwrite nested objects but merge updates', () => {
req.session.applicationData = {
type: { name: 'Swap VOs', value: 'swap-vos' },
additionalData: { swapVOsToPinCreditDetails: 'Old value' },
}

updateSessionData(req, {
additionalData: { swapVOsToPinCreditDetails: 'New value' },
})

expect(req.session.applicationData).toEqual({
type: { name: 'Swap VOs', value: 'swap-vos' },
additionalData: { swapVOsToPinCreditDetails: 'New value' },
})
})

it('should handle undefined session gracefully', () => {
req.session = undefined

expect(() => updateSessionData(req, { prisonerName: 'John Doe' })).toThrow()
})
})
14 changes: 14 additions & 0 deletions server/utils/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Request } from 'express'
import { SessionData } from 'express-session'

// eslint-disable-next-line import/prefer-default-export
export const updateSessionData = (req: Request, updates: Partial<SessionData['applicationData']>) => {
if (!req.session.applicationData) {
req.session.applicationData = {} as SessionData['applicationData']
}

req.session.applicationData = {
...req.session.applicationData,
...updates,
}
}
Loading

0 comments on commit 96a481c

Please sign in to comment.