Skip to content

Commit 2e16a2d

Browse files
authored
Merge pull request #166 from DEFRA/fix/df-995-delete-dlq
fix/df-995: Delete DLQ message by id
2 parents e3d5a84 + 7acaace commit 2e16a2d

4 files changed

Lines changed: 66 additions & 30 deletions

File tree

src/messaging/event.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@aws-sdk/client-sqs'
66

77
import { config } from '~/src/config/index.js'
8+
import { createLogger } from '~/src/helpers/logging/logger.js'
89
import { sqsClient } from '~/src/messaging/sqs.js'
910

1011
export const receiveMessageTimeout = config.get('receiveMessageTimeout')
@@ -14,6 +15,8 @@ const deadLetterQueueArn = config.get('sqsEventsDlqArn')
1415
const maxNumberOfMessages = config.get('maxNumberOfMessages')
1516
const visibilityTimeout = config.get('visibilityTimeout')
1617

18+
const logger = createLogger()
19+
1720
/**
1821
* @type {ReceiveMessageCommandInput}
1922
*/
@@ -58,17 +61,41 @@ export function redriveDlqMessages() {
5861
}
5962

6063
/**
61-
* Delete event message
62-
* @param {string} receiptHandle
63-
* @returns {Promise<DeleteMessageCommandOutput>}
64+
* Delete DLQ message by messageId
65+
* This has to be done as a combined 'read then delete' (while using a visibility timeout of non-zero)
66+
* otherwise the receipt handles become stale and the delete operation doesn't work.
67+
* @param {string} messageId
6468
*/
65-
export function deleteDlqMessage(receiptHandle) {
66-
const command = new DeleteMessageCommand({
69+
export async function deleteDlqMessage(messageId) {
70+
const receiveCommand = new ReceiveMessageCommand({
6771
QueueUrl: deadLetterQueueUrl,
68-
ReceiptHandle: receiptHandle
72+
MaxNumberOfMessages: 10,
73+
VisibilityTimeout: 2,
74+
WaitTimeSeconds: 0
6975
})
76+
const messageResponse = await sqsClient.send(receiveCommand)
7077

71-
return sqsClient.send(command)
78+
const messages = messageResponse.Messages
79+
? messageResponse.Messages.filter((m) => m.MessageId === messageId)
80+
: undefined
81+
if (!messages?.length) {
82+
const errorText = `Message with id ${messageId} not found in notify-listener DLQ`
83+
logger.info(errorText)
84+
throw new Error(errorText)
85+
}
86+
87+
logger.info(
88+
`[DLQ] Number of messages found with id ${messageId}: ${messages.length}`
89+
)
90+
for (const message of messages) {
91+
const deleteCommand = new DeleteMessageCommand({
92+
QueueUrl: deadLetterQueueUrl,
93+
ReceiptHandle: message.ReceiptHandle
94+
})
95+
logger.info(`[DLQ] Deleting message with id ${messageId}`)
96+
await sqsClient.send(deleteCommand)
97+
logger.info(`[DLQ] Deleted message with id ${messageId}`)
98+
}
7299
}
73100

74101
/**

src/messaging/event.test.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,36 @@ describe('event', () => {
9494

9595
describe('deleteDlqMessage', () => {
9696
it('should delete event message', async () => {
97-
/**
98-
* @type {DeleteMessageCommandOutput}
99-
*/
100-
const deleteResult = {
101-
$metadata: {}
97+
const receivedMessage = {
98+
Messages: [messageStub, messageStub, messageStub]
10299
}
103100

104-
snsMock.on(DeleteMessageCommand).resolves(deleteResult)
105-
await deleteDlqMessage(messageStub.ReceiptHandle)
101+
snsMock.on(ReceiveMessageCommand).resolves(receivedMessage)
102+
await deleteDlqMessage(messageStub.MessageId)
103+
expect(snsMock).toHaveReceivedCommandWith(ReceiveMessageCommand, {
104+
QueueUrl: expect.any(String),
105+
MaxNumberOfMessages: 10,
106+
VisibilityTimeout: 2,
107+
WaitTimeSeconds: 0
108+
})
106109
expect(snsMock).toHaveReceivedCommandWith(DeleteMessageCommand, {
107110
QueueUrl: expect.any(String),
108111
ReceiptHandle: receiptHandle
109112
})
110113
})
114+
115+
it('should throw if message not found', async () => {
116+
const receivedMessage = {
117+
Messages: []
118+
}
119+
120+
snsMock.on(ReceiveMessageCommand).resolves(receivedMessage)
121+
await expect(() =>
122+
deleteDlqMessage(messageStub.MessageId)
123+
).rejects.toThrow(
124+
'Message with id 31cb6fff-8317-412e-8488-308d099034c4 not found in notify-listener DLQ'
125+
)
126+
})
111127
})
112128
})
113129

src/routes/admin.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ const messageIdSchema = Joi.object({
1616
messageId: Joi.string().required()
1717
})
1818

19-
const receiptHandleSchema = Joi.object({
20-
receiptHandle: Joi.string().required()
21-
})
22-
2319
export default [
2420
/**
2521
* @satisfies {ServerRoute}
@@ -58,25 +54,25 @@ export default [
5854
}),
5955

6056
/**
61-
* @satisfies {ServerRoute<{ Params: { messageId: string }, Payload: { receiptHandle: string } }>}
57+
* @satisfies {ServerRoute<{ Params: { messageId: string } }>}
6258
*/
6359
({
6460
method: 'DELETE',
6561
path: '/admin/deadletter/{messageId}',
6662
async handler(request, h) {
67-
const { params, payload } = request
68-
logger.info(`Deleting DLQ message ${params.messageId}`)
69-
await deleteDlqMessage(payload.receiptHandle)
70-
logger.info(`Deleted DLQ message ${params.messageId}`)
63+
const { params } = request
64+
const { messageId } = params
65+
logger.info(`Deleting DLQ message ${messageId}`)
66+
await deleteDlqMessage(messageId)
67+
logger.info(`Deleted DLQ message ${messageId}`)
7168
return h.response({ message: 'success' }).code(OK_RESPONSE)
7269
},
7370
options: {
7471
auth: {
7572
scope: [`+${Scopes.DeadLetterQueues}`]
7673
},
7774
validate: {
78-
params: messageIdSchema,
79-
payload: receiptHandleSchema
75+
params: messageIdSchema
8076
}
8177
}
8278
})

src/routes/admin.test.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,13 @@ describe('Admin routes', () => {
6262
const response = await server.inject({
6363
method: 'DELETE',
6464
url: '/admin/deadletter/message-id',
65-
auth,
66-
payload: {
67-
receiptHandle: 'receipt-handle'
68-
}
65+
auth
6966
})
7067

7168
expect(response.statusCode).toEqual(okStatusCode)
7269
expect(response.headers['content-type']).toContain(jsonContentType)
7370
expect(response.result).toEqual({ message: 'success' })
74-
expect(deleteDlqMessage).toHaveBeenCalledWith('receipt-handle')
71+
expect(deleteDlqMessage).toHaveBeenCalledWith('message-id')
7572
})
7673
})
7774
})

0 commit comments

Comments
 (0)