Skip to content

Commit 5cda343

Browse files
committed
CCM-12740: update events and how they are propagated
1 parent 7a8ab82 commit 5cda343

File tree

10 files changed

+128
-76
lines changed

10 files changed

+128
-76
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('createHandler', () => {
2727
dataschemaversion: '1.0',
2828
severitytext: 'INFO',
2929
data: {
30-
uri: 'https://example.com/ttl/resource',
30+
messageUri: 'https://example.com/ttl/resource',
3131
'digital-letter-id': '123e4567-e89b-12d3-a456-426614174000',
3232
messageReference: 'ref1',
3333
senderId: 'sender1',

lambdas/ttl-create-lambda/src/__tests__/app/create-ttl.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('CreateTtl', () => {
2828
'digital-letter-id': '123e4567-e89b-12d3-a456-426614174000',
2929
messageReference: 'ref1',
3030
senderId: 'sender1',
31-
uri: 'https://example.com/ttl/resource',
31+
messageUri: 'https://example.com/ttl/resource',
3232
},
3333
};
3434

lambdas/ttl-create-lambda/src/__tests__/infra/ttl-repository.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('TtlRepository', () => {
3737
'digital-letter-id': '123e4567-e89b-12d3-a456-426614174000',
3838
messageReference: 'ref1',
3939
senderId: 'sender1',
40-
uri: 'https://example.com/ttl/resource',
40+
messageUri: 'https://example.com/ttl/resource',
4141
},
4242
};
4343

@@ -79,12 +79,11 @@ describe('TtlRepository', () => {
7979
expect(putCommand.input).toStrictEqual({
8080
TableName: tableName,
8181
Item: {
82-
PK: item.data.uri,
82+
PK: item.data.messageUri,
8383
SK: 'TTL',
8484
dateOfExpiry: expectedDateOfExpiry,
85-
messageReference: 'ref1',
85+
event: item,
8686
ttl: expectedTtlSeconds,
87-
senderId: 'sender1',
8887
},
8988
});
9089
});

lambdas/ttl-create-lambda/src/infra/ttl-repository.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class TtlRepository {
2323

2424
this.logger.info({
2525
description: 'Inserting item into TTL table',
26-
PK: item.data.uri,
26+
PK: item.data.messageUri,
2727
ttlTime,
2828
});
2929

@@ -38,10 +38,7 @@ export class TtlRepository {
3838
}
3939
}
4040

41-
private async putTtlRecord(
42-
{ data: { messageReference, senderId, uri } }: TtlItemEvent,
43-
ttlTime: number,
44-
) {
41+
private async putTtlRecord(ttlItemEvent: TtlItemEvent, ttlTime: number) {
4542
// GSI PK utilising write sharding YYYY-MM-DD#<RANDOM_INT_BETWEEN_0_AND_[shardCount]>
4643
const ttlGsiPk = `${
4744
new Date(ttlTime * 1000).toISOString().split('T')[0]
@@ -51,12 +48,11 @@ export class TtlRepository {
5148
new PutCommand({
5249
TableName: this.tableName,
5350
Item: {
54-
PK: uri,
51+
PK: ttlItemEvent.data.messageUri,
5552
SK: 'TTL',
5653
ttl: ttlTime,
5754
dateOfExpiry: ttlGsiPk,
58-
messageReference,
59-
senderId,
55+
event: ttlItemEvent,
6056
},
6157
}),
6258
);

lambdas/ttl-handle-expiry-lambda/src/__tests__/apis/dynamodb-stream-handler.test.ts

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,53 @@ const mockEvent: DynamoDBStreamEvent = {
2020
dynamodb: {
2121
ApproximateCreationDateTime: 1_234_567_890,
2222
Keys: {
23-
PK: { S: 'MESSAGE#test-id-1' },
23+
PK: { S: 'https://example.com/ttl/resource' },
2424
SK: { S: 'METADATA' },
2525
},
2626
OldImage: {
27-
PK: { S: 'MESSAGE#test-id-1' },
27+
PK: { S: 'https://example.com/ttl/resource' },
2828
SK: { S: 'METADATA' },
2929
dateOfExpiry: { S: 'dateOfExpiry' },
30-
messageReference: { S: 'ref1' },
30+
event: {
31+
M: {
32+
profileversion: { S: '1.0.0' },
33+
profilepublished: { S: '2025-10' },
34+
id: { S: '550e8400-e29b-41d4-a716-446655440001' },
35+
specversion: { S: '1.0' },
36+
source: {
37+
S: '/nhs/england/notify/production/primary/data-plane/digital-letters',
38+
},
39+
subject: {
40+
S: 'customer/920fca11-596a-4eca-9c47-99f624614658/recipient/769acdd4-6a47-496f-999f-76a6fd2c3959',
41+
},
42+
type: {
43+
S: 'uk.nhs.notify.digital.letters.mesh.inbox.message.downloaded.v1',
44+
},
45+
time: { S: '2023-06-20T12:00:00Z' },
46+
recordedtime: { S: '2023-06-20T12:00:00.250Z' },
47+
severitynumber: { N: '2' },
48+
traceparent: {
49+
S: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
50+
},
51+
datacontenttype: { S: 'application/json' },
52+
dataschema: {
53+
S: 'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10/digital-letter-base-data.schema.json',
54+
},
55+
dataschemaversion: { S: '1.0' },
56+
severitytext: { S: 'INFO' },
57+
data: {
58+
M: {
59+
messageUri: { S: 'https://example.com/ttl/resource' },
60+
'digital-letter-id': {
61+
S: '123e4567-e89b-12d3-a456-426614174000',
62+
},
63+
messageReference: { S: 'ref1' },
64+
senderId: { S: 'sender1' },
65+
},
66+
},
67+
},
68+
},
3169
ttl: { N: futureTimestamp.toString() },
32-
senderId: { S: 'sender1' },
3370
},
3471
SequenceNumber: '123456789',
3572
SizeBytes: 100,
@@ -79,7 +116,7 @@ describe('createHandler', () => {
79116
source:
80117
'/nhs/england/notify/production/primary/data-plane/digital-letters',
81118
subject:
82-
'customer/00000000-0000-0000-0000-000000000000/recipient/00000000-0000-0000-0000-000000000000',
119+
'customer/920fca11-596a-4eca-9c47-99f624614658/recipient/769acdd4-6a47-496f-999f-76a6fd2c3959',
83120
type: 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1',
84121
datacontenttype: 'application/json',
85122
dataschema:
@@ -89,7 +126,7 @@ describe('createHandler', () => {
89126
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
90127
),
91128
messageReference: 'ref1',
92-
messageUri: 'MESSAGE#test-id-1',
129+
messageUri: 'https://example.com/ttl/resource',
93130
senderId: 'sender1',
94131
}),
95132
}),
@@ -139,7 +176,7 @@ describe('createHandler', () => {
139176
dynamodb: {
140177
ApproximateCreationDateTime: 1_234_567_890,
141178
Keys: {
142-
PK: { S: 'MESSAGE#test-id-1' },
179+
PK: { S: 'https://example.com/ttl/resource' },
143180
},
144181
SequenceNumber: '123456789',
145182
SizeBytes: 100,
@@ -159,29 +196,19 @@ describe('createHandler', () => {
159196
expect(result).toEqual({});
160197
});
161198

162-
it('should handle processing errors by sending to DLQ', async () => {
199+
it('should handle ttl dynamodb record parsing errors by sending to DLQ', async () => {
163200
const mockInvalidEvent: DynamoDBStreamEvent = {
164201
Records: [
165202
{
166-
eventID: 'test-event-1',
167-
eventName: 'REMOVE',
168-
eventVersion: '1.1',
169-
eventSource: 'aws:dynamodb',
170-
awsRegion: 'us-east-1',
203+
...mockEvent.Records[0],
171204
dynamodb: {
172-
ApproximateCreationDateTime: 1_234_567_890,
173-
Keys: {
174-
id: { S: 'test-id-1' },
175-
},
205+
...mockEvent.Records[0].dynamodb,
176206
OldImage: {
177207
invalidField: { S: 'invalid-data' },
178208
},
179-
SequenceNumber: '123456789',
180-
SizeBytes: 100,
181-
StreamViewType: 'OLD_IMAGE',
182209
},
183210
},
184-
] as DynamoDBRecord[],
211+
],
185212
};
186213

187214
const result = await handler(mockInvalidEvent);
@@ -198,6 +225,40 @@ describe('createHandler', () => {
198225
expect(result).toEqual({});
199226
});
200227

228+
it('should handle ttl item event parsing errors by sending to DLQ', async () => {
229+
const mockInvalidEvent: DynamoDBStreamEvent = {
230+
Records: [
231+
{
232+
...mockEvent.Records[0],
233+
dynamodb: {
234+
...mockEvent.Records[0].dynamodb,
235+
OldImage: {
236+
...mockEvent.Records[0].dynamodb?.OldImage,
237+
event: {
238+
M: {
239+
invalidField: { S: 'invalid-data' },
240+
},
241+
},
242+
},
243+
},
244+
},
245+
],
246+
};
247+
248+
const result = await handler(mockInvalidEvent);
249+
250+
expect(logger.warn).toHaveBeenCalledWith(
251+
expect.objectContaining({
252+
err: expect.any(Object),
253+
description: 'Error parsing ttl item event',
254+
}),
255+
);
256+
257+
expect(dlq.send).toHaveBeenCalledWith([mockInvalidEvent.Records[0]]);
258+
expect(eventPublisher.sendEvents).not.toHaveBeenCalled();
259+
expect(result).toEqual({});
260+
});
261+
201262
it('should handle empty records array', async () => {
202263
const mockNoRecordsEvent: DynamoDBStreamEvent = {
203264
Records: [],
@@ -296,7 +357,7 @@ describe('createHandler', () => {
296357
expect(logger.info).toHaveBeenCalledWith({
297358
description: 'ItemDequeued event not sent as item withdrawn',
298359
messageReference: 'ref1',
299-
messageUri: 'MESSAGE#test-id-1',
360+
messageUri: 'https://example.com/ttl/resource',
300361
senderId: 'sender1',
301362
});
302363

@@ -329,7 +390,7 @@ describe('createHandler', () => {
329390
type: 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1',
330391
data: expect.objectContaining({
331392
messageReference: 'ref1',
332-
messageUri: 'MESSAGE#test-id-1',
393+
messageUri: 'https://example.com/ttl/resource',
333394
senderId: 'sender1',
334395
}),
335396
}),

lambdas/ttl-handle-expiry-lambda/src/apis/dynamodb-stream-handler.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import type {
44
DynamoDBStreamEvent,
55
} from 'aws-lambda';
66
import { unmarshall } from '@aws-sdk/util-dynamodb';
7-
import { $TtlDynamodbRecord, EventPublisher, Logger } from 'utils';
7+
import {
8+
$TtlDynamodbRecord,
9+
$TtlItemEvent,
10+
EventPublisher,
11+
Logger,
12+
} from 'utils';
813
import { randomUUID } from 'node:crypto';
914
import { Dlq } from 'app/dlq';
1015

@@ -57,39 +62,38 @@ export const createHandler = ({
5762
return;
5863
}
5964

65+
const {
66+
data: itemEvent,
67+
error: eventParseError,
68+
success: eventParseSuccess,
69+
} = $TtlItemEvent.safeParse(item.event);
70+
71+
if (!eventParseSuccess) {
72+
logger.warn({
73+
err: eventParseError,
74+
description: 'Error parsing ttl item event',
75+
});
76+
77+
failures.push(record);
78+
79+
return;
80+
}
81+
6082
if (item.withdrawn) {
6183
logger.info({
6284
description: 'ItemDequeued event not sent as item withdrawn',
63-
messageReference: item.messageReference,
85+
messageReference: itemEvent.data.messageReference,
6486
messageUri: item.PK,
65-
senderId: item.senderId,
87+
senderId: itemEvent.data.senderId,
6688
});
6789
} else {
6890
await eventPublisher.sendEvents([
6991
{
70-
profileversion: '1.0.0',
71-
profilepublished: '2025-10',
72-
specversion: '1.0',
92+
...itemEvent,
7393
id: randomUUID(),
7494
time: new Date().toISOString(),
7595
recordedtime: new Date().toISOString(),
76-
severitynumber: 5,
77-
traceparent:
78-
'00-00000000000000000000000000000000-0000000000000000-01',
79-
source:
80-
'/nhs/england/notify/production/primary/data-plane/digital-letters',
81-
subject:
82-
'customer/00000000-0000-0000-0000-000000000000/recipient/00000000-0000-0000-0000-000000000000',
8396
type: 'uk.nhs.notify.digital.letters.queue.item.dequeued.v1',
84-
datacontenttype: 'application/json',
85-
dataschema:
86-
'https://notify.nhs.uk/cloudevents/schemas/digital-letters/2025-10/digital-letter-base-data.schema.json',
87-
data: {
88-
'digital-letter-id': randomUUID(),
89-
messageReference: item.messageReference,
90-
messageUri: item.PK,
91-
senderId: item.senderId,
92-
},
9397
},
9498
]);
9599
}

lambdas/ttl-poll-lambda/src/__tests__/infra/ttl-expiry-service.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,21 @@ const queryOutput = {
2727
SK: 'REQUEST_ITEM_PLAN#hello1',
2828
dateOfExpiry: mockDate,
2929
ttl: mockTtl,
30-
messageReference: 'ref1',
31-
senderId: 'sender1',
30+
event: {},
3231
},
3332
{
3433
PK: 'REQUEST_ITEM#hello2',
3534
SK: 'REQUEST_ITEM_PLAN#hello2',
3635
dateOfExpiry: mockDate,
3736
ttl: mockTtl,
38-
messageReference: 'ref2',
39-
senderId: 'sender2',
37+
event: {},
4038
},
4139
{
4240
PK: 'REQUEST_ITEM#hello3',
4341
SK: 'REQUEST_ITEM_PLAN#hello3',
4442
dateOfExpiry: mockDate,
4543
ttl: mockTtl,
46-
messageReference: 'ref3',
47-
senderId: 'sender3',
44+
event: {},
4845
},
4946
],
5047
$metadata: {},
@@ -336,8 +333,7 @@ describe('TtlExpiryService', () => {
336333
SK: 'REQUEST_ITEM_PLAN#hello1',
337334
dateOfExpiry: mockDate,
338335
ttl: futureDateTime,
339-
messageReference: 'ref1',
340-
senderId: 'sender1',
336+
event: {},
341337
},
342338
],
343339
$metadata: {},
@@ -356,8 +352,7 @@ describe('TtlExpiryService', () => {
356352
SK: 'REQUEST_ITEM_PLAN#hello1',
357353
dateOfExpiry: mockDate,
358354
ttl: futureDateTime,
359-
messageReference: 'ref1',
360-
senderId: 'sender1',
355+
event: {},
361356
},
362357
ttlBeforeSeconds: mockTtlBeforeSeconds,
363358
err: 'TTL of record is after target expiry time',

utils/utils/src/__tests__/types/ttl-item-event.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('$TtlItemEvent', () => {
2626
'digital-letter-id': '123e4567-e89b-12d3-a456-426614174000',
2727
messageReference: 'ref1',
2828
senderId: 'sender1',
29-
uri: 'https://example.com/ttl/resource',
29+
messageUri: 'https://example.com/ttl/resource',
3030
},
3131
};
3232

@@ -72,7 +72,7 @@ describe('validateTtlItemEvent', () => {
7272
'digital-letter-id': '123e4567-e89b-12d3-a456-426614174000',
7373
messageReference: 'ref1',
7474
senderId: 'sender1',
75-
uri: 'https://example.com/ttl/resource',
75+
messageUri: 'https://example.com/ttl/resource',
7676
},
7777
};
7878

0 commit comments

Comments
 (0)