Skip to content

Commit f509cf4

Browse files
authored
CCM-12896: Use Generated Event Types and Validators in Lambdas (#148)
* CCM-12896: Update ttl-create-lambda to use new types/validators * CCM-12896: Update ttl-handle-expiry-lambda to use new types/validators * CCM-12896: Update playwright tests to use new types/validators * CCM-12896: Remove manually-created event types * CCM-12896: Make acceptance tests generate dependencies * CCM-12896: Remove dataschemaversion from eventbridge rule * CCM-12896: Add config for VSCode jest plugin * CCM-12896: Extend create TTL component test to check ItemEnqueued event is published * CCM-12896: Make handle-ttl component test use valid events
1 parent 6487e77 commit f509cf4

File tree

25 files changed

+358
-733
lines changed

25 files changed

+358
-733
lines changed

.github/actions/acceptance-tests/action.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ runs:
3030
run: |
3131
npm ci
3232
33+
- name: "Generate dependencies"
34+
shell: bash
35+
run: |
36+
npm run generate-dependencies
37+
3338
- name: Run test - ${{ inputs.testType }}
3439
shell: bash
3540
run: |

infrastructure/terraform/components/dl/cloudwatch_event_rule_mesh_inbox_message_downloaded.tf

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ resource "aws_cloudwatch_event_rule" "mesh_inbox_message_downloaded" {
88
"type" : [
99
"uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1"
1010
],
11-
"dataschemaversion" : [{
12-
"prefix" : "1."
13-
}]
1411
}
1512
})
1613
}

lambdas/ttl-create-lambda/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"dependencies": {
33
"@aws-sdk/lib-dynamodb": "^3.908.0",
4-
"utils": "^0.0.1",
5-
"zod": "^4.1.12"
4+
"digital-letters-events": "^0.0.1",
5+
"utils": "^0.0.1"
66
},
77
"devDependencies": {
88
"@tsconfig/node22": "^22.0.2",

lambdas/ttl-create-lambda/src/__tests__/apis/sqs-trigger-lambda.test.ts

Lines changed: 81 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createHandler } from 'apis/sqs-trigger-lambda';
22
import 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';
48
import { randomUUID } from 'node:crypto';
59

610
jest.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

Comments
 (0)