@@ -9,6 +9,8 @@ import {SpiedFunction} from "jest-mock"
99import { Logger } from "@aws-lambda-powertools/logger"
1010import { LogItemMessage , LogItemExtraInput } from "@aws-lambda-powertools/logger/lib/cjs/types/Logger"
1111import * as sqs from "@aws-sdk/client-sqs"
12+ import { BatchProcessingResult , PostDatedSQSMessage } from "../src/types"
13+ import { createMockPostModifiedDataItem } from "./testUtils"
1214
1315export function mockSQSClient ( ) {
1416 const mockSend = jest . fn ( )
@@ -25,7 +27,14 @@ export function mockSQSClient() {
2527
2628const { 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
3039const 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