66} from "@internal/datastore" ;
77import { mockDeep } from "jest-mock-extended" ;
88import pino from "pino" ;
9- import { MetricStatus } from "@internal/helpers" ;
109import {
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