Skip to content

Commit e1f2e62

Browse files
committed
CCM-15186 Custom metric for update letter queue lambda
1 parent a777277 commit e1f2e62

2 files changed

Lines changed: 139 additions & 79 deletions

File tree

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",
@@ -48,6 +50,7 @@ function generateLetter(status: LetterStatus, id?: string): Letter {
4850
source: "test-source",
4951
subject: "test-subject",
5052
billingRef: "billing-ref-1",
53+
...overrides,
5154
};
5255
}
5356

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

145148
it("returns on the first failure", async () => {
146149
const handler = createHandler(mockedDeps);
147-
const newLetter1 = generateLetter("PENDING", "1");
148-
const newLetter2 = generateLetter("PENDING", "2");
150+
const newLetter1 = generateLetter("PENDING", { id: "1" });
151+
const newLetter2 = generateLetter("PENDING", { id: "2" });
149152
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
150153
.mockRejectedValueOnce({})
151154
.mockResolvedValueOnce({});
@@ -164,8 +167,8 @@ describe("update-letter-queue Lambda", () => {
164167

165168
it("does not treat a replayed insert as a failure", async () => {
166169
const handler = createHandler(mockedDeps);
167-
const newLetter1 = generateLetter("PENDING", "1");
168-
const newLetter2 = generateLetter("PENDING", "2");
170+
const newLetter1 = generateLetter("PENDING", { id: "1" });
171+
const newLetter2 = generateLetter("PENDING", { id: "2" });
169172
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
170173
.mockRejectedValueOnce(new LetterAlreadyExistsError("supplier1", "1"))
171174
.mockResolvedValueOnce({});
@@ -181,10 +184,10 @@ describe("update-letter-queue Lambda", () => {
181184

182185
it("does not treat a replayed delete as a failure", async () => {
183186
const handler = createHandler(mockedDeps);
184-
const oldLetter1 = generateLetter("PENDING", "1");
185-
const oldLetter2 = generateLetter("PENDING", "2");
186-
const newLetter1 = generateLetter("ACCEPTED", "1");
187-
const newLetter2 = generateLetter("ACCEPTED", "2");
187+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
188+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
189+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
190+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
188191
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
189192
.mockRejectedValueOnce(new LetterDoesNotExistError("supplier1", "1"))
190193
.mockResolvedValueOnce({});
@@ -227,26 +230,51 @@ describe("update-letter-queue Lambda", () => {
227230
});
228231

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

236241
const testData = generateKinesisEvent([
237242
generateModifyRecord(oldLetter1, newLetter1),
238243
generateInsertRecord(newLetter2),
244+
generateInsertRecord(newLetter3),
239245
]);
240246
await handler(testData, mockDeep<Context>(), jest.fn());
241247

242-
assertSuccessMetricLogged(2);
243-
assertFailureMetricLogged(0);
248+
assertQueueDeltaMetricLogged("supplier1", 1);
244249
});
245250

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

260-
assertSuccessMetricLogged(1);
261-
assertFailureMetricLogged(1);
288+
assertQueueDeltaMetricLogged("supplier1", 1);
262289
});
263290

264-
it("emits failure metrics when a letter fails to be deleted", async () => {
291+
// eslint-disable-next-line jest/expect-expect
292+
it("counts a failed delete as zero", async () => {
265293
const handler = createHandler(mockedDeps);
266-
const oldLetter1 = generateLetter("PENDING", "1");
267-
const oldLetter2 = generateLetter("PENDING", "2");
268-
const newLetter1 = generateLetter("ACCEPTED", "1");
269-
const newLetter2 = generateLetter("ACCEPTED", "2");
294+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
295+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
296+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
297+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
270298
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
271299
.mockResolvedValueOnce({})
272300
.mockRejectedValueOnce(new Error("DynamoDB error"));
@@ -277,14 +305,14 @@ describe("update-letter-queue Lambda", () => {
277305
]);
278306
await handler(testData, mockDeep<Context>(), jest.fn());
279307

280-
assertSuccessMetricLogged(1);
281-
assertFailureMetricLogged(1);
308+
assertQueueDeltaMetricLogged("supplier1", -1);
282309
});
283310

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

289317
(mockedDeps.letterQueueRepository.putLetter as jest.Mock)
290318
.mockRejectedValueOnce(new LetterAlreadyExistsError("supplier1", "1"))
@@ -296,16 +324,16 @@ describe("update-letter-queue Lambda", () => {
296324
]);
297325
await handler(testData, mockDeep<Context>(), jest.fn());
298326

299-
assertSuccessMetricLogged(1);
300-
assertFailureMetricLogged(0);
327+
assertQueueDeltaMetricLogged("supplier1", 1);
301328
});
302329

303-
it("does not count a replayed delete as a success or failure", async () => {
330+
// eslint-disable-next-line jest/expect-expect
331+
it("counts a replayed delete as zero", async () => {
304332
const handler = createHandler(mockedDeps);
305-
const oldLetter1 = generateLetter("PENDING", "1");
306-
const oldLetter2 = generateLetter("PENDING", "2");
307-
const newLetter1 = generateLetter("ACCEPTED", "1");
308-
const newLetter2 = generateLetter("ACCEPTED", "2");
333+
const oldLetter1 = generateLetter("PENDING", { id: "1" });
334+
const oldLetter2 = generateLetter("PENDING", { id: "2" });
335+
const newLetter1 = generateLetter("ACCEPTED", { id: "1" });
336+
const newLetter2 = generateLetter("ACCEPTED", { id: "2" });
309337
(mockedDeps.letterQueueRepository.deleteLetter as jest.Mock)
310338
.mockRejectedValueOnce(new LetterDoesNotExistError("supplier1", "1"))
311339
.mockResolvedValueOnce({});
@@ -316,19 +344,36 @@ describe("update-letter-queue Lambda", () => {
316344
]);
317345
await handler(testData, mockDeep<Context>(), jest.fn());
318346

319-
assertSuccessMetricLogged(1);
320-
assertFailureMetricLogged(0);
347+
assertQueueDeltaMetricLogged("supplier1", -1);
321348
});
322349

323-
it("emits zero success metrics when no pending letters are in the batch", async () => {
350+
// eslint-disable-next-line jest/expect-expect
351+
it("logs zero counts when no pending letters are in the batch", async () => {
324352
const handler = createHandler(mockedDeps);
325353
const newLetter = generateLetter("PRINTED");
326354

327355
const testData = generateKinesisEvent([generateInsertRecord(newLetter)]);
328356
await handler(testData, mockDeep<Context>(), jest.fn());
329357

330-
assertSuccessMetricLogged(0);
331-
assertFailureMetricLogged(0);
358+
assertQueueDeltaMetricNotLogged();
359+
});
360+
361+
it("skips records with no NewImage (e.g. DELETE events) without error", async () => {
362+
const handler = createHandler(mockedDeps);
363+
const deleteRecord: DynamoDBRecord = {
364+
eventName: "REMOVE",
365+
dynamodb: { OldImage: mapToImage(generateLetter("PENDING")) },
366+
};
367+
368+
const testData = generateKinesisEvent([deleteRecord]);
369+
const result = await handler(testData, mockDeep<Context>(), jest.fn());
370+
371+
expect(mockedDeps.letterQueueRepository.putLetter).not.toHaveBeenCalled();
372+
expect(
373+
mockedDeps.letterQueueRepository.deleteLetter,
374+
).not.toHaveBeenCalled();
375+
expect(result.batchItemFailures).toEqual([]);
376+
assertQueueDeltaMetricNotLogged();
332377
});
333378
});
334379
});
@@ -375,43 +420,42 @@ function mapToImage(oldLetter: Letter) {
375420
);
376421
}
377422

378-
function assertSuccessMetricLogged(count: number) {
423+
function assertQueueDeltaMetricLogged(supplierId: string, delta: number) {
379424
expect(mockedDeps.logger.info).toHaveBeenCalledWith(
380425
expect.objectContaining({
426+
supplier: supplierId,
381427
_aws: expect.objectContaining({
382428
CloudWatchMetrics: expect.arrayContaining([
383429
expect.objectContaining({
384430
Metrics: [
385431
expect.objectContaining({
386-
Name: MetricStatus.Success,
387-
Value: count,
432+
Name: "QueueDelta",
433+
Value: delta,
434+
Unit: Unit.Count,
388435
}),
389436
],
390437
}),
391438
]),
392439
}),
393-
success: count,
440+
QueueDelta: delta,
394441
}),
395442
);
396443
}
397444

398-
function assertFailureMetricLogged(count: number) {
399-
expect(mockedDeps.logger.info).toHaveBeenCalledWith(
445+
function assertQueueDeltaMetricNotLogged() {
446+
expect(mockedDeps.logger.info).not.toHaveBeenCalledWith(
400447
expect.objectContaining({
401448
_aws: expect.objectContaining({
402449
CloudWatchMetrics: expect.arrayContaining([
403450
expect.objectContaining({
404451
Metrics: [
405452
expect.objectContaining({
406-
Name: MetricStatus.Failure,
407-
Value: count,
408-
Unit: Unit.Count,
453+
Name: "QueueDelta",
409454
}),
410455
],
411456
}),
412457
]),
413458
}),
414-
failure: count,
415459
}),
416460
);
417461
}

0 commit comments

Comments
 (0)