11import { createHandler } from 'apis/sqs-trigger-lambda' ;
22import type { SQSEvent } from 'aws-lambda' ;
3- import { $TtlItemBusEvent , TtlItemBusEvent } from 'utils' ;
3+ import {
4+ ItemEnqueued ,
5+ MESHInboxMessageDownloaded ,
6+ } from 'digital-letters-events' ;
7+ import itemEnqueuedValidator from 'digital-letters-events/ItemEnqueued.js' ;
48import { randomUUID } from 'node:crypto' ;
59
610jest . mock ( 'node:crypto' , ( ) => ( {
@@ -18,35 +22,45 @@ describe('createHandler', () => {
1822 let logger : any ;
1923 let handler : any ;
2024
21- const validItem : TtlItemBusEvent = {
22- detail : {
23- profileversion : '1.0.0' ,
24- profilepublished : '2025-10' ,
25- id : '550e8400-e29b-41d4-a716-446655440001' ,
26- specversion : '1.0' ,
27- source :
28- '/nhs/england/notify/production/primary/data-plane/digital-letters' ,
29- subject :
30- 'customer/920fca11-596a-4eca-9c47-99f624614658/recipient/769acdd4-6a47-496f-999f-76a6fd2c3959' ,
31- type : 'uk.nhs.notify.digital.letters.sent.v1' ,
32- time : '2023-06-20T12:00:00Z' ,
33- recordedtime : '2023-06-20T12:00:00.250Z' ,
34- severitynumber : 2 ,
35- traceparent : '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01' ,
36- datacontenttype : 'application/json' ,
37- dataschema :
38- 'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10/digital-letter-base-data.schema.json' ,
39- dataschemaversion : '1.0' ,
40- severitytext : 'INFO' ,
41- data : {
42- messageUri : 'https://example.com/ttl/resource' ,
43- 'digital-letter-id' : '123e4567-e89b-12d3-a456-426614174000' ,
44- messageReference : 'ref1' ,
45- senderId : 'sender1' ,
46- } ,
25+ const messageDownloadedEvent : MESHInboxMessageDownloaded = {
26+ id : '550e8400-e29b-41d4-a716-446655440001' ,
27+ specversion : '1.0' ,
28+ source :
29+ '/nhs/england/notify/production/primary/data-plane/digitalletters/mesh' ,
30+ subject :
31+ 'customer/920fca11-596a-4eca-9c47-99f624614658/recipient/769acdd4-6a47-496f-999f-76a6fd2c3959' ,
32+ type : 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1' ,
33+ time : '2023-06-20T12:00:00Z' ,
34+ recordedtime : '2023-06-20T12:00:00.250Z' ,
35+ severitynumber : 2 ,
36+ traceparent : '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01' ,
37+ datacontenttype : 'application/json' ,
38+ dataschema :
39+ 'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10-draft/data/digital-letters-mesh-inbox-message-downloaded-data.schema.json' ,
40+ severitytext : 'INFO' ,
41+ data : {
42+ messageUri : 'https://example.com/ttl/resource' ,
43+ messageReference : 'ref1' ,
44+ senderId : 'sender1' ,
4745 } ,
4846 } ;
4947
48+ const eventBusEvent = {
49+ detail : messageDownloadedEvent ,
50+ } ;
51+
52+ const itemEnqueuedEvent : ItemEnqueued = {
53+ ...messageDownloadedEvent ,
54+ id : '550e8400-e29b-41d4-a716-446655440001' ,
55+ source :
56+ '/nhs/england/notify/production/primary/data-plane/digitalletters/queue' ,
57+ type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
58+ time : '2023-06-20T12:00:00.250Z' ,
59+ recordedtime : '2023-06-20T12:00:00.250Z' ,
60+ dataschema :
61+ 'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10-draft/data/digital-letters-queue-item-enqueued-data.schema.json' ,
62+ } ;
63+
5064 beforeEach ( ( ) => {
5165 createTtl = { send : jest . fn ( ) } ;
5266 eventPublisher = { sendEvents : jest . fn ( ) . mockResolvedValue ( [ ] ) } ;
@@ -55,27 +69,24 @@ describe('createHandler', () => {
5569 } ) ;
5670
5771 it ( 'processes a valid SQS event and returns success' , async ( ) => {
58- jest
59- . spyOn ( $TtlItemBusEvent , 'safeParse' )
60- . mockReturnValue ( { success : true , data : validItem } ) ;
6172 createTtl . send . mockResolvedValue ( 'sent' ) ;
6273 const event : SQSEvent = {
63- Records : [ { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ] ,
74+ Records : [ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ] ,
6475 } as any ;
6576
6677 const res = await handler ( event ) ;
6778
6879 expect ( res . batchItemFailures ) . toEqual ( [ ] ) ;
69- expect ( createTtl . send ) . toHaveBeenCalledWith ( validItem . detail ) ;
70- expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith ( [
71- {
72- ... validItem . detail ,
73- id : '550e8400-e29b-41d4-a716-446655440001' ,
74- time : '2023-06-20T12:00:00.250Z' ,
75- recordedtime : '2023-06-20T12:00:00.250Z' ,
76- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
77- } ,
78- ] ) ;
80+ expect ( createTtl . send ) . toHaveBeenCalledWith ( messageDownloadedEvent ) ;
81+ expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith (
82+ [ itemEnqueuedEvent ] ,
83+ itemEnqueuedValidator ,
84+ ) ;
85+
86+ const publishedEvent = eventPublisher . sendEvents . mock . lastCall ?. [ 0 ] ;
87+ expect ( publishedEvent ) . toHaveLength ( 1 ) ;
88+ expect ( itemEnqueuedValidator ( publishedEvent ?. [ 0 ] ) ) . toBeTruthy ( ) ;
89+
7990 expect ( logger . info ) . toHaveBeenCalledWith ( {
8091 description : 'Processed SQS Event.' ,
8192 failed : 0 ,
@@ -85,10 +96,6 @@ describe('createHandler', () => {
8596 } ) ;
8697
8798 it ( 'handles parse failure and logs error' , async ( ) => {
88- const zodError = { errors : [ ] } as any ;
89- jest
90- . spyOn ( $TtlItemBusEvent , 'safeParse' )
91- . mockReturnValue ( { success : false , error : zodError } ) ;
9299 const event : SQSEvent = {
93100 Records : [ { body : '{}' , messageId : 'msg2' } ] ,
94101 } as any ;
@@ -110,12 +117,9 @@ describe('createHandler', () => {
110117 } ) ;
111118
112119 it ( 'handles createTtl.send failure' , async ( ) => {
113- jest
114- . spyOn ( $TtlItemBusEvent , 'safeParse' )
115- . mockReturnValue ( { success : true , data : validItem } ) ;
116120 createTtl . send . mockResolvedValue ( 'failed' ) ;
117121 const event : SQSEvent = {
118- Records : [ { body : JSON . stringify ( validItem ) , messageId : 'msg3' } ] ,
122+ Records : [ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg3' } ] ,
119123 } as any ;
120124
121125 const res = await handler ( event ) ;
@@ -130,11 +134,9 @@ describe('createHandler', () => {
130134 } ) ;
131135
132136 it ( 'handles thrown error and logs' , async ( ) => {
133- jest . spyOn ( $TtlItemBusEvent , 'safeParse' ) . mockImplementation ( ( ) => {
134- throw new Error ( 'bad json' ) ;
135- } ) ;
137+ createTtl . send . mockRejectedValue ( new Error ( 'TTL service error' ) ) ;
136138 const event : SQSEvent = {
137- Records : [ { body : '{}' , messageId : 'msg4' } ] ,
139+ Records : [ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg4' } ] ,
138140 } as any ;
139141
140142 const res = await handler ( event ) ;
@@ -181,45 +183,23 @@ describe('createHandler', () => {
181183 } ) ;
182184
183185 it ( 'processes multiple successful events and sends them as a batch' , async ( ) => {
184- jest
185- . spyOn ( $TtlItemBusEvent , 'safeParse' )
186- . mockReturnValue ( { success : true , data : validItem } ) ;
187186 createTtl . send . mockResolvedValue ( 'sent' ) ;
188187 const sqsEvent : SQSEvent = {
189188 Records : [
190- { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ,
191- { body : JSON . stringify ( validItem ) , messageId : 'msg2' } ,
192- { body : JSON . stringify ( validItem ) , messageId : 'msg3' } ,
189+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ,
190+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg2' } ,
191+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg3' } ,
193192 ] ,
194193 } as any ;
195194
196195 const res = await handler ( sqsEvent ) ;
197196
198197 expect ( res . batchItemFailures ) . toEqual ( [ ] ) ;
199198 expect ( createTtl . send ) . toHaveBeenCalledTimes ( 3 ) ;
200- expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith ( [
201- {
202- ...validItem . detail ,
203- id : '550e8400-e29b-41d4-a716-446655440001' ,
204- time : '2023-06-20T12:00:00.250Z' ,
205- recordedtime : '2023-06-20T12:00:00.250Z' ,
206- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
207- } ,
208- {
209- ...validItem . detail ,
210- id : '550e8400-e29b-41d4-a716-446655440001' ,
211- time : '2023-06-20T12:00:00.250Z' ,
212- recordedtime : '2023-06-20T12:00:00.250Z' ,
213- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
214- } ,
215- {
216- ...validItem . detail ,
217- id : '550e8400-e29b-41d4-a716-446655440001' ,
218- time : '2023-06-20T12:00:00.250Z' ,
219- recordedtime : '2023-06-20T12:00:00.250Z' ,
220- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
221- } ,
222- ] ) ;
199+ expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith (
200+ [ itemEnqueuedEvent , itemEnqueuedEvent , itemEnqueuedEvent ] ,
201+ itemEnqueuedValidator ,
202+ ) ;
223203 expect ( logger . info ) . toHaveBeenCalledWith ( {
224204 description : 'Processed SQS Event.' ,
225205 failed : 0 ,
@@ -229,39 +209,24 @@ describe('createHandler', () => {
229209 } ) ;
230210
231211 it ( 'handles partial event publishing failures and logs warning' , async ( ) => {
232- jest
233- . spyOn ( $TtlItemBusEvent , 'safeParse' )
234- . mockReturnValue ( { success : true , data : validItem } ) ;
235212 createTtl . send . mockResolvedValue ( 'sent' ) ;
236- const failedEvents = [ validItem ] ;
213+ const failedEvents = [ messageDownloadedEvent ] ;
237214 eventPublisher . sendEvents . mockResolvedValue ( failedEvents ) ;
238215
239216 const event : SQSEvent = {
240217 Records : [
241- { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ,
242- { body : JSON . stringify ( validItem ) , messageId : 'msg2' } ,
218+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ,
219+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg2' } ,
243220 ] ,
244221 } as any ;
245222
246223 const res = await handler ( event ) ;
247224
248225 expect ( res . batchItemFailures ) . toEqual ( [ ] ) ;
249- expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith ( [
250- {
251- ...validItem . detail ,
252- id : '550e8400-e29b-41d4-a716-446655440001' ,
253- time : '2023-06-20T12:00:00.250Z' ,
254- recordedtime : '2023-06-20T12:00:00.250Z' ,
255- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
256- } ,
257- {
258- ...validItem . detail ,
259- id : '550e8400-e29b-41d4-a716-446655440001' ,
260- time : '2023-06-20T12:00:00.250Z' ,
261- recordedtime : '2023-06-20T12:00:00.250Z' ,
262- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
263- } ,
264- ] ) ;
226+ expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith (
227+ [ itemEnqueuedEvent , itemEnqueuedEvent ] ,
228+ itemEnqueuedValidator ,
229+ ) ;
265230 expect ( logger . warn ) . toHaveBeenCalledWith ( {
266231 description : 'Some events failed to publish' ,
267232 failedCount : 1 ,
@@ -270,29 +235,21 @@ describe('createHandler', () => {
270235 } ) ;
271236
272237 it ( 'handles event publishing exception and logs warning' , async ( ) => {
273- jest
274- . spyOn ( $TtlItemBusEvent , 'safeParse' )
275- . mockReturnValue ( { success : true , data : validItem } ) ;
276238 createTtl . send . mockResolvedValue ( 'sent' ) ;
277239 const publishError = new Error ( 'EventBridge error' ) ;
278240 eventPublisher . sendEvents . mockRejectedValue ( publishError ) ;
279241
280242 const event : SQSEvent = {
281- Records : [ { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ] ,
243+ Records : [ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ] ,
282244 } as any ;
283245
284246 const res = await handler ( event ) ;
285247
286248 expect ( res . batchItemFailures ) . toEqual ( [ ] ) ;
287- expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith ( [
288- {
289- ...validItem . detail ,
290- id : '550e8400-e29b-41d4-a716-446655440001' ,
291- time : '2023-06-20T12:00:00.250Z' ,
292- recordedtime : '2023-06-20T12:00:00.250Z' ,
293- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
294- } ,
295- ] ) ;
249+ expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith (
250+ [ itemEnqueuedEvent ] ,
251+ itemEnqueuedValidator ,
252+ ) ;
296253 expect ( logger . warn ) . toHaveBeenCalledWith ( {
297254 err : publishError ,
298255 description : 'Failed to send events to EventBridge' ,
@@ -301,13 +258,10 @@ describe('createHandler', () => {
301258 } ) ;
302259
303260 it ( 'does not call eventPublisher when no successful events' , async ( ) => {
304- jest
305- . spyOn ( $TtlItemBusEvent , 'safeParse' )
306- . mockReturnValue ( { success : true , data : validItem } ) ;
307261 createTtl . send . mockResolvedValue ( 'failed' ) ;
308262
309263 const event : SQSEvent = {
310- Records : [ { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ] ,
264+ Records : [ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ] ,
311265 } as any ;
312266
313267 const res = await handler ( event ) ;
@@ -323,20 +277,15 @@ describe('createHandler', () => {
323277 } ) ;
324278
325279 it ( 'handles mixed success and failure scenarios' , async ( ) => {
326- jest
327- . spyOn ( $TtlItemBusEvent , 'safeParse' )
328- . mockReturnValueOnce ( { success : true , data : validItem } )
329- . mockReturnValueOnce ( { success : false , error : { errors : [ ] } as any } )
330- . mockReturnValueOnce ( { success : true , data : validItem } ) ;
331280 createTtl . send
332281 . mockResolvedValueOnce ( 'sent' )
333282 . mockResolvedValueOnce ( 'failed' ) ;
334283
335284 const event : SQSEvent = {
336285 Records : [
337- { body : JSON . stringify ( validItem ) , messageId : 'msg1' } ,
286+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg1' } ,
338287 { body : '{}' , messageId : 'msg2' } ,
339- { body : JSON . stringify ( validItem ) , messageId : 'msg3' } ,
288+ { body : JSON . stringify ( eventBusEvent ) , messageId : 'msg3' } ,
340289 ] ,
341290 } as any ;
342291
@@ -346,15 +295,10 @@ describe('createHandler', () => {
346295 { itemIdentifier : 'msg2' } ,
347296 { itemIdentifier : 'msg3' } ,
348297 ] ) ;
349- expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith ( [
350- {
351- ...validItem . detail ,
352- id : '550e8400-e29b-41d4-a716-446655440001' ,
353- time : '2023-06-20T12:00:00.250Z' ,
354- recordedtime : '2023-06-20T12:00:00.250Z' ,
355- type : 'uk.nhs.notify.digital.letters.queue.item.enqueued.v1' ,
356- } ,
357- ] ) ;
298+ expect ( eventPublisher . sendEvents ) . toHaveBeenCalledWith (
299+ [ itemEnqueuedEvent ] ,
300+ itemEnqueuedValidator ,
301+ ) ;
358302 expect ( logger . info ) . toHaveBeenCalledWith ( {
359303 description : 'Processed SQS Event.' ,
360304 failed : 2 ,
0 commit comments