Skip to content

Commit a5e03b7

Browse files
committed
Expand SQS test suite
1 parent 28974e0 commit a5e03b7

2 files changed

Lines changed: 236 additions & 9 deletions

File tree

packages/postDatedLambda/src/sqs.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ export async function removeSQSMessages(
164164
messageIds: entries.map((e) => e.Id)
165165
})
166166
}
167+
168+
logger.info(`Successfully removed ${delResult.Successful?.length ?? 0} messages from SQS`)
167169
}
168170

169171
/**
@@ -206,15 +208,21 @@ export async function returnMessagesToQueue(
206208
QueueUrl: sqsUrl,
207209
Entries: entries
208210
})
209-
const result = await sqs.send(changeVisibilityCmd)
210211

211-
if (result.Failed && result.Failed.length > 0) {
212-
logger.error("Some messages failed to have visibility changed in this batch", {failed: result.Failed})
213-
} else {
214-
logger.info("Successfully returned SQS messages to queue", {
215-
result: result,
216-
messageIds: entries.map((e) => e.Id)
217-
})
212+
try {
213+
const result = await sqs.send(changeVisibilityCmd)
214+
215+
if (result.Failed && result.Failed.length > 0) {
216+
logger.error("Some messages failed to have visibility changed in this batch", {failed: result.Failed})
217+
} else {
218+
logger.info("Successfully returned SQS messages to queue", {
219+
result: result,
220+
messageIds: entries.map((e) => e.Id)
221+
})
222+
}
223+
} catch (error) {
224+
const message = error instanceof Error ? error.message : "Failed to change SQS message visibility"
225+
logger.error(message, {error})
218226
}
219227
}
220228

packages/postDatedLambda/tests/testSqs.test.ts

Lines changed: 220 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {SpiedFunction} from "jest-mock"
99
import {Logger} from "@aws-lambda-powertools/logger"
1010
import {LogItemMessage, LogItemExtraInput} from "@aws-lambda-powertools/logger/lib/cjs/types/Logger"
1111
import * as sqs from "@aws-sdk/client-sqs"
12+
import {BatchProcessingResult, PostDatedSQSMessage} from "../src/types"
13+
import {createMockPostModifiedDataItem} from "./testUtils"
1214

1315
export function mockSQSClient() {
1416
const mockSend = jest.fn()
@@ -25,7 +27,14 @@ export function mockSQSClient() {
2527

2628
const {mockSend} = mockSQSClient()
2729

28-
const {getQueueUrl, reportQueueStatus} = await import("../src/sqs")
30+
const {
31+
getQueueUrl,
32+
reportQueueStatus,
33+
receivePostDatedSQSMessages,
34+
removeSQSMessages,
35+
returnMessagesToQueue,
36+
handleProcessedMessages
37+
} = await import("../src/sqs")
2938

3039
const ORIGINAL_ENV = {...process.env}
3140

@@ -115,4 +124,214 @@ describe("sqs", () => {
115124
)
116125
})
117126
})
127+
128+
describe("receivePostDatedSQSMessages", () => {
129+
it("Should receive messages from the SQS queue", async () => {
130+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
131+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
132+
133+
// Mock SQS response with messages
134+
const mockMessages = [
135+
{
136+
MessageId: "1",
137+
Body: JSON.stringify({PrescriptionID: "presc1"})
138+
},
139+
{
140+
MessageId: "2",
141+
Body: JSON.stringify({PrescriptionID: "presc2"})
142+
}
143+
]
144+
mockSend.mockReturnValueOnce({
145+
Messages: mockMessages
146+
})
147+
148+
const result = await receivePostDatedSQSMessages(logger)
149+
150+
expect(mockSend).toHaveBeenCalledTimes(1)
151+
expect(result).toHaveLength(2)
152+
expect(result[0].MessageId).toBe("1")
153+
expect(result[0].prescriptionData.PrescriptionID).toBe("presc1")
154+
expect(result[1].MessageId).toBe("2")
155+
expect(result[1].prescriptionData.PrescriptionID).toBe("presc2")
156+
})
157+
158+
it("Should return an empty array if no messages are received", async () => {
159+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
160+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
161+
162+
// Mock SQS response with no messages
163+
mockSend.mockReturnValueOnce({
164+
Messages: []
165+
})
166+
167+
const result = await receivePostDatedSQSMessages(logger)
168+
169+
expect(mockSend).toHaveBeenCalledTimes(1)
170+
expect(result).toHaveLength(0)
171+
})
172+
})
173+
174+
describe("removeSQSMessages", () => {
175+
it("Should remove messages from the SQS queue", async () => {
176+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
177+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
178+
179+
const messagesToRemove = [
180+
{MessageId: "1", ReceiptHandle: "handle1"},
181+
{MessageId: "2", ReceiptHandle: "handle2"}
182+
]
183+
184+
// Mock SQS delete response
185+
mockSend.mockReturnValueOnce({
186+
Successful: messagesToRemove.map((msg) => ({Id: msg.MessageId})),
187+
Failed: []
188+
})
189+
190+
await removeSQSMessages(logger, messagesToRemove)
191+
192+
expect(mockSend).toHaveBeenCalledTimes(1)
193+
expect(infoSpy).toHaveBeenCalledWith("Successfully removed 2 messages from SQS")
194+
})
195+
196+
it("Should handle empty message array gracefully", async () => {
197+
await removeSQSMessages(logger, [])
198+
199+
expect(mockSend).toHaveBeenCalledTimes(0)
200+
expect(infoSpy).toHaveBeenCalledWith("No messages to delete")
201+
})
202+
203+
it("Should log errors but not throw if deletion fails", async () => {
204+
// We don't want to throw on failed deletions, as this would cause
205+
// later batches to be skipped unnecessarily.
206+
// The messages that are failed to delete will become visible and be processed again after the visibility timeout
207+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
208+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
209+
210+
const messagesToRemove = [
211+
{MessageId: "1", ReceiptHandle: "handle1"},
212+
{MessageId: "2", ReceiptHandle: "handle2"}
213+
]
214+
215+
// Mock SQS delete response with failures
216+
mockSend.mockReturnValueOnce({
217+
Successful: [{Id: "1"}],
218+
Failed: [{Id: "2", Message: "Some error"}]
219+
})
220+
221+
await removeSQSMessages(logger, messagesToRemove)
222+
223+
expect(mockSend).toHaveBeenCalledTimes(1)
224+
expect(errorSpy).toHaveBeenCalledWith("Some messages failed to delete", {
225+
failed: [{Id: "2", Message: "Some error"}]
226+
})
227+
expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS")
228+
})
229+
})
230+
231+
describe("returnMessagesToQueue", () => {
232+
it("Should return messages to the SQS queue by updating their visibility timeout", async () => {
233+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
234+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
235+
236+
const messagesToReturn = [
237+
{MessageId: "1", ReceiptHandle: "handle1"},
238+
{MessageId: "2", ReceiptHandle: "handle2"}
239+
]
240+
241+
// Mock SQS change visibility response
242+
mockSend.mockReturnValueOnce({
243+
// No specific return value needed for ChangeMessageVisibilityBatch
244+
})
245+
246+
await returnMessagesToQueue(logger, messagesToReturn)
247+
248+
expect(mockSend).toHaveBeenCalledTimes(1)
249+
expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", {
250+
numberOfMessages: 2,
251+
idAndTimeouts: [
252+
{id: "1", visibilityTimeout: 300},
253+
{id: "2", visibilityTimeout: 300}
254+
]
255+
})
256+
expect(errorSpy).not.toHaveBeenCalled()
257+
})
258+
259+
it("Should handle empty message array gracefully", async () => {
260+
await returnMessagesToQueue(logger, [])
261+
262+
expect(mockSend).toHaveBeenCalledTimes(0)
263+
expect(infoSpy).toHaveBeenCalledWith("No messages to return to queue")
264+
})
265+
266+
it("should log an error if SQS change visibility fails", async () => {
267+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
268+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
269+
270+
const messagesToReturn = [
271+
{MessageId: "1", ReceiptHandle: "handle1"}
272+
]
273+
274+
// Mock SQS change visibility to throw an error
275+
const expectedError = new Error("SQS change visibility failed")
276+
mockSend.mockImplementationOnce(() => {
277+
throw expectedError
278+
})
279+
280+
await returnMessagesToQueue(logger, messagesToReturn)
281+
282+
expect(mockSend).toHaveBeenCalledTimes(1)
283+
expect(errorSpy).toHaveBeenCalledWith("SQS change visibility failed", {error: expectedError})
284+
})
285+
})
286+
287+
describe("handleProcessedMessages", () => {
288+
it("should remove matured messages and return immature messages to the queue", async () => {
289+
const testUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/test-queue"
290+
process.env.POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL = testUrl
291+
292+
const maturedMessages: Array<PostDatedSQSMessage> = [
293+
{MessageId: "1", ReceiptHandle: "handle1", prescriptionData: createMockPostModifiedDataItem({})}
294+
]
295+
const immatureMessages: Array<PostDatedSQSMessage> = [
296+
{MessageId: "2", ReceiptHandle: "handle2", prescriptionData: createMockPostModifiedDataItem({})}
297+
]
298+
299+
const batchResult: BatchProcessingResult = {
300+
maturedPrescriptionUpdates: maturedMessages,
301+
immaturePrescriptionUpdates: immatureMessages
302+
}
303+
304+
// Mock SQS responses
305+
mockSend
306+
.mockReturnValueOnce({
307+
Successful: [{Id: "1"}],
308+
Failed: []
309+
}) // For removeSQSMessages
310+
.mockReturnValueOnce({}) // For returnMessagesToQueue
311+
312+
await handleProcessedMessages(batchResult, logger)
313+
314+
expect(mockSend).toHaveBeenCalledTimes(2)
315+
expect(infoSpy).toHaveBeenCalledWith("Successfully removed 1 messages from SQS")
316+
expect(infoSpy).toHaveBeenCalledWith("Returning messages to queue with timeouts", {
317+
numberOfMessages: 1,
318+
idAndTimeouts: [
319+
{id: "2", visibilityTimeout: 300}
320+
]
321+
})
322+
})
323+
324+
it("should handle empty matured and immature message arrays gracefully", async () => {
325+
const batchResult: BatchProcessingResult = {
326+
maturedPrescriptionUpdates: [],
327+
immaturePrescriptionUpdates: []
328+
}
329+
330+
await handleProcessedMessages(batchResult, logger)
331+
332+
expect(mockSend).toHaveBeenCalledTimes(0)
333+
expect(infoSpy).not.toHaveBeenCalledWith("Successfully removed")
334+
expect(infoSpy).not.toHaveBeenCalledWith("Returning messages to queue with timeouts", expect.anything())
335+
})
336+
})
118337
})

0 commit comments

Comments
 (0)