@@ -3,13 +3,13 @@ import {SpiedFunction} from "jest-mock"
33
44import { Logger } from "@aws-lambda-powertools/logger"
55import { DynamoDBDocumentClient , PutCommand } from "@aws-sdk/lib-dynamodb"
6- import { Message } from "@aws-sdk/client-sqs"
6+ import { DeleteMessageBatchCommand , Message } from "@aws-sdk/client-sqs"
77
88import { constructPSUDataItem , mockSQSClient } from "./testHelpers"
99
1010const { mockSend : sqsMockSend } = mockSQSClient ( )
1111
12- const { addPrescriptionToNotificationStateStore, drainQueue} = await import ( "../src/utils" )
12+ const { addPrescriptionToNotificationStateStore, clearCompletedSQSMessages , drainQueue} = await import ( "../src/utils" )
1313
1414const ORIGINAL_ENV = { ...process . env }
1515
@@ -31,13 +31,10 @@ describe("NHS notify lambda helper functions", () => {
3131 it ( "Does not throw an error when the SQS fetch succeeds" , async ( ) => {
3232 const payload = { Messages : Array . from ( { length : 10 } , ( ) => ( constructPSUDataItem ( ) as Message ) ) }
3333
34- // Mock once for the fetch, and once for the delete
35- sqsMockSend
36- . mockImplementationOnce ( ( ) => Promise . resolve ( payload ) )
37- . mockImplementationOnce ( ( ) => Promise . resolve ( { Successful : [ ] } ) )
34+ sqsMockSend . mockImplementationOnce ( ( ) => Promise . resolve ( payload ) )
3835
3936 const messages = await drainQueue ( logger , 10 )
40- expect ( sqsMockSend ) . toHaveBeenCalledTimes ( 2 )
37+ expect ( sqsMockSend ) . toHaveBeenCalledTimes ( 1 )
4138 expect ( messages ) . toStrictEqual ( payload . Messages )
4239 } )
4340
@@ -47,39 +44,92 @@ describe("NHS notify lambda helper functions", () => {
4744 const messages = await drainQueue ( logger , 5 )
4845 expect ( messages ) . toEqual ( [ ] )
4946 expect ( sqsMockSend ) . toHaveBeenCalledTimes ( 1 )
50- // no deletion attempted
5147 } )
5248
5349 it ( "Throws an error if the SQS fetch fails" , async ( ) => {
5450 sqsMockSend . mockImplementation ( ( ) => Promise . reject ( new Error ( "Fetch failed" ) ) )
5551 await expect ( drainQueue ( logger , 10 ) ) . rejects . toThrow ( "Fetch failed" )
5652 } )
5753
58- it ( "Throws an error if the delete batch operation fails" , async ( ) => {
59- const msg = constructPSUDataItem ( ) as Message
60- // first call: fetch, second call: delete
61- sqsMockSend
62- . mockImplementationOnce ( ( ) =>
63- Promise . resolve ( { Messages : [ msg ] } )
64- )
65- . mockImplementationOnce ( ( ) =>
66- Promise . resolve ( {
67- Failed : [ { Id : msg . MessageId ! , Message : "del-error" , Code : "500" } ]
68- } )
69- )
70-
71- await expect ( drainQueue ( logger , 1 ) ) . rejects . toThrow ( "Failed to delete fetched messages from SQS" )
54+ it ( "Throws an error if the SQS URL is not configured" , async ( ) => {
55+ delete process . env . NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL
56+ const { drainQueue} = await import ( "../src/utils" )
57+
58+ await expect ( drainQueue ( logger ) ) . rejects . toThrow ( "NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL not set" )
59+ expect ( errorSpy ) . toHaveBeenCalledWith ( "Notifications SQS URL not configured" )
60+ } )
61+ } )
62+
63+ describe ( "clearCompletedSQSMessages" , ( ) => {
64+ let logger : Logger
65+ let errorSpy : SpiedFunction < ( msg : string , ...meta : Array < unknown > ) => void >
66+
67+ beforeEach ( ( ) => {
68+ jest . resetModules ( )
69+ jest . clearAllMocks ( )
70+
71+ process . env = { ...ORIGINAL_ENV }
72+ logger = new Logger ( { serviceName : "test-service" } )
73+ errorSpy = jest . spyOn ( logger , "error" )
74+ } )
75+
76+ it ( "deletes messages successfully without error" , async ( ) => {
77+ const messages : Array < Message > = [
78+ { MessageId : "msg1" , ReceiptHandle : "rh1" } ,
79+ { MessageId : "msg2" , ReceiptHandle : "rh2" }
80+ ]
81+
82+ // successful delete (no .Failed)
83+ sqsMockSend . mockImplementationOnce ( ( ) => Promise . resolve ( { } ) )
84+
85+ await expect ( clearCompletedSQSMessages ( messages , logger ) )
86+ . resolves
87+ . toBeUndefined ( )
88+
89+ expect ( sqsMockSend ) . toHaveBeenCalledTimes ( 1 )
90+
91+ const cmd = sqsMockSend . mock . calls [ 0 ] [ 0 ]
92+
93+ expect ( cmd ) . toBeInstanceOf ( DeleteMessageBatchCommand )
94+ expect ( ( cmd as DeleteMessageBatchCommand ) . input ) . toEqual ( {
95+ QueueUrl : process . env . NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL ,
96+ Entries : [
97+ { Id : "msg1" , ReceiptHandle : "rh1" } ,
98+ { Id : "msg2" , ReceiptHandle : "rh2" }
99+ ]
100+ } )
101+
102+ expect ( errorSpy ) . not . toHaveBeenCalled ( )
103+ } )
104+
105+ it ( "logs and throws if some deletions fail" , async ( ) => {
106+ const messages : Array < Message > = [
107+ { MessageId : "msg1" , ReceiptHandle : "rh1" }
108+ ]
109+ const failedEntries = [
110+ { Id : "msg1" , SenderFault : true , Code : "Error" , Message : "fail" }
111+ ]
112+
113+ // partial failure
114+ sqsMockSend . mockImplementationOnce ( ( ) => Promise . resolve ( { Failed : failedEntries } ) )
115+
116+ await expect ( clearCompletedSQSMessages ( messages , logger ) )
117+ . rejects
118+ . toThrow ( "Failed to delete fetched messages from SQS" )
119+
72120 expect ( errorSpy ) . toHaveBeenCalledWith (
73121 "Some messages failed to delete" ,
74- { failed : expect . any ( Array ) }
122+ { failed : failedEntries }
75123 )
76124 } )
77125
78126 it ( "Throws an error if the SQS URL is not configured" , async ( ) => {
79127 delete process . env . NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL
80- const { drainQueue } = await import ( "../src/utils" )
128+ const { clearCompletedSQSMessages } = await import ( "../src/utils" )
81129
82- await expect ( drainQueue ( logger ) ) . rejects . toThrow ( "NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL not set" )
130+ await expect ( clearCompletedSQSMessages ( [ ] , logger ) )
131+ . rejects
132+ . toThrow ( "NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL not set" )
83133 expect ( errorSpy ) . toHaveBeenCalledWith ( "Notifications SQS URL not configured" )
84134 } )
85135 } )
0 commit comments