@@ -2,6 +2,7 @@ import { expect, test } from '@playwright/test';
22import {
33 ENV ,
44 MESH_DOWNLOAD_DLQ_NAME ,
5+ MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME ,
56 MESH_POLL_LAMBDA_NAME ,
67 NON_PII_S3_BUCKET_NAME ,
78 PII_S3_BUCKET_NAME ,
@@ -16,6 +17,56 @@ import { v4 as uuidv4 } from 'uuid';
1617import { SENDER_ID_SKIPS_NOTIFY } from 'constants/tests-constants' ;
1718import { validateMESHInboxMessageReceived } from 'digital-letters-events' ;
1819
20+ const validPdmRequest = {
21+ resourceType : 'DocumentReference' ,
22+ id : '82bfb7f3-4889-4e15-b308-bbe4e3cd431f' ,
23+ status : 'current' ,
24+ docStatus : 'final' ,
25+ type : {
26+ coding : [
27+ {
28+ // eslint-disable-next-line sonarjs/no-clear-text-protocols
29+ system : 'http://snomed.info/sct' ,
30+ code : '308540004' ,
31+ display : 'Appointment' ,
32+ } ,
33+ ] ,
34+ } ,
35+ subject : {
36+ identifier : {
37+ system : 'https://fhir.nhs.uk/Id/nhs-number' ,
38+ value : '9876543210' ,
39+ } ,
40+ } ,
41+ author : [
42+ {
43+ identifier : {
44+ system : 'https://fhir.nhs.uk/Id/ods-organization-code' ,
45+ value : 'RX809' ,
46+ } ,
47+ display : 'Example NHS Trust' ,
48+ } ,
49+ ] ,
50+ custodian : {
51+ identifier : {
52+ system : 'https://fhir.nhs.uk/Id/ods-organization-code' ,
53+ value : 'C4L8E' ,
54+ } ,
55+ display : 'NHS ENGLAND: NHS NOTIFY' ,
56+ } ,
57+ date : '2025-11-19T14:30:00Z' ,
58+ description : 'Appointment notification letter for outpatient consultation' ,
59+ content : [
60+ {
61+ attachment : {
62+ contentType : 'application/pdf' ,
63+ title : 'Appointment Letter - November 2025' ,
64+ data : 'base64here==' ,
65+ } ,
66+ } ,
67+ ] ,
68+ } ;
69+
1970test . describe ( 'Digital Letters - MESH Poll and Download' , ( ) => {
2071 const senderId = SENDER_ID_SKIPS_NOTIFY ;
2172 const sendersMeshMailboxId = 'test-mesh-sender-1' ;
@@ -75,15 +126,31 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
75126 } , 180_000 ) ;
76127 }
77128
129+ async function expectMeshInboxMessageInvalidEvent (
130+ meshMessageId : string ,
131+ messageReference : string ,
132+ ) : Promise < void > {
133+ await expectToPassEventually ( async ( ) => {
134+ const eventLogEntry = await getLogsFromCloudwatch (
135+ `/aws/vendedlogs/events/event-bus/nhs-${ ENV } -dl` ,
136+ [
137+ '$.message_type = "EVENT_RECEIPT"' ,
138+ '$.details.detail_type = "uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1"' ,
139+ `$.details.event_detail = "*\\"messageReference\\":\\"${ messageReference } \\"*"` ,
140+ `$.details.event_detail = "*\\"senderId\\":\\"${ senderId } \\"*"` ,
141+ `$.details.event_detail = "*\\"meshMessageId\\":\\"${ meshMessageId } \\"*"` ,
142+ `$.details.event_detail = "*\\"failureCode\\":\\"DL_CLIV_005\\"*"` ,
143+ ] ,
144+ ) ;
145+
146+ expect ( eventLogEntry . length ) . toBeGreaterThanOrEqual ( 1 ) ;
147+ } , 180_000 ) ;
148+ }
149+
78150 test ( 'should poll message from MESH inbox, publish received event, download message, and publish downloaded event' , async ( ) => {
79151 const meshMessageId = `${ Date . now ( ) } _TEST_${ uuidv4 ( ) . slice ( 0 , 8 ) } ` ;
80152 const messageReference = uuidv4 ( ) ;
81- const messageContent = JSON . stringify ( {
82- senderId,
83- messageReference,
84- testData : 'This is a test letter content' ,
85- timestamp : new Date ( ) . toISOString ( ) ,
86- } ) ;
153+ const messageContent = JSON . stringify ( validPdmRequest ) ;
87154
88155 await uploadMeshMessage ( meshMessageId , messageReference , messageContent ) ;
89156
@@ -111,6 +178,43 @@ test.describe('Digital Letters - MESH Poll and Download', () => {
111178 } , 60_000 ) ;
112179 } ) ;
113180
181+ test ( 'given invalid PDM request should publish invalid event, log an error, acknowledge message' , async ( ) => {
182+ const meshMessageId = `${ Date . now ( ) } _TEST_${ uuidv4 ( ) . slice ( 0 , 8 ) } ` ;
183+ const messageReference = uuidv4 ( ) ;
184+ const invalidPdmRequest = { ...validPdmRequest , id : undefined } ;
185+
186+ const messageContent = JSON . stringify ( invalidPdmRequest ) ;
187+
188+ await uploadMeshMessage ( meshMessageId , messageReference , messageContent ) ;
189+
190+ await invokeLambda ( MESH_POLL_LAMBDA_NAME ) ;
191+
192+ await expectMeshInboxMessageReceivedEvent ( meshMessageId ) ;
193+ await expectMeshInboxMessageInvalidEvent ( meshMessageId , messageReference ) ;
194+
195+ await expectToPassEventually ( async ( ) => {
196+ const filteredLogs = await getLogsFromCloudwatch (
197+ MESH_DOWNLOAD_LAMBDA_LOG_GROUP_NAME ,
198+ [
199+ '$.event = "FHIR content is not invalid"' ,
200+ `$.mesh_message_id = "${ meshMessageId } "` ,
201+ '$.error = "\'id\' is a required property*"' ,
202+ ] ,
203+ ) ;
204+
205+ expect ( filteredLogs . length ) . toEqual ( 1 ) ;
206+ } , 120 ) ;
207+
208+ await expectToPassEventually ( async ( ) => {
209+ await expect ( async ( ) => {
210+ await downloadFromS3 (
211+ NON_PII_S3_BUCKET_NAME ,
212+ `mock-mesh/${ meshMailboxId } /in/${ meshMessageId } ` ,
213+ ) ;
214+ } ) . rejects . toThrow ( 'No objects found' ) ;
215+ } , 60_000 ) ;
216+ } ) ;
217+
114218 test ( 'should send message to mesh-download DLQ when download fails' , async ( ) => {
115219 test . setTimeout ( 160_000 ) ;
116220
0 commit comments