Skip to content

Commit 595d4b6

Browse files
Add trying v1 parse if v2 fails
1 parent d6e80aa commit 595d4b6

File tree

4 files changed

+194
-123
lines changed

4 files changed

+194
-123
lines changed

lambdas/upsert-letter/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"@aws-sdk/lib-dynamodb": "^3.858.0",
55
"@internal/datastore": "*",
66
"@nhsdigital/nhs-notify-event-schemas-letter-rendering": "^2.0.0",
7+
"@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1": "npm:@nhsdigital/nhs-notify-event-schemas-letter-rendering@^1.1.5",
78
"@types/aws-lambda": "^8.10.148",
89
"aws-lambda": "^1.0.7",
910
"esbuild": "^0.24.0",

lambdas/upsert-letter/src/handler/__tests__/upsert-handler.test.ts

Lines changed: 148 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,31 @@ import { SNSMessage, SQSEvent } from "aws-lambda";
22
import pino from "pino";
33
import { LetterRepository } from "internal/datastore/src";
44
import { LetterRequestPreparedEventV2 } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering";
5+
import { LetterRequestPreparedEvent } from "@nhsdigital/nhs-notify-event-schemas-letter-rendering-v1";
56
import createUpsertLetterHandler from "../upsert-handler";
67
import { Deps } from "../../config/deps";
78
import { EnvVars } from "../../config/env";
89

