Skip to content

Commit 26ac7f8

Browse files
authored
CCM-15186 Custom metric for update letter queue lambda (#489)
1 parent 90ae464 commit 26ac7f8

File tree

2 files changed

+139
-79
lines changed

2 files changed

+139
-79
lines changed

lambdas/update-letter-queue/src/__tests__/update-letter-queue.test.ts

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
} from "@internal/datastore";
77
import { mockDeep } from "jest-mock-extended";
88
import pino from "pino";
9-
import { MetricStatus } from "@internal/helpers";
109
import {
1110
Context,
1211
DynamoDBRecord,
@@ -32,9 +31,12 @@ const mockedDeps: jest.Mocked<Deps> = {
3231
env: {} as unknown as EnvVars,
3332
} as Deps;
3433

35-
function generateLetter(status: LetterStatus, id?: string): Letter {
34+
function generateLetter(
35+
status: LetterStatus,
36+
overrides?: Partial<Letter>,
37+
): Letter {
3638
return {
37-
id: id || "1",
39+
id: "1",
3840
status,
3941
specificationId: "spec1",
4042
supplierId: "supplier1",
@@ -50,6 +52,7 @@ function generateLetter(status: LetterStatus, id?: string): Letter {
5052
billingRef: "billing-ref-1",
5153
specificationBillingId: "billing1",
5254
priority: 2,
55+
...overrides,
5356
};
5457
}
5558

@@ -147,8 +150,8 @@ describe("update-letter-queue Lambda", () => {
147150

148151
it("returns on the first failure", async () => {
149152
const handler = createHandler(mockedDeps);
150-
const newLetter1 = generateLetter("PENDING", "1");
151-
const newLetter2 = generateLetter("PENDING", "2");
153+
const newLetter1 = generateLetter("PENDING", { id: "1" });
154+
const newLetter2 = generateLetter("PENDING", { id: "2" });
152155
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
153156
.mockRejectedValueOnce({})
154157
.mockResolvedValueOnce({});
@@ -167,8 +170,8 @@ describe("update-letter-queue Lambda", () => {
167170

168171
it("does not treat a replayed insert as a failure", async () => {
169172
const handler = createHandler(mockedDeps);
170-
const newLetter1 = generateLetter("PENDING", "1");
171-
const newLetter2 = generateLetter("PENDING", "2");
173+
const newLetter1 = generateLetter("PENDING", { id: "1" });
174+
const newLetter2 = generateLetter("PENDING", { id: "2" });
172175
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
173176
.mockRejectedValueOnce(new LetterAlreadyExistsError("supplier1", "1"))
174177
.mockResolvedValueOnce({});
@@ -184,10 +187,10 @@ describe("update-letter-queue Lambda", () => {
184187

185188
it("does not treat a replayed delete as a failure", async () => {
186189
const handler = createHandler(mockedDeps);
187-
const oldLetter1 = generateLetter("PENDING", "1");
188-
const oldLetter2 = generateLetter("PENDING", "2");
189-
const newLetter1 = generateLetter("ACCEPTED", "1");
190-
const newLetter2 = generateLetter("ACCEPTED", "2");
190+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
191+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
192+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
193+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
191194
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
192195
.mockRejectedValueOnce(new LetterDoesNotExistError("supplier1", "1"))
193196
.mockResolvedValueOnce({});
@@ -230,26 +233,51 @@ describe("update-letter-queue Lambda", () => {
230233
});
231234

232235
describe("Metrics", () => {
233-
it("emits success metrics when all letters are processed successfully", async () => {
236+
// eslint-disable-next-line jest/expect-expect
237+
it("logs a metric containing the delta of pending letters added/deleted", async () => {
234238
const handler = createHandler(mockedDeps);
235-
const oldLetter1 = generateLetter("PENDING", "1");
236-
const newLetter1 = generateLetter("ACCEPTED", "1");
237-
const newLetter2 = generateLetter("PENDING", "2");
239+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
240+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
241+
const newLetter2 = generateLetter("PENDING", { id: "2" });
242+
const newLetter3 = generateLetter("PENDING", { id: "3" });
238243

239244
const testData = generateKinesisEvent([
240245
generateModifyRecord(oldLetter1, newLetter1),
241246
generateInsertRecord(newLetter2),
247+
generateInsertRecord(newLetter3),
242248
]);
243249
await handler(testData, mockDeep<Context>(), jest.fn());
244250

245-
assertSuccessMetricLogged(2);
246-
assertFailureMetricLogged(0);
251+
assertQueueDeltaMetricLogged("supplier1", 1);
247252
});
248253

249-
it("emits failure metrics when a letter fails to be inserted", async () => {
254+
// eslint-disable-next-line jest/expect-expect
255+
it("breaks the metric down by supplier", async () => {
250256
const handler = createHandler(mockedDeps);
251-
const newLetter1 = generateLetter("PENDING", "1");
252-
const newLetter2 = generateLetter("PENDING", "2");
257+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
258+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
259+
const newLetter2 = generateLetter("PENDING", {
260+
supplierId: "supplier2",
261+
id: "2",
262+
});
263+
const newLetter3 = generateLetter("PENDING", { id: "3" });
264+
265+
const testData = generateKinesisEvent([
266+
generateModifyRecord(oldLetter1, newLetter1),
267+
generateInsertRecord(newLetter2),
268+
generateInsertRecord(newLetter3),
269+
]);
270+
await handler(testData, mockDeep<Context>(), jest.fn());
271+
272+
assertQueueDeltaMetricLogged("supplier1", 0);
273+
assertQueueDeltaMetricLogged("supplier2", 1);
274+
});
275+
276+
// eslint-disable-next-line jest/expect-expect
277+
it("counts a failed insert as zero", async () => {
278+
const handler = createHandler(mockedDeps);
279+
const newLetter1 = generateLetter("PENDING", { id: "1" });
280+
const newLetter2 = generateLetter("PENDING", { id: "2" });
253281
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
254282
.mockResolvedValueOnce({})
255283
.mockRejectedValueOnce(new Error("DynamoDB error"));
@@ -260,16 +288,16 @@ describe("update-letter-queue Lambda", () => {
260288
]);
261289
await handler(testData, mockDeep<Context>(), jest.fn());
262290

263-
assertSuccessMetricLogged(1);
264-
assertFailureMetricLogged(1);
291+
assertQueueDeltaMetricLogged("supplier1", 1);
265292
});
266293

267-
it("emits failure metrics when a letter fails to be deleted", async () => {
294+
// eslint-disable-next-line jest/expect-expect
295+
it("counts a failed delete as zero", async () => {
268296
const handler = createHandler(mockedDeps);
269-
const oldLetter1 = generateLetter("PENDING", "1");
270-
const oldLetter2 = generateLetter("PENDING", "2");
271-
const newLetter1 = generateLetter("ACCEPTED", "1");
272-
const newLetter2 = generateLetter("ACCEPTED", "2");
297+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
298+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
299+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
300+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
273301
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
274302
.mockResolvedValueOnce({})
275303
.mockRejectedValueOnce(new Error("DynamoDB error"));
@@ -280,14 +308,14 @@ describe("update-letter-queue Lambda", () => {
280308
]);
281309
await handler(testData, mockDeep<Context>(), jest.fn());
282310

283-
assertSuccessMetricLogged(1);
284-
assertFailureMetricLogged(1);
311+
assertQueueDeltaMetricLogged("supplier1", -1);
285312
});
286313

287-
it("does not count a replayed insert as a success or failure", async () => {
314+
// eslint-disable-next-line jest/expect-expect
315+
it("counts a replayed insert as zero", async () => {
288316
const handler = createHandler(mockedDeps);
289-
const newLetter1 = generateLetter("PENDING", "1");
290-
const newLetter2 = generateLetter("PENDING", "2");
317+
const newLetter1 = generateLetter("PENDING", { id: "1" });
318+
const newLetter2 = generateLetter("PENDING", { id: "2" });
291319

292320
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
293321
.mockRejectedValueOnce(new LetterAlreadyExistsError("supplier1", "1"))
@@ -299,16 +327,16 @@ describe("update-letter-queue Lambda", () => {
299327
]);
300328
await handler(testData, mockDeep<Context>(), jest.fn());
301329

302-
assertSuccessMetricLogged(1);
303-
assertFailureMetricLogged(0);
330+
assertQueueDeltaMetricLogged("supplier1", 1);
304331
});
305332

306-
it("does not count a replayed delete as a success or failure", async () => {
333+
// eslint-disable-next-line jest/expect-expect
334+
it("counts a replayed delete as zero", async () => {
307335
const handler = createHandler(mockedDeps);
308-
const oldLetter1 = generateLetter("PENDING", "1");
309-
const oldLetter2 = generateLetter("PENDING", "2");
310-
const newLetter1 = generateLetter("ACCEPTED", "1");
311-
const newLetter2 = generateLetter("ACCEPTED", "2");
336+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
337+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
338+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
339+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
312340
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
313341
.mockRejectedValueOnce(new LetterDoesNotExistError("supplier1", "1"))
314342
.mockResolvedValueOnce({});
@@ -319,19 +347,36 @@ describe("update-letter-queue Lambda", () => {
319347
]);
320348
await handler(testData, mockDeep<Context>(), jest.fn());
321349

322-
assertSuccessMetricLogged(1);
323-
assertFailureMetricLogged(0);
350+
assertQueueDeltaMetricLogged("supplier1", -1);
324351
});
325352

326-
it("emits zero success metrics when no pending letters are in the batch", async () => {
353+
// eslint-disable-next-line jest/expect-expect
354+
it("logs zero counts when no pending letters are in the batch", async () => {
327355
const handler = createHandler(mockedDeps);
328356
const newLetter = generateLetter("PRINTED");
329357

330358
const testData = generateKinesisEvent([generateInsertRecord(newLetter)]);
331359
await handler(testData, mockDeep<Context>(), jest.fn());
332360

333-
assertSuccessMetricLogged(0);
334-
assertFailureMetricLogged(0);
361+
assertQueueDeltaMetricNotLogged();
362+
});
363+
364+
it("skips records with no NewImage (e.g. DELETE events) without error", async () => {
365+
const handler = createHandler(mockedDeps);
366+
const deleteRecord: DynamoDBRecord = {
367+
eventName: "REMOVE",
368+
dynamodb: { OldImage: mapToImage(generateLetter("PENDING")) },
369+
};
370+
371+
const testData = generateKinesisEvent([deleteRecord]);
372+
const result = await handler(testData, mockDeep<Context>(), jest.fn());
373+
374+
expect(mockedDeps.letterQueueRepository.putLetter).not.toHaveBeenCalled();
375+
expect(
376+
mockedDeps.letterQueueRepository.deleteLetter,
377+
).not.toHaveBeenCalled();
378+
expect(result.batchItemFailures).toEqual([]);
379+
assertQueueDeltaMetricNotLogged();
335380
});
336381
});
337382
});
@@ -378,43 +423,42 @@ function mapToImage(oldLetter: Letter) {
378423
);
379424
}
380425

381-
function assertSuccessMetricLogged(count: number) {
426+
function assertQueueDeltaMetricLogged(supplierId: string, delta: number) {
382427
expect(mockedDeps.logger.info).toHaveBeenCalledWith(
383428
expect.objectContaining({
429+
supplier: supplierId,
384430
_aws: expect.objectContaining({
385431
CloudWatchMetrics: expect.arrayContaining([
386432
expect.objectContaining({
387433
Metrics: [
388434
expect.objectContaining({
389-
Name: MetricStatus.Success,
390-
Value: count,
435+
Name: "QueueDelta",
436+
Value: delta,
437+
Unit: Unit.Count,
391438
}),
392439
],
393440
}),
394441
]),
395442
}),
396-
success: count,
443+
QueueDelta: delta,
397444
}),
398445
);
399446
}
400447

401-
function assertFailureMetricLogged(count: number) {
402-
expect(mockedDeps.logger.info).toHaveBeenCalledWith(
448+
function assertQueueDeltaMetricNotLogged() {
449+
expect(mockedDeps.logger.info).not.toHaveBeenCalledWith(
403450
expect.objectContaining({
404451
_aws: expect.objectContaining({
405452
CloudWatchMetrics: expect.arrayContaining([
406453
expect.objectContaining({
407454
Metrics: [
408455
expect.objectContaining({
409-
Name: MetricStatus.Failure,
410-
Value: count,
411-
Unit: Unit.Count,
456+
Name: "QueueDelta",
412457
}),
413458
],
414459
}),
415460
]),
416461
}),
417-
failure: count,
418462
}),
419463
);
420464
}

0 commit comments

Comments
 (0)