11import type { SQSEvent , SQSRecord } from 'aws-lambda' ;
22import { mock } from 'jest-mock-extended' ;
33import { Logger , Sender } from 'utils' ;
4- import { PDMResourceAvailable } from 'digital-letters-events' ;
54import { NotifyMessageProcessor } from 'app/notify-message-processor' ;
6- import { SenderManagement } from 'sender-management' ;
5+ import { ISenderManagement } from 'sender-management' ;
76import { EventPublisherFacade } from 'infra/event-publisher-facade' ;
87import { SqsHandlerDependencies , createHandler } from 'apis/sqs-handler' ;
98import { parseSqsRecord } from 'app/parse-sqs-message' ;
109import { InvalidPdmResourceAvailableEvent } from 'domain/invalid-pdm-resource-available-event' ;
1110import { RequestNotifyError } from 'domain/request-notify-error' ;
11+ import { validPdmEvent , validSender } from '__tests__/constants' ;
1212
1313jest . mock ( 'app/parse-sqs-message' ) ;
1414
1515const mockLogger = mock < Logger > ( ) ;
1616const mockNotifyMessageProcessor = mock < NotifyMessageProcessor > ( ) ;
17- const mockSenderManagement = mock < SenderManagement > ( ) ;
17+ const mockSenderManagement = mock < ISenderManagement > ( ) ;
1818const mockEventPublisherFacade = mock < EventPublisherFacade > ( ) ;
1919const mockParseSqsRecord = jest . mocked ( parseSqsRecord ) ;
2020
@@ -31,36 +31,11 @@ describe('createHandler', () => {
3131 const messageReference = 'msg-ref-123' ;
3232 const notifyId = 'notify-id-123' ;
3333
34- const mockSender : Sender = {
35- senderId,
36- routingConfigId,
37- senderName : 'Test Sender' ,
38- meshMailboxSenderId : 'meshMailBoxSender-123' ,
39- meshMailboxReportsId : 'meshMailBoxReports-123' ,
40- fallbackWaitTimeSeconds : 100 ,
41- } ;
42-
43- const mockPdmEvent : PDMResourceAvailable = {
44- id : 'event-id-123' ,
45- source : 'urn:nhs:names:services:notify:pdm' ,
46- specversion : '1.0' ,
47- type : 'uk.nhs.notify.digital.letters.pdm.resource.available.v1' ,
48- time : '2025-12-15T10:00:00Z' ,
49- datacontenttype : 'application/json' ,
50- data : {
51- senderId,
52- messageReference,
53- resourceId : 'ResourceId-123' ,
54- nhsNumber : '9991234566' ,
55- odsCode : 'A12345' ,
56- } ,
57- } ;
58-
5934 const createSqsRecord = ( messageId : string ) : SQSRecord => ( {
6035 messageId,
6136 receiptHandle : 'receipt-handle' ,
6237 body : JSON . stringify ( {
63- detail : mockPdmEvent ,
38+ detail : validPdmEvent ,
6439 } ) ,
6540 attributes : {
6641 ApproximateReceiveCount : '1' ,
@@ -90,8 +65,8 @@ describe('createHandler', () => {
9065 const sqsEvent = createSqsEvent ( 1 ) ;
9166 const handler = createHandler ( dependencies ) ;
9267
93- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
94- mockSenderManagement . getSender . mockReturnValueOnce ( mockSender ) ;
68+ mockParseSqsRecord . mockReturnValueOnce ( validPdmEvent ) ;
69+ mockSenderManagement . getSender . mockResolvedValue ( validSender ) ;
9570 mockNotifyMessageProcessor . process . mockResolvedValueOnce ( notifyId ) ;
9671
9772 const result = await handler ( sqsEvent ) ;
@@ -107,32 +82,27 @@ describe('createHandler', () => {
10782 sqsEvent . Records [ 0 ] ,
10883 mockLogger ,
10984 ) ;
110- expect ( mockSenderManagement . getSender ) . toHaveBeenCalledWith ( senderId ) ;
85+ expect ( mockSenderManagement . getSender ) . toHaveBeenCalledWith ( { senderId : senderId } ) ;
11186 expect ( mockNotifyMessageProcessor . process ) . toHaveBeenCalledTimes ( 1 ) ;
11287 expect (
11388 mockEventPublisherFacade . publishMessageRequestSubmitted ,
11489 ) . toHaveBeenCalledTimes ( 1 ) ;
11590 } ) ;
116- } ) ;
11791
118- describe ( 'when sender has no routing config' , ( ) => {
11992 it ( 'skips the message and publishes a skipped event' , async ( ) => {
12093 const sqsEvent = createSqsEvent ( 1 ) ;
12194 const handler = createHandler ( dependencies ) ;
12295 const senderWithoutRouting : Sender = {
123- ...mockSender ,
96+ ...validSender ,
12497 routingConfigId : undefined ,
12598 } ;
12699
127- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
128- mockSenderManagement . getSender . mockReturnValueOnce ( senderWithoutRouting ) ;
100+ mockParseSqsRecord . mockReturnValueOnce ( validPdmEvent ) ;
101+ mockSenderManagement . getSender . mockResolvedValue ( senderWithoutRouting ) ;
129102
130103 const result = await handler ( sqsEvent ) ;
131104
132105 expect ( result ) . toEqual ( { batchItemFailures : [ ] } ) ;
133- expect ( mockLogger . debug ) . toHaveBeenCalledWith (
134- `No routing config for sender ${ senderId } , skipping message` ,
135- ) ;
136106 expect ( mockNotifyMessageProcessor . process ) . not . toHaveBeenCalled ( ) ;
137107 expect (
138108 mockEventPublisherFacade . publishMessageRequestSkipped ,
@@ -145,8 +115,8 @@ describe('createHandler', () => {
145115 const sqsEvent = createSqsEvent ( 3 ) ;
146116 const handler = createHandler ( dependencies ) ;
147117
148- mockParseSqsRecord . mockReturnValue ( mockPdmEvent ) ;
149- mockSenderManagement . getSender . mockReturnValue ( mockSender ) ;
118+ mockParseSqsRecord . mockReturnValue ( validPdmEvent ) ;
119+ mockSenderManagement . getSender . mockResolvedValue ( validSender ) ;
150120 mockNotifyMessageProcessor . process . mockResolvedValue ( notifyId ) ;
151121
152122 const result = await handler ( sqsEvent ) ;
@@ -161,6 +131,30 @@ describe('createHandler', () => {
161131 expect ( mockParseSqsRecord ) . toHaveBeenCalledTimes ( 3 ) ;
162132 expect ( mockNotifyMessageProcessor . process ) . toHaveBeenCalledTimes ( 3 ) ;
163133 } ) ;
134+
135+ it ( 'returns only failed message IDs' , async ( ) => {
136+ const sqsEvent = createSqsEvent ( 3 ) ;
137+ const handler = createHandler ( dependencies ) ;
138+
139+ mockParseSqsRecord
140+ . mockReturnValueOnce ( validPdmEvent )
141+ . mockImplementationOnce ( ( ) => {
142+ throw new Error ( 'Parse error' ) ;
143+ } )
144+ . mockReturnValueOnce ( validPdmEvent ) ;
145+
146+ mockSenderManagement . getSender . mockResolvedValue ( validSender ) ;
147+ mockNotifyMessageProcessor . process . mockResolvedValue ( notifyId ) ;
148+
149+ const result = await handler ( sqsEvent ) ;
150+
151+ expect ( result ) . toEqual ( {
152+ batchItemFailures : [ { itemIdentifier : 'message-id-2' } ] ,
153+ } ) ;
154+ expect ( mockLogger . info ) . toHaveBeenCalledWith (
155+ '2 of 3 records processed successfully' ,
156+ ) ;
157+ } ) ;
164158 } ) ;
165159
166160 describe ( 'when parseSqsRecord throws InvalidPdmResourceAvailableEvent' , ( ) => {
@@ -189,21 +183,15 @@ describe('createHandler', () => {
189183 } ) ;
190184 } ) ;
191185
192- describe ( 'when processing throws RequestNotifyError ' , ( ) => {
193- it ( 'marks the message as failed for retry since error lacks messageReference ' , async ( ) => {
186+ describe ( 'when processing throws error ' , ( ) => {
187+ it ( 'marks the message as failed as is not a RequestNotifyError ' , async ( ) => {
194188 const sqsEvent = createSqsEvent ( 1 ) ;
195189 const handler = createHandler ( dependencies ) ;
196190 const { messageId } = sqsEvent . Records [ 0 ] ;
197- const errorCode = 'VALIDATION_ERROR' ;
198- const correlationId = 'corr-123' ;
199- const error = new RequestNotifyError (
200- new Error ( 'Validation failed' ) ,
201- correlationId ,
202- errorCode ,
203- ) ;
191+ const error = new Error ( 'Validation failed' ) ;
204192
205- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
206- mockSenderManagement . getSender . mockReturnValueOnce ( mockSender ) ;
193+ mockParseSqsRecord . mockReturnValueOnce ( validPdmEvent ) ;
194+ mockSenderManagement . getSender . mockResolvedValue ( validSender ) ;
207195 mockNotifyMessageProcessor . process . mockRejectedValueOnce ( error ) ;
208196
209197 const result = await handler ( sqsEvent ) ;
@@ -237,8 +225,8 @@ describe('createHandler', () => {
237225 // Add messageReference property dynamically to trigger the terminal error path
238226 ( error as any ) . messageReference = messageReference ;
239227
240- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
241- mockSenderManagement . getSender . mockReturnValueOnce ( mockSender ) ;
228+ mockParseSqsRecord . mockReturnValueOnce ( validPdmEvent ) ;
229+ mockSenderManagement . getSender . mockResolvedValue ( validSender ) ;
242230 mockNotifyMessageProcessor . process . mockRejectedValueOnce ( error ) ;
243231
244232 const result = await handler ( sqsEvent ) ;
@@ -266,75 +254,4 @@ describe('createHandler', () => {
266254 ) ;
267255 } ) ;
268256 } ) ;
269-
270- describe ( 'when processing throws a generic error' , ( ) => {
271- it ( 'marks the message as failed for retry' , async ( ) => {
272- const sqsEvent = createSqsEvent ( 1 ) ;
273- const handler = createHandler ( dependencies ) ;
274- const { messageId } = sqsEvent . Records [ 0 ] ;
275- const error = new Error ( 'Unexpected error' ) ;
276-
277- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
278- mockSenderManagement . getSender . mockReturnValueOnce ( mockSender ) ;
279- mockNotifyMessageProcessor . process . mockRejectedValueOnce ( error ) ;
280-
281- const result = await handler ( sqsEvent ) ;
282-
283- expect ( result ) . toEqual ( {
284- batchItemFailures : [ { itemIdentifier : messageId } ] ,
285- } ) ;
286- expect ( mockLogger . warn ) . toHaveBeenCalledWith ( {
287- error : error . message ,
288- description : 'Failed processing message' ,
289- messageId,
290- } ) ;
291- expect (
292- mockEventPublisherFacade . publishMessageRequestRejected ,
293- ) . not . toHaveBeenCalled ( ) ;
294- } ) ;
295- } ) ;
296-
297- describe ( 'when processing mixed success and failure records' , ( ) => {
298- it ( 'returns only failed message IDs' , async ( ) => {
299- const sqsEvent = createSqsEvent ( 3 ) ;
300- const handler = createHandler ( dependencies ) ;
301-
302- mockParseSqsRecord
303- . mockReturnValueOnce ( mockPdmEvent )
304- . mockImplementationOnce ( ( ) => {
305- throw new Error ( 'Parse error' ) ;
306- } )
307- . mockReturnValueOnce ( mockPdmEvent ) ;
308-
309- mockSenderManagement . getSender . mockReturnValue ( mockSender ) ;
310- mockNotifyMessageProcessor . process . mockResolvedValue ( notifyId ) ;
311-
312- const result = await handler ( sqsEvent ) ;
313-
314- expect ( result ) . toEqual ( {
315- batchItemFailures : [ { itemIdentifier : 'message-id-2' } ] ,
316- } ) ;
317- expect ( mockLogger . info ) . toHaveBeenCalledWith (
318- '2 of 3 records processed successfully' ,
319- ) ;
320- } ) ;
321- } ) ;
322-
323- describe ( 'when notifyMessageProcessor returns undefined' , ( ) => {
324- it ( 'does not publish submitted event' , async ( ) => {
325- const sqsEvent = createSqsEvent ( 1 ) ;
326- const handler = createHandler ( dependencies ) ;
327-
328- mockParseSqsRecord . mockReturnValueOnce ( mockPdmEvent ) ;
329- mockSenderManagement . getSender . mockReturnValueOnce ( mockSender ) ;
330- mockNotifyMessageProcessor . process . mockResolvedValueOnce ( ) ;
331-
332- const result = await handler ( sqsEvent ) ;
333-
334- expect ( result ) . toEqual ( { batchItemFailures : [ ] } ) ;
335- expect (
336- mockEventPublisherFacade . publishMessageRequestSubmitted ,
337- ) . not . toHaveBeenCalled ( ) ;
338- } ) ;
339- } ) ;
340257} ) ;
0 commit comments