10+
function createSqsRecord(msgId: string, body: string) {
11+
return {
12+
messageId: msgId,
13+
receiptHandle: "",
14+
body,
15+
attributes: {
16+
ApproximateReceiveCount: "",
17+
SentTimestamp: "",
18+
SenderId: "",
19+
ApproximateFirstReceiveTimestamp: "",
20+
},
21+
messageAttributes: {},
22+
md5OfBody: "",
23+
eventSource: "",
24+
eventSourceARN: "",
25+
awsRegion: "",
26+
};
27+
}
928
function createNotification(
10-
event: LetterRequestPreparedEventV2,
29+
event: LetterRequestPreparedEventV2 | LetterRequestPreparedEvent,
1130
): Partial<SNSMessage> {
1231
return {
1332
SignatureVersion: "",
@@ -25,7 +44,7 @@ function createNotification(
2544
};
2645
}
2746

28-
function createValidEvent(
47+
function createValidV2Event(
2948
overrides: Partial<any> = {},
3049
): LetterRequestPreparedEventV2 {
3150
// minimal valid event matching the prepared letter schema
@@ -65,12 +84,52 @@ function createValidEvent(
6584
};
6685
}
6786

87+
function createValidV1Event(
88+
overrides: Partial<any> = {},
89+
): LetterRequestPreparedEvent {
90+
// minimal valid event matching the prepared letter schema
91+
const now = new Date().toISOString();
92+
93+
return {
94+
specversion: "1.0",
95+
id: overrides.id ?? "7b9a03ca-342a-4150-b56b-989109c45613",
96+
source: "/data-plane/letter-rendering/test",
97+
subject: "client/client1/letter-request/letterRequest1",
98+
type: "uk.nhs.notify.letter-rendering.letter-request.prepared.v1",
99+
time: now,
100+
dataschema:
101+
"https://notify.nhs.uk/cloudevents/schemas/letter-rendering/letter-request.prepared.1.0.0.schema.json",
102+
dataschemaversion: "1.0.0",
103+
data: {
104+
domainId: overrides.domainId ?? "letter1",
105+
letterVariantId: overrides.letterVariantId ?? "lv1",
106+
requestId: overrides.requestId ?? "request1",
107+
requestItemId: overrides.requestItemId ?? "requestItem1",
108+
requestItemPlanId: overrides.requestItemPlanId ?? "requestItemPlan1",
109+
clientId: overrides.clientId ?? "client1",
110+
campaignId: overrides.campaignId ?? "campaign1",
111+
templateId: overrides.templateId ?? "template1",
112+
url: overrides.url ?? "s3://letterDataBucket/letter1.pdf",
113+
sha256Hash:
114+
"3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6",
115+
createdAt: now,
116+
pageCount: 1,
117+
status: "PREPARED",
118+
},
119+
traceparent: "00-0af7651916cd43dd8448eb211c803191-b7ad6b7169203331-01",
120+
recordedtime: now,
121+
severitynumber: 2,
122+
severitytext: "INFO",
123+
plane: "data",
124+
};
125+
}
126+
68127
describe("createUpsertLetterHandler", () => {
69128
const mockedDeps: jest.Mocked<Deps> = {
70129
letterRepo: {
71130
upsertLetter: jest.fn(),
72131
} as unknown as LetterRepository,
73-
logger: { error: jest.fn() } as unknown as pino.Logger,
132+
logger: { error: jest.fn(), info: jest.fn() } as unknown as pino.Logger,
74133
env: {
75134
LETTERS_TABLE_NAME: "LETTERS_TABLE_NAME",
76135
LETTER_TTL_HOURS: 12_960,
@@ -90,46 +149,22 @@ describe("createUpsertLetterHandler", () => {
90149
test("processes all records successfully and returns no batch failures", async () => {
91150
const evt: SQSEvent = {
92151
Records: [
93-
{
94-
messageId: "msg1",
95-
receiptHandle: "rh1",
96-
body: JSON.stringify(createNotification(createValidEvent())),
97-
attributes: {
98-
ApproximateReceiveCount: "",
99-
SentTimestamp: "",
100-
SenderId: "",
101-
ApproximateFirstReceiveTimestamp: "",
102-
},
103-
messageAttributes: {},
104-
md5OfBody: "",
105-
eventSource: "",
106-
eventSourceARN: "",
107-
awsRegion: "",
108-
},
109-
{
110-
messageId: "msg2",
111-
receiptHandle: "rh2",
112-
body: JSON.stringify(
152+
createSqsRecord(
153+
"msg1",
154+
JSON.stringify(createNotification(createValidV2Event())),
155+
),
156+
createSqsRecord(
157+
"msg2",
158+
JSON.stringify(
113159
createNotification(
114-
createValidEvent({
160+
createValidV2Event({
115161
id: "7b9a03ca-342a-4150-b56b-989109c45614",
116162
domainId: "letter2",
117163
url: "s3://letterDataBucket/letter2.pdf",
118164
}),
119165
),
120166
),
121-
attributes: {
122-
ApproximateReceiveCount: "",
123-
SentTimestamp: "",
124-
SenderId: "",
125-
ApproximateFirstReceiveTimestamp: "",
126-
},
127-
messageAttributes: {},
128-
md5OfBody: "",
129-
eventSource: "",
130-
eventSourceARN: "",
131-
awsRegion: "",
132-
},
167+
),
133168
],
134169
};
135170

@@ -167,25 +202,25 @@ describe("createUpsertLetterHandler", () => {
167202
expect(firstArg.source).toBe("/data-plane/letter-rendering/test");
168203
});
169204

170-
test("invalid JSON body produces batch failure and logs error", async () => {
205+
test("processes all v1 records successfully and returns no batch failures", async () => {
171206
const evt: SQSEvent = {
172207
Records: [
173-
{
174-
messageId: "bad-json",
175-
receiptHandle: "rh1",
176-
body: "this-is-not-json",
177-
attributes: {
178-
ApproximateReceiveCount: "",
179-
SentTimestamp: "",
180-
SenderId: "",
181-
ApproximateFirstReceiveTimestamp: "",
182-
},
183-
messageAttributes: {},
184-
md5OfBody: "",
185-
eventSource: "",
186-
eventSourceARN: "",
187-
awsRegion: "",
188-
},
208+
createSqsRecord(
209+
"msg1",
210+
JSON.stringify(createNotification(createValidV1Event())),
211+
),
212+
createSqsRecord(
213+
"msg2",
214+
JSON.stringify(
215+
createNotification(
216+
createValidV1Event({
217+
id: "7b9a03ca-342a-4150-b56b-989109c45614",
218+
domainId: "letter2",
219+
url: "s3://letterDataBucket/letter2.pdf",
220+
}),
221+
),
222+
),
223+
),
189224
],
190225
};
191226

@@ -195,6 +230,45 @@ describe("createUpsertLetterHandler", () => {
195230
{} as any,
196231
);
197232

233+
expect(result).toBeDefined();
234+
if (!result) throw new Error("expected BatchResponse, got void");
235+
expect(result.batchItemFailures).toHaveLength(0);
236+
237+
expect(mockedDeps.letterRepo.upsertLetter).toHaveBeenCalledTimes(2);
238+
239+
const firstArg = (mockedDeps.letterRepo.upsertLetter as jest.Mock).mock
240+
.calls[0][0];
241+
expect(firstArg.id).toBe("letter1");
242+
expect(firstArg.supplierId).toBe("supplier1");
243+
expect(firstArg.specificationId).toBe("spec1");
244+
expect(firstArg.url).toBe("s3://letterDataBucket/letter1.pdf");
245+
expect(firstArg.status).toBe("PENDING");
246+
expect(firstArg.groupId).toBe("client1campaign1template1");
247+
expect(firstArg.source).toBe("/data-plane/letter-rendering/test");
248+
249+
const secondArg = (mockedDeps.letterRepo.upsertLetter as jest.Mock).mock
250+
.calls[1][0];
251+
expect(secondArg.id).toBe("letter2");
252+
expect(secondArg.supplierId).toBe("supplier1");
253+
expect(secondArg.specificationId).toBe("spec1");
254+
expect(secondArg.url).toBe("s3://letterDataBucket/letter2.pdf");
255+
expect(secondArg.status).toBe("PENDING");
256+
expect(secondArg.groupId).toBe("client1campaign1template1");
257+
expect(secondArg.groupId).toBe("client1campaign1template1");
258+
expect(firstArg.source).toBe("/data-plane/letter-rendering/test");
259+
});
260+
261+
test("invalid JSON body produces batch failure and logs error", async () => {
262+
const evt: SQSEvent = {
263+
Records: [createSqsRecord("bad-json", "this-is-not-json")],
264+
};
265+
266+
const result = await createUpsertLetterHandler(mockedDeps)(
267+
evt,
268+
{} as any,
269+
{} as any,
270+
);
271+
198272
expect(result).toBeDefined();
199273
if (!result) throw new Error("expected BatchResponse, got void");
200274
expect(result.batchItemFailures).toHaveLength(1);
@@ -209,22 +283,10 @@ describe("createUpsertLetterHandler", () => {
209283
test("invalid notification schema produces batch failure and logs error", async () => {
210284
const evt: SQSEvent = {
211285
Records: [
212-
{
213-
messageId: "bad-schema",
214-
receiptHandle: "rh1",
215-
body: JSON.stringify({ not: "the expected shape" }),
216-
attributes: {
217-
ApproximateReceiveCount: "",
218-
SentTimestamp: "",
219-
SenderId: "",
220-
ApproximateFirstReceiveTimestamp: "",
221-
},
222-
messageAttributes: {},
223-
md5OfBody: "",
224-
eventSource: "",
225-
eventSourceARN: "",
226-
awsRegion: "",
227-
},
286+
createSqsRecord(
287+
"bad-schema",
288+
JSON.stringify({ not: "the expected shape" }),
289+
),
228290
],
229291
};
230292

@@ -248,25 +310,13 @@ describe("createUpsertLetterHandler", () => {
248310
test("invalid event schema produces batch failure and logs error", async () => {
249311
const evt: SQSEvent = {
250312
Records: [
251-
{
252-
messageId: "bad-schema",
253-
receiptHandle: "rh1",
254-
body: JSON.stringify({
313+
createSqsRecord(
314+
"bad-schema",
315+
JSON.stringify({
255316
Type: "Notification",
256317
Message: JSON.stringify({ bad: "shape" }),
257318
}),
258-
attributes: {
259-
ApproximateReceiveCount: "",
260-
SentTimestamp: "",
261-
SenderId: "",
262-
ApproximateFirstReceiveTimestamp: "",
263-
},
264-
messageAttributes: {},
265-
md5OfBody: "",
266-
eventSource: "",
267-
eventSourceARN: "",
268-
awsRegion: "",
269-
},
319+
),
270320
],
271321
};
272322

@@ -281,6 +331,9 @@ describe("createUpsertLetterHandler", () => {
281331
expect(result.batchItemFailures).toHaveLength(1);
282332
expect(result.batchItemFailures[0].itemIdentifier).toBe("bad-schema");
283333

334+
expect((mockedDeps.logger.info as jest.Mock).mock.calls[0][1]).toBe(
335+
"Trying to parse message with V1 schema",
336+
);
284337
expect((mockedDeps.logger.error as jest.Mock).mock.calls[0][1]).toBe(
285338
"Error parsing letter event in upsert",
286339
);
@@ -294,52 +347,28 @@ describe("createUpsertLetterHandler", () => {
294347

295348
const evt: SQSEvent = {
296349
Records: [
297-
{
298-
messageId: "ok-msg",
299-
receiptHandle: "rh1",
300-
body: JSON.stringify(
350+
createSqsRecord(
351+
"ok-msg",
352+
JSON.stringify(
301353
createNotification(
302-
createValidEvent({
354+
createValidV2Event({
303355
id: "7b9a03ca-342a-4150-b56b-989109c45615",
304356
data: { domainId: "ok" },
305357
}),
306358
),
307359
),
308-
attributes: {
309-
ApproximateReceiveCount: "",
310-
SentTimestamp: "",
311-
SenderId: "",
312-
ApproximateFirstReceiveTimestamp: "",
313-
},
314-
messageAttributes: {},
315-
md5OfBody: "",
316-
eventSource: "",
317-
eventSourceARN: "",
318-
awsRegion: "",
319-
},
320-
{
321-
messageId: "fail-msg",
322-
receiptHandle: "rh2",
323-
body: JSON.stringify(
360+
),
361+
createSqsRecord(
362+
"fail-msg",
363+
JSON.stringify(
324364
createNotification(
325-
createValidEvent({
365+
createValidV2Event({
326366
id: "7b9a03ca-342a-4150-b56b-989109c45616",
327367
data: { domainId: "ok" },
328368
}),
329369
),
330370
),
331-
attributes: {
332-
ApproximateReceiveCount: "",
333-
SentTimestamp: "",
334-
SenderId: "",
335-
ApproximateFirstReceiveTimestamp: "",
336-
},
337-
messageAttributes: {},
338-
md5OfBody: "",
339-
eventSource: "",
340-
eventSourceARN: "",
341-
awsRegion: "",
342-
},
371+
),
343372
],
344373
};
345374

0 commit comments

Comments
 (0)