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
7 changes: 7 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import logging from './plugins/logging.js'
import session from './plugins/session.js'
import onPostHandler from './plugins/on-post-handler.js'

const expire = 72 * 60 * 60 * 1000

const createServer = async options => {
// Create the hapi server
options = {
Expand All @@ -34,6 +36,11 @@ const createServer = async options => {

const init = async server => {
await _registerPlugins(server)
server.app.mediaUploadCache = server.cache({
cache: 'redis_cache',
segment: 'media-upload',
expiresIn: expire
})
await server.start()
}

Expand Down
156 changes: 156 additions & 0 deletions server/routes/__tests__/report-sent.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,169 @@
import { submitGetRequest } from '../../__test-helpers__/server.js'
import constants from '../../utils/constants.js'
import { questionSets } from '../../utils/question-sets.js'
import config from '../../utils/config.js'
import reportSentRoutes from '../report-sent.js'

const url = constants.routes.REPORT_SENT
const header = 'Report sent'
const submissionTimestamp = '2026-04-09T09:00:00.000Z'
const sessionId = 'test-session-id'
const expectedMediaUploadLink = `${config.mediaUploadUrl}/upload-photo?sirid=${sessionId}`

const journeySessionData = {
100: {
contactDetails: {
reporterEmailAddress: 'water@test.com'
},
imagesOrVideo: [{
answerId: questionSets.WATER_POLLUTION.questions.WATER_POLLUTION_IMAGES_OR_VIDEO.answers.yes.answerId
}]
},
200: {
contactDetails: {
reporterEmailAddress: 'smell@test.com'
},
imagesOrVideo: [{
answerId: questionSets.SMELL.questions.SMELL_IMAGES_OR_VIDEO.answers.yes.answerId
}]
},
300: {
contactDetails: {
reporterEmailAddress: 'blockage@test.com'
},
imagesOrVideo: [{
answerId: questionSets.BLOCKAGE.questions.BLOCKAGE_IMAGES_OR_VIDEO.answers.yes.answerId
}]
},
1800: {
contactDetails: {
reporterEmailAddress: 'fishing@test.com'
},
imagesOrVideo: [{
answerId: questionSets.ILLEGAL_FISHING.questions.ILLEGAL_FISHING_IMAGES_OR_VIDEO.answers.yes.answerId
}]
}
}

const handler = async (questionSetID, overrides = {}) => {
const set = jest.fn()
const view = jest.fn()

const defaultJourneyData = journeySessionData[questionSetID] || {
contactDetails: { reporterEmailAddress: '' },
imagesOrVideo: []
}

const contactDetails = overrides.contactDetails !== undefined
? overrides.contactDetails
: defaultJourneyData.contactDetails
const imagesOrVideo = overrides.imagesOrVideo !== undefined
? overrides.imagesOrVideo
: defaultJourneyData.imagesOrVideo

const keyMap = {
100: {
contactDetailsKey: constants.redisKeys.WATER_POLLUTION_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.WATER_POLLUTION_IMAGES_OR_VIDEO
},
200: {
contactDetailsKey: constants.redisKeys.SMELL_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.SMELL_IMAGES_OR_VIDEO
},
300: {
contactDetailsKey: constants.redisKeys.BLOCKAGE_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.BLOCKAGE_IMAGES_OR_VIDEO
},
1800: {
contactDetailsKey: constants.redisKeys.ILLEGAL_FISHING_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.ILLEGAL_FISHING_IMAGES_OR_VIDEO
}
}

const journeyKeys = keyMap[questionSetID] || {}

await reportSentRoutes[0].handler({
yar: {
get: jest.fn(key => ({
[constants.redisKeys.QUESTION_SET_ID]: questionSetID,
[constants.redisKeys.SUBMISSION_TIMESTAMP]: submissionTimestamp,
[journeyKeys.contactDetailsKey]: contactDetails,
[journeyKeys.imagesOrVideoKey]: imagesOrVideo
}[key])),
id: sessionId,
reset: jest.fn()
},
server: {
app: {
mediaUploadCache: { set }
}
}
}, { view })

return { set, view }
}

describe(url, () => {
describe('GET', () => {
it(`Should return success response and correct view for ${url}`, async () => {
await submitGetRequest({ url }, header)
})

it.each([
{ questionSetID: 100, expectedJourney: 'water pollution' },
{ questionSetID: 200, expectedJourney: 'smell' },
{ questionSetID: 300, expectedJourney: 'blockage' },
{ questionSetID: 1800, expectedJourney: 'illegal fishing' }
])('should cache journey "$expectedJourney" for questionSetID $questionSetID', async ({ questionSetID, expectedJourney }) => {
const { set } = await handler(questionSetID)

expect(set).toHaveBeenCalledWith(sessionId, {
journey: expectedJourney,
dateTime: submissionTimestamp
})
})

it('should pass mediaUploadLink in photoUploadDetails', async () => {
const { view } = await handler(100)

expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({
photoUploadDetails: expect.objectContaining({
mediaUploadLink: expectedMediaUploadLink
// mediaUploadLink: config.mediaUploadUrl
})
}))
})

it.each([
{ questionSetID: 100, email: 'water@test.com' },
{ questionSetID: 200, email: 'smell@test.com' },
{ questionSetID: 300, email: 'blockage@test.com' },
{ questionSetID: 1800, email: 'fishing@test.com' }
])('should pass photo upload details for questionSetID $questionSetID', async ({ questionSetID, email }) => {
const { view } = await handler(questionSetID)

expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({
photoUploadDetails: {
mediaUploadLink: expectedMediaUploadLink,
reportersEmail: email,
userAgreedForImages: true
}
}))
})

it('should default photo upload details when contact details and image answer are missing', async () => {
const { view } = await handler(100, {
contactDetails: null,
imagesOrVideo: null
})

expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({
photoUploadDetails: {
mediaUploadLink: expectedMediaUploadLink,
reportersEmail: '',
userAgreedForImages: false
}
}))
})
})
})
74 changes: 68 additions & 6 deletions server/routes/report-sent.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,82 @@
import constants from '../utils/constants.js'
import { questionSets } from '../utils/question-sets.js'
import config from '../utils/config.js'

