@@ -75,6 +75,48 @@ function createPreparedV1Event(
7575 } ;
7676}
7777
78+ function createSupplierStatusChangeEventWithoutSupplier (
79+ overrides : Partial < any > = { } ,
80+ ) : LetterEvent {
81+ const now = new Date ( ) . toISOString ( ) ;
82+
83+ return $LetterEvent . parse ( {
84+ data : {
85+ domainId : overrides . domainId ?? "f47ac10b-58cc-4372-a567-0e02b2c3d479" ,
86+ groupId : "client_template" ,
87+ origin : {
88+ domain : "letter-rendering" ,
89+ event : "f47ac10b-58cc-4372-a567-0e02b2c3d479" ,
90+ source : "/data-plane/letter-rendering/prod/render-pdf" ,
91+ subject :
92+ "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5" ,
93+ } ,
94+ reasonCode : "R07" ,
95+ reasonText : "No such address" ,
96+ specificationId : "1y3q9v1zzzz" ,
97+ billingRef : "1y3q9v1zzzz" ,
98+ status : "RETURNED" ,
99+ supplierId : "" ,
100+ } ,
101+ datacontenttype : "application/json" ,
102+ dataschema :
103+ "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.RETURNED.1.0.0.schema.json" ,
104+ dataschemaversion : "1.0.0" ,
105+ id : overrides . id ?? "23f1f09c-a555-4d9b-8405-0b33490bc920" ,
106+ plane : "data" ,
107+ recordedtime : now ,
108+ severitynumber : 2 ,
109+ severitytext : "INFO" ,
110+ source : "/data-plane/supplier-api/prod/update-status" ,
111+ specversion : "1.0" ,
112+ subject :
113+ "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" ,
114+ time : now ,
115+ traceparent : "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" ,
116+ type : "uk.nhs.notify.supplier-api.letter.RETURNED.v1" ,
117+ } ) ;
118+ }
119+
78120function createPreparedV2Event (
79121 overrides : Partial < any > = { } ,
80122) : LetterRequestPreparedEventV2 {
@@ -129,6 +171,26 @@ function createSupplierStatusChangeEvent(
129171 } ) ;
130172}
131173
174+ // Mock aws-embedded-metrics
175+ let mockMetrics : any ;
176+ jest . mock ( "aws-embedded-metrics" , ( ) => ( {
177+ metricScope : (
178+ handler : ( metrics : any ) => ( event : SQSEvent ) => Promise < any > ,
179+ ) => {
180+ return async ( event : SQSEvent ) => {
181+ mockMetrics = {
182+ setNamespace : jest . fn ( ) ,
183+ putDimensions : jest . fn ( ) ,
184+ putMetric : jest . fn ( ) ,
185+ } ;
186+ return handler ( mockMetrics ) ( event ) ;
187+ } ;
188+ } ,
189+ Unit : {
190+ Count : "Count" ,
191+ } ,
192+ } ) ) ;
193+
132194describe ( "createUpsertLetterHandler" , ( ) => {
133195 const mockedDeps : jest . Mocked < Deps > = {
134196 letterRepo : {
@@ -213,6 +275,40 @@ describe("createUpsertLetterHandler", () => {
213275 expect ( updatedLetter . reasonCode ) . toBe ( "R07" ) ;
214276 expect ( updatedLetter . reasonText ) . toBe ( "No such address" ) ;
215277 expect ( updatedLetter . supplierId ) . toBe ( "supplier1" ) ;
278+ expect ( mockMetrics . setNamespace ) . toHaveBeenCalledWith ( "upsertLetter" ) ;
279+ expect ( mockMetrics . putDimensions ) . toHaveBeenCalledWith ( {
280+ Supplier : "supplier1" ,
281+ } ) ;
282+ expect ( mockMetrics . putMetric ) . toHaveBeenCalledWith (
283+ "MessagesProcessed" ,
284+ 3 ,
285+ "Count" ,
286+ ) ;
287+ } ) ;
288+
289+ test ( "unknown supplier has metric emitted with 'unknown' supplier dimension" , async ( ) => {
290+ const letterEvent = createSupplierStatusChangeEventWithoutSupplier ( ) ;
291+
292+ const message = {
293+ letterEvent,
294+ operationType : "uk.nhs.notify.supplier-api.letter.RETURNED.v1" ,
295+ supplierSpec : undefined ,
296+ } ;
297+ const evt : SQSEvent = createSQSEvent ( [
298+ createSqsRecord ( "unknown-supplier" , JSON . stringify ( message ) ) ,
299+ ] ) ;
300+
301+ await createUpsertLetterHandler ( mockedDeps ) ( evt , { } as any , { } as any ) ;
302+
303+ expect ( mockMetrics . setNamespace ) . toHaveBeenCalledWith ( "upsertLetter" ) ;
304+ expect ( mockMetrics . putDimensions ) . toHaveBeenCalledWith ( {
305+ Supplier : "unknown" ,
306+ } ) ;
307+ expect ( mockMetrics . putMetric ) . toHaveBeenCalledWith (
308+ "MessagesProcessed" ,
309+ 1 ,
310+ "Count" ,
311+ ) ;
216312 } ) ;
217313
218314 test ( "invalid JSON produces batch failure and logs error" , async ( ) => {
@@ -235,6 +331,15 @@ describe("createUpsertLetterHandler", () => {
235331 "Error processing upsert of record bad-json" ,
236332 ) ;
237333 expect ( mockedDeps . letterRepo . putLetter ) . not . toHaveBeenCalled ( ) ;
334+ expect ( mockMetrics . setNamespace ) . toHaveBeenCalledWith ( "upsertLetter" ) ;
335+ expect ( mockMetrics . putDimensions ) . toHaveBeenCalledWith ( {
336+ Supplier : "unknown" ,
337+ } ) ;
338+ expect ( mockMetrics . putMetric ) . toHaveBeenCalledWith (
339+ "MessageFailed" ,
340+ 1 ,
341+ "Count" ,
342+ ) ;
238343 } ) ;
239344
240345 test ( "invalid letter event schema produces batch failure" , async ( ) => {
@@ -260,6 +365,16 @@ describe("createUpsertLetterHandler", () => {
260365 expect ( result . batchItemFailures ) . toHaveLength ( 1 ) ;
261366 expect ( result . batchItemFailures [ 0 ] . itemIdentifier ) . toBe ( "bad-schema" ) ;
262367 expect ( mockedDeps . letterRepo . putLetter ) . not . toHaveBeenCalled ( ) ;
368+ expect ( mockMetrics . setNamespace ) . toHaveBeenCalledWith ( "upsertLetter" ) ;
369+ expect ( mockMetrics . putDimensions ) . toHaveBeenCalledWith ( {
370+ Supplier : "supplier1" ,
371+ } ) ;
372+ expect ( mockMetrics . putMetric ) . toHaveBeenCalledWith (
373+ "MessageFailed" ,
374+ 1 ,
375+ "Count" ,
376+ ) ;
377+
263378 } ) ;
264379
265380 test ( "unknown operation type produces batch failure" , async ( ) => {
@@ -283,6 +398,16 @@ describe("createUpsertLetterHandler", () => {
283398 if ( ! result ) throw new Error ( "expected BatchResponse, got void" ) ;
284399 expect ( result . batchItemFailures ) . toHaveLength ( 1 ) ;
285400 expect ( result . batchItemFailures [ 0 ] . itemIdentifier ) . toBe ( "unknown-op" ) ;
401+
402+ expect ( mockMetrics . setNamespace ) . toHaveBeenCalledWith ( "upsertLetter" ) ;
403+ expect ( mockMetrics . putDimensions ) . toHaveBeenCalledWith ( {
404+ Supplier : "supplier1" ,
405+ } ) ;
406+ expect ( mockMetrics . putMetric ) . toHaveBeenCalledWith (
407+ "MessageFailed" ,
408+ 1 ,
409+ "Count" ,
410+ ) ;
286411 } ) ;
287412
288413 test ( "processes multiple records with mixed results" , async ( ) => {
0 commit comments