Skip to content

Commit 1f0beaa

Browse files
authored
Merge pull request #310 from DEFRA/bugfix/DF-834-file-upload-expired
Handle cases where CDP's file upload service can't find the upload ID
2 parents a6cab10 + 9fbb78a commit 1f0beaa

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/dot-notation */
22
import { ComponentType, type ComponentDef } from '@defra/forms-model'
3+
import Boom from '@hapi/boom'
34
import { type ValidationErrorItem, type ValidationResult } from 'joi'
45

56
import {
@@ -195,6 +196,89 @@ describe('FileUploadPageController', () => {
195196
)
196197
})
197198

199+
it('initiates new upload when getUploadStatus throws a 404 error', async () => {
200+
const state = {
201+
upload: {
202+
[controller.path]: {
203+
upload: {
204+
uploadId: 'some-id',
205+
uploadUrl: 'some-url',
206+
statusUrl: 'some-status-url'
207+
},
208+
files: []
209+
}
210+
}
211+
} as unknown as FormSubmissionState
212+
213+
const notFoundError = Boom.notFound('Upload not found')
214+
215+
jest
216+
.spyOn(uploadService, 'getUploadStatus')
217+
.mockRejectedValue(notFoundError)
218+
219+
const testController = controller as TestableFileUploadPageController
220+
const initiateSpy = jest.spyOn(
221+
testController,
222+
'initiateAndStoreNewUpload'
223+
)
224+
initiateSpy.mockResolvedValue(state as never)
225+
226+
const result = await controller['checkUploadStatus'](request, state, 1)
227+
228+
expect(initiateSpy).toHaveBeenCalledWith(request, state)
229+
expect(result).toBe(state)
230+
})
231+
232+
it('re-throws non-404 Boom errors from getUploadStatus', async () => {
233+
const state = {
234+
upload: {
235+
[controller.path]: {
236+
upload: {
237+
uploadId: 'some-id',
238+
uploadUrl: 'some-url',
239+
statusUrl: 'some-status-url'
240+
},
241+
files: []
242+
}
243+
}
244+
} as unknown as FormSubmissionState
245+
246+
const serverError = Boom.internal('Server error')
247+
248+
jest
249+
.spyOn(uploadService, 'getUploadStatus')
250+
.mockRejectedValue(serverError)
251+
252+
await expect(
253+
controller['checkUploadStatus'](request, state, 1)
254+
).rejects.toThrow('Server error')
255+
})
256+
257+
it('re-throws non-Boom errors from getUploadStatus', async () => {
258+
const state = {
259+
upload: {
260+
[controller.path]: {
261+
upload: {
262+
uploadId: 'some-id',
263+
uploadUrl: 'some-url',
264+
statusUrl: 'some-status-url'
265+
},
266+
files: []
267+
}
268+
}
269+
} as unknown as FormSubmissionState
270+
271+
const networkError = new Error('Network failure')
272+
273+
jest
274+
.spyOn(uploadService, 'getUploadStatus')
275+
.mockRejectedValue(networkError)
276+
277+
await expect(
278+
controller['checkUploadStatus'](request, state, 1)
279+
).rejects.toThrow('Network failure')
280+
})
281+
198282
it('handles pending upload with backoff and retries', async () => {
199283
const state = {
200284
upload: {

src/server/plugins/engine/pageControllers/FileUploadPageController.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ComponentType, type PageFileUpload } from '@defra/forms-model'
22
import Boom from '@hapi/boom'
33
import { wait } from '@hapi/hoek'
4+
import { StatusCodes } from 'http-status-codes'
45
import { type ValidationErrorItem } from 'joi'
56

67
import {
@@ -327,7 +328,25 @@ export class FileUploadPageController extends QuestionPageController {
327328
}
328329

329330
const uploadId = upload.uploadId
330-
const statusResponse = await getUploadStatus(uploadId)
331+
332+
let statusResponse
333+
334+
try {
335+
statusResponse = await getUploadStatus(uploadId)
336+
} catch (err) {
337+
// if the user loads a file upload page and queries the cached upload, after the upload has
338+
// expired in CDP, we will get a 404 from the getUploadStatus endpoint.
339+
// In this case we want to initiate a new upload and return that state, so the form
340+
// doesn't blow up for the end user.
341+
if (
342+
Boom.isBoom(err) &&
343+
err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()
344+
) {
345+
return this.initiateAndStoreNewUpload(request, state)
346+
}
347+
throw err
348+
}
349+
331350
if (!statusResponse) {
332351
throw Boom.badRequest(
333352
`Unexpected empty response from getUploadStatus for ${uploadId}`

0 commit comments

Comments
 (0)