Skip to content

Commit 42de73d

Browse files
authored
Merge pull request #471 from DEFRA/feat/df-912-dlq
feat/df-912: Added DLQ capabilities
2 parents 5ba59e0 + 9f1bb47 commit 42de73d

9 files changed

Lines changed: 379 additions & 33 deletions

File tree

package-lock.json

Lines changed: 45 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/types.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@
5959
* @typedef {{ Params: { formId: string }}} GenerateFeedbackSubmissionsFile
6060
*/
6161

62+
/**
63+
* @typedef {{ Params: { dlq: string }}} DeadLetterQueueRequest
64+
*/
65+
66+
/**
67+
* @typedef {{ Params: { dlq: string, receiptHandle: string }}} DeadLetterQueueAndHandleRequest
68+
*/
69+
6270
/**
6371
* @typedef {object} UploadPayload
6472
* @property {('initiated'|'pending'|'ready')} uploadStatus - Have all scans completed, can be initiated, pending or ready

src/config/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ export const config = convict({
265265
default: '',
266266
env: 'SUBMISSION_QUEUE_URL'
267267
},
268+
sqsFormSubmissionsDlqArn: {
269+
doc: 'SQS deadletter queue ARN for form submission events',
270+
format: String,
271+
default: '',
272+
env: 'FORM_SUBMISSIONS_SQS_DLQ_ARN'
273+
},
274+
sqsSaveAndExitDlqArn: {
275+
doc: 'SQS deadletter queue ARN for save-and-exit events',
276+
format: String,
277+
default: '',
278+
env: 'SAVE_AND_EXIT_SQS_DLQ_ARN'
279+
},
268280
receiveMessageTimeout: {
269281
doc: 'The wait time between each poll in milliseconds',
270282
format: Number,

src/messaging/event.js

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
DeleteMessageCommand,
3-
ReceiveMessageCommand
3+
ReceiveMessageCommand,
4+
StartMessageMoveTaskCommand
45
} from '@aws-sdk/client-sqs'
56

67
import { config } from '~/src/config/index.js'
@@ -11,6 +12,15 @@ export const receiveMessageTimeout = config.get('receiveMessageTimeout')
1112
const maxNumberOfMessages = config.get('maxNumberOfMessages')
1213
const visibilityTimeout = config.get('visibilityTimeout')
1314

15+
/**
16+
* @param {string} dlqName
17+
*/
18+
function getDeadLetterQueueUrl(dlqName) {
19+
return dlqName === 'save-and-exit'
20+
? `${config.get('saveAndExitQueueUrl')}-deadletter`
21+
: `${config.get('submissionQueueUrl')}-deadletter`
22+
}
23+
1424
/**
1525
* Receive event messages
1626
* @param {string} queueUrl - the SQS queue url
@@ -30,6 +40,53 @@ export function receiveMessages(queueUrl) {
3040
return sqsClient.send(command)
3141
}
3242

43+
/**
44+
* Receive dead-letter queue messages
45+
* @param {string} dlq - the SQS deadletter queue identifier
46+
* @returns {Promise<ReceiveMessageResult>}
47+
*/
48+
export function receiveDlqMessages(dlq) {
49+
const queueUrl = getDeadLetterQueueUrl(dlq)
50+
const command = new ReceiveMessageCommand({
51+
QueueUrl: queueUrl,
52+
MaxNumberOfMessages: 10,
53+
VisibilityTimeout: 1,
54+
WaitTimeSeconds: 0
55+
})
56+
return sqsClient.send(command)
57+
}
58+
59+
/**
60+
* Redrive all messages from the dead-letter queue to the main queue
61+
* @param {string} dlq - the SQS deadletter queue ARN
62+
* @returns {Promise<StartMessageMoveTaskResult>}
63+
*/
64+
export function redriveDlqMessages(dlq) {
65+
const queueArn =
66+
dlq === 'save-and-exit'
67+
? config.get('sqsSaveAndExitDlqArn')
68+
: config.get('sqsFormSubmissionsDlqArn')
69+
const command = new StartMessageMoveTaskCommand({
70+
SourceArn: queueArn
71+
})
72+
return sqsClient.send(command)
73+
}
74+
75+
/**
76+
* Delete the specified message from the dead-letter queue
77+
* @param {string} dlq - the SQS deadletter queue ARN
78+
* @param {string} receiptHandle - the message receipt handle (not the same as the message id)
79+
* @returns {Promise<DeleteMessageCommandOutput>}
80+
*/
81+
export function deleteDlqMessage(dlq, receiptHandle) {
82+
const queueUrl = getDeadLetterQueueUrl(dlq)
83+
const command = new DeleteMessageCommand({
84+
QueueUrl: queueUrl,
85+
ReceiptHandle: receiptHandle
86+
})
87+
return sqsClient.send(command)
88+
}
89+
3390
/**
3491
* Delete event message
3592
* @param {string} queueUrl - the SQS queue url
@@ -46,5 +103,5 @@ export function deleteMessage(queueUrl, message) {
46103
}
47104

48105
/**
49-
* @import { ReceiveMessageCommandInput, ReceiveMessageResult, DeleteMessageCommandOutput, Message } from '@aws-sdk/client-sqs'
106+
* @import { ReceiveMessageCommandInput, ReceiveMessageResult, DeleteMessageCommandOutput, Message, StartMessageMoveTaskResult } from '@aws-sdk/client-sqs'
50107
*/

