Skip to content
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@aws-sdk/client-sns": "^3.1079.0",
"@defra/forms-engine-plugin": "^5.0.0-alpha.21",
"@defra/forms-engine-plugin": "^5.0.0-alpha.22",
"@defra/forms-model": "^3.0.704",
"@defra/hapi-tracing": "^1.30.0",
"@elastic/ecs-pino-format": "^1.5.0",
Expand Down
2 changes: 2 additions & 0 deletions src/server/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StatusCodes } from 'http-status-codes'
import { FORM_PREFIX } from '~/src/server/constants.js'
import { configureEnginePlugin, createServer } from '~/src/server/index.js'
import {
generateReferenceNumber,
getFormDefinition,
getFormMetadata
} from '~/src/server/services/formsService.js'
Expand All @@ -31,6 +32,7 @@ describe('Model cache', () => {

beforeEach(() => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
jest.mocked(generateReferenceNumber).mockResolvedValue('XXX-XXX-XXX')
server.app.models.clear()
})

Expand Down
3 changes: 2 additions & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ export const configureEnginePlugin = async ({
},
ordnanceSurveyApiKey: config.get('ordnanceSurveyApiKey'),
ordnanceSurveyApiSecret: config.get('ordnanceSurveyApiSecret'),
getLanguage: resolveLanguage
getLanguage: resolveLanguage,
generateReferenceNumber: formsService.generateReferenceNumber
}
}
const routeOptions = {
Expand Down
23 changes: 22 additions & 1 deletion src/server/services/formsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ export async function validateSaveAndExitCredentials(
return results
}

/**
* Generates a unique reference number
* @param {string} [prefix] - the prefix
*/
export async function generateReferenceNumber(prefix) {
const postJsonByType =
/** @type {typeof postJson<GenerateReferenceNumber>} */ (postJson)

const query = prefix ? `?prefix=${prefix}` : ''
const { payload: results } = await postJsonByType(
`${submissionUrl}/submission/generate-reference-number${query}`,
{ payload: {}, timeout: 10 * 1000 } // 10 seconds
)
Comment thread
davidjamesstone marked this conversation as resolved.

if (!results) {
throw new Error('Unexpected empty response in generateReferenceNumber')
}

return results.referenceNumber
}

/**
* Retrieves a form secret and decrypts the value
* @param {string} formId - the id of the form
Expand All @@ -130,5 +151,5 @@ export async function getFormSecret(formId, secretName) {

/**
* @import { FormDefinition, FormMetadata } from '@defra/forms-model'
* @import { SaveAndExitDetails, SaveAndExitResumeDetails } from '~/src/server/types.js'
* @import { GenerateReferenceNumber, SaveAndExitDetails, SaveAndExitResumeDetails } from '~/src/server/types.js'
*/
51 changes: 51 additions & 0 deletions src/server/services/formsService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FormStatus } from '@defra/forms-engine-plugin/types'
import { StatusCodes } from 'http-status-codes'

import {
generateReferenceNumber,
getFormDefinition,
getFormMetadata,
getFormMetadataById,
Expand Down Expand Up @@ -217,6 +218,56 @@ describe('Forms service', () => {
})
})

describe('generateReferenceNumber', () => {
it('returns the generated reference number', async () => {
jest.mocked(postJson).mockResolvedValue({
res: /** @type {IncomingMessage} */ ({
statusCode: StatusCodes.OK
}),
payload: { referenceNumber: 'XXX-XXX-XXX' }
})

const referenceNumber = await generateReferenceNumber()

expect(referenceNumber).toBe('XXX-XXX-XXX')
expect(postJson).toHaveBeenCalledWith(
`${SUBMISSION_URL}/submission/generate-reference-number`,
{ payload: {}, timeout: 10 * 1000 } // 10 seconds
)
})

it('returns the generated reference number with prefix', async () => {
jest.mocked(postJson).mockResolvedValue({
res: /** @type {IncomingMessage} */ ({
statusCode: StatusCodes.OK
}),
payload: { referenceNumber: 'XYZ-XXX-XXX' }
})

const referenceNumber = await generateReferenceNumber('XYZ')

expect(referenceNumber).toBe('XYZ-XXX-XXX')
expect(postJson).toHaveBeenCalledWith(
`${SUBMISSION_URL}/submission/generate-reference-number?prefix=XYZ`,
{ payload: {}, timeout: 10 * 1000 } // 10 seconds
)
})

it('throws if no results', async () => {
// @ts-expect-error - partial mock of payload
jest.mocked(postJson).mockResolvedValue({
res: /** @type {IncomingMessage} */ ({
statusCode: StatusCodes.OK
}),
payload: undefined
})

await expect(() => generateReferenceNumber()).rejects.toThrow(
'Unexpected empty response in generateReferenceNumber'
)
})
})