const mediaUploadBaseUrl = `${config.mediaUploadUrl}/upload-photo`

const journeyMap = {
100: 'water pollution',
200: 'smell',
300: 'blockage',
1800: 'illegal fishing'
}

const journeyConfigMap = {
100: {
contactDetailsKey: constants.redisKeys.WATER_POLLUTION_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.WATER_POLLUTION_IMAGES_OR_VIDEO,
imagesQuestion: questionSets.WATER_POLLUTION.questions.WATER_POLLUTION_IMAGES_OR_VIDEO
},
200: {
contactDetailsKey: constants.redisKeys.SMELL_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.SMELL_IMAGES_OR_VIDEO,
imagesQuestion: questionSets.SMELL.questions.SMELL_IMAGES_OR_VIDEO
},
300: {
contactDetailsKey: constants.redisKeys.BLOCKAGE_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.BLOCKAGE_IMAGES_OR_VIDEO,
imagesQuestion: questionSets.BLOCKAGE.questions.BLOCKAGE_IMAGES_OR_VIDEO
},
1800: {
contactDetailsKey: constants.redisKeys.ILLEGAL_FISHING_CONTACT_DETAILS,
imagesOrVideoKey: constants.redisKeys.ILLEGAL_FISHING_IMAGES_OR_VIDEO,
imagesQuestion: questionSets.ILLEGAL_FISHING.questions.ILLEGAL_FISHING_IMAGES_OR_VIDEO
}
}

const handlers = {
get: async (request, h) => {
const questionSetID = request.yar.get(constants.redisKeys.QUESTION_SET_ID)
const submissionTimestamp = request.yar.get(constants.redisKeys.SUBMISSION_TIMESTAMP)
const journey = journeyMap[questionSetID]
const sessionId = request.yar.id
const journeyConfig = journeyConfigMap[questionSetID]

const contactDetails = journeyConfig
? request.yar.get(journeyConfig.contactDetailsKey)
: null
const imagesOrVideoAnswer = journeyConfig
? request.yar.get(journeyConfig.imagesOrVideoKey)
: null

const reportersEmail = contactDetails?.reporterEmailAddress || ''
const userAgreedForImages = journeyConfig
? imagesOrVideoAnswer?.[0]?.answerId === journeyConfig.imagesQuestion.answers.yes.answerId
: false

await request.server.app.mediaUploadCache.set(sessionId, {
journey,
dateTime: submissionTimestamp
})

const mediaUploadLink = `${mediaUploadBaseUrl}?sirid=${sessionId}`

request.yar.reset()
request.yar.set(constants.redisKeys.QUESTION_SET_ID, questionSetID)
const context = _getContext()
return h.view(constants.views.REPORT_SENT, {
...context
const context = _getContext({
reportersEmail,
userAgreedForImages,
mediaUploadLink
// mediaUploadLink: config.mediaUploadUrl
})
return h.view(constants.views.REPORT_SENT, context)
}
}

const _getContext = () => {
const _getContext = (photoUploadDetails) => {
return {
hideBackLink: true
hideBackLink: true,
photoUploadDetails
// mediaUploadLink: config.mediaUploadUrl
}
}

Expand Down
6 changes: 4 additions & 2 deletions server/utils/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const schema = Joi.object().keys({
captchaEnabled: Joi.bool().default(false),
captchaApiKey: Joi.string().allow(''),
captchaSiteKey: Joi.string().allow(''),
captchaBypassKey: Joi.string()
captchaBypassKey: Joi.string(),
mediaUploadUrl: Joi.string().uri({ scheme: ['http', 'https'] }).allow('').default('')
})

const captchaEnabled = getBoolean(process.env.CAPTCHA_ENABLED)
Expand All @@ -54,7 +55,8 @@ const config = {
captchaEnabled,
captchaApiKey: captchaEnabled ? process.env.CAPTCHA_API_KEY : '',
captchaSiteKey: captchaEnabled ? process.env.CAPTCHA_SITE_KEY : '',
captchaBypassKey: process.env.CAPTCHA_BYPASS_KEY
captchaBypassKey: process.env.CAPTCHA_BYPASS_KEY,
mediaUploadUrl: process.env.MEDIA_UPLOAD_URL
}

// Validate config
Expand Down
14 changes: 14 additions & 0 deletions server/views/report-sent.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@
titleText: "Report sent",
html: "We have received your report"
}) }}
{% if(photoUploadDetails.userAgreedForImages and photoUploadDetails.reportersEmail !== '') %}
<p>We have sent a confirmation of your report and a reference number to <strong>{{ photoUploadDetails.reportersEmail }}</strong></p>
{% endif %}

{% if(photoUploadDetails.userAgreedForImages) %}
<h2 class="govuk-heading-m">
Upload images
</h2>
<p>You can now <a href="{{ photoUploadDetails.mediaUploadLink }}">upload any photos</a> you have that might help us investigate the problem.</p>
{% endif %}
{% if(photoUploadDetails.userAgreedForImages and photoUploadDetails.reportersEmail !== '') %}
<p>We've also included this link in your confirmation email, if you want to do this later.</p>
{% endif %}

<h2 class="govuk-heading-m">
What happens next
</h2>
Expand Down
Loading