src/messaging/event.test.js

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import {
22
DeleteMessageCommand,
33
ReceiveMessageCommand,
4-
SQSClient
4+
SQSClient,
5+
StartMessageMoveTaskCommand
56
} from '@aws-sdk/client-sqs'
67
import { mockClient } from 'aws-sdk-client-mock'
78

89
import 'aws-sdk-client-mock-jest'
9-
import { deleteMessage, receiveMessages } from '~/src/messaging/event.js'
10+
import {
11+
deleteDlqMessage,
12+
deleteMessage,
13+
receiveDlqMessages,
14+
receiveMessages,
15+
redriveDlqMessages
16+
} from '~/src/messaging/event.js'
1017

1118
jest.mock('~/src/helpers/logging/logger.js')
1219

@@ -51,8 +58,90 @@ describe('event', () => {
5158
})
5259
})
5360
})
61+
62+
describe('receiveDlqMessages', () => {
63+
it('should receive dead-letter queue messages from form-submissions', async () => {
64+
const receivedMessage = {
65+
Messages: [messageStub]
66+
}
67+
68+
snsMock.on(ReceiveMessageCommand).resolves(receivedMessage)
69+
await receiveDlqMessages('form-submissions')
70+
expect(snsMock).toHaveReceivedCommandWith(ReceiveMessageCommand, {
71+
QueueUrl: expect.any(String),
72+
VisibilityTimeout: 1,
73+
WaitTimeSeconds: 0
74+
})
75+
})
76+
77+
it('should receive dead-letter queue messages from save-and-exit', async () => {
78+
const receivedMessage = {
79+
Messages: [messageStub]
80+
}
81+
82+
snsMock.on(ReceiveMessageCommand).resolves(receivedMessage)
83+
await receiveDlqMessages('save-and-exit')
84+
expect(snsMock).toHaveReceivedCommandWith(ReceiveMessageCommand, {
85+
QueueUrl: expect.any(String),
86+
VisibilityTimeout: 1,
87+
WaitTimeSeconds: 0
88+
})
89+
})
90+
})
91+
92+
describe('redriveDlqMessages', () => {
93+
it('should redrive dead-letter queue messages from form-submissions', async () => {
94+
/**
95+
* @type {StartMessageMoveTaskCommandOutput}
96+
*/
97+
const redriveResult = {
98+
TaskHandle: '123',
99+
$metadata: {}
100+
}
101+
102+
snsMock.on(StartMessageMoveTaskCommand).resolves(redriveResult)
103+
await redriveDlqMessages('form-submissions')
104+
expect(snsMock).toHaveReceivedCommandWith(StartMessageMoveTaskCommand, {
105+
SourceArn: expect.any(String)
106+
})
107+
})
108+
109+
it('should redrive dead-letter queue messages from save-and-exit', async () => {
110+
/**
111+
* @type {StartMessageMoveTaskCommandOutput}
112+
*/
113+
const redriveResult = {
114+
TaskHandle: '123',
115+
$metadata: {}
116+
}
117+
118+
snsMock.on(StartMessageMoveTaskCommand).resolves(redriveResult)
119+
await redriveDlqMessages('save-and-exit')
120+
expect(snsMock).toHaveReceivedCommandWith(StartMessageMoveTaskCommand, {
121+
SourceArn: expect.any(String)
122+
})
123+
})
124+
})
125+
126+
describe('deleteDlqMessage', () => {
127+
it('should delete event message', async () => {
128+
/**
129+
* @type {DeleteMessageCommandOutput}
130+
*/
131+
const deleteResult = {
132+
$metadata: {}
133+
}
134+
135+
snsMock.on(DeleteMessageCommand).resolves(deleteResult)
136+
await deleteDlqMessage('save-and-exit', messageStub.ReceiptHandle)
137+
expect(snsMock).toHaveReceivedCommandWith(DeleteMessageCommand, {
138+
QueueUrl: expect.any(String),
139+
ReceiptHandle: receiptHandle
140+
})
141+
})
142+
})
54143
})
55144

56145
/**
57-
* @import { DeleteMessageCommandOutput } from '@aws-sdk/client-sqs'
146+
* @import { DeleteMessageCommandOutput, StartMessageMoveTaskCommandOutput } from '@aws-sdk/client-sqs'
58147
*/

src/models/form.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ export const resetSaveAndExitLinkResponseSchema = Joi.object({
4646
recordFound: Joi.boolean().required(),
4747
recordUpdated: Joi.boolean().required()
4848
}).label('resetSaveAndExitLinkResponseSchema')
49+
50+
export const dqlSchema = Joi.string().valid('form-submissions', 'save-and-exit')
51+
52+
export const receiptHandleSchema = Joi.string()

0 commit comments

Comments
 (0)