describe('getFormSecret', () => {
beforeEach(() => {
// @ts-expect-error - mock fetch
Expand Down
4 changes: 4 additions & 0 deletions src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ export interface SaveAndExitDetails {
export interface SaveAndExitResumeDetails extends SaveAndExitDetails {
validPassword: boolean
}

export interface GenerateReferenceNumber {
referenceNumber: string
}
6 changes: 5 additions & 1 deletion test/form/confirmation-email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ 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/services/formsService.js'
import {
generateReferenceNumber,
getFormMetadata
} from '~/src/server/services/formsService.js'
import * as fixtures from '~/test/fixtures/index.js'
import { renderResponse } from '~/test/helpers/component-helpers.js'
import { getCookie, getCookieHeader } from '~/test/utils/get-cookie.js'
Expand Down Expand Up @@ -99,6 +102,7 @@ describe('Confirmation email', () => {
// server.app.models.clear()
jest.clearAllMocks()
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
jest.mocked(generateReferenceNumber).mockResolvedValue('XXX-XXX-XXX')
})

afterAll(async () => {
Expand Down
6 changes: 5 additions & 1 deletion test/form/cookies.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ 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/services/formsService.js'
import {
generateReferenceNumber,
getFormMetadata
} from '~/src/server/services/formsService.js'
import * as fixtures from '~/test/fixtures/index.js'
import { renderResponse } from '~/test/helpers/component-helpers.js'
import { getCookieHeader } from '~/test/utils/get-cookie.js'
Expand All @@ -18,6 +21,7 @@ describe(`Cookie banner and analytics`, () => {

beforeEach(() => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
jest.mocked(generateReferenceNumber).mockResolvedValue('XXX-XXX-XXX')
})

afterEach(async () => {
Expand Down
6 changes: 5 additions & 1 deletion test/form/phase-banner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { within } from '@testing-library/dom'

import { FORM_PREFIX } from '~/src/server/constants.js'
import { createServer } from '~/src/server/index.js'
import { getFormMetadata } from '~/src/server/services/formsService.js'
import {
generateReferenceNumber,
getFormMetadata
} from '~/src/server/services/formsService.js'
import * as fixtures from '~/test/fixtures/index.js'
import { renderResponse } from '~/test/helpers/component-helpers.js'
jest.mock('~/src/server/services/formsService.js')
Expand All @@ -19,6 +22,7 @@ describe(`Phase banner`, () => {

beforeEach(() => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
jest.mocked(generateReferenceNumber).mockResolvedValue('XXX-XXX-XXX')
})

test('shows the server phase tag by default', async () => {
Expand Down
3 changes: 3 additions & 0 deletions test/form/user-feedback.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { StatusCodes } from 'http-status-codes'
import { FORM_PREFIX } from '~/src/server/constants.js'
import { createServer } from '~/src/server/index.js'
import {
generateReferenceNumber,
getFormMetadata,
getFormMetadataById
} from '~/src/server/services/formsService.js'
Expand Down Expand Up @@ -105,6 +106,8 @@ describe('User feedback journey', () => {
beforeAll(async () => {
jest.mocked(getFormMetadataById).mockResolvedValue(metadata)
jest.mocked(getFormMetadata).mockResolvedValue(metadata)
jest.mocked(generateReferenceNumber).mockResolvedValue('XXX-XXX-XXX')

server = await createServer({
formFileName: 'user-feedback-with-custom-controller.js',
formFilePath: join(import.meta.dirname, 'definitions'),
Expand Down
Loading