@@ -68,6 +68,58 @@ def create_sqs_record(cloud_event=None):
6868 'body' : json .dumps ({'detail' : cloud_event })
6969 }
7070
71+ def create_fhir_content ():
72+ """
73+ Create a mock FHIR JSON content for testing
74+ """
75+ return json .dumps ({
76+ "resourceType" : "DocumentReference" ,
77+ "id" : "82bfb7f3-4889-4e15-b308-bbe4e3cd431f" ,
78+ "status" : "current" ,
79+ "docStatus" : "final" ,
80+ "type" : {
81+ "coding" : [
82+ {
83+ "system" : "http://snomed.info/sct" ,
84+ "code" : "308540004" ,
85+ "display" : "Appointment"
86+ }
87+ ]
88+ },
89+ "subject" : {
90+ "identifier" : {
91+ "system" : "https://fhir.nhs.uk/Id/nhs-number" ,
92+ "value" : "9876543210"
93+ }
94+ },
95+ "author" : [
96+ {
97+ "identifier" : {
98+ "system" : "https://fhir.nhs.uk/Id/ods-organization-code" ,
99+ "value" : "RX809"
100+ },
101+ "display" : "Example NHS Trust"
102+ }
103+ ],
104+ "custodian" : {
105+ "identifier" : {
106+ "system" : "https://fhir.nhs.uk/Id/ods-organization-code" ,
107+ "value" : "C4L8E"
108+ },
109+ "display" : "NHS ENGLAND: NHS NOTIFY"
110+ },
111+ "date" : "2025-11-19T14:30:00Z" ,
112+ "description" : "Appointment notification letter for outpatient consultation" ,
113+ "content" : [
114+ {
115+ "attachment" : {
116+ "contentType" : "application/pdf" ,
117+ "title" : "Appointment Letter - November 2025" ,
118+ "data" : "base64here=="
119+ }
120+ }
121+ ]
122+ })
71123
72124def create_mesh_message (message_id = 'test_123' , sender = 'SENDER_001' , local_id = 'ref_001' ):
73125 """
@@ -80,8 +132,9 @@ def create_mesh_message(message_id='test_123', sender='SENDER_001', local_id='re
80132 message .subject = 'test_document.pdf'
81133 message .workflow_id = 'TEST_WORKFLOW'
82134 message .message_type = 'DATA'
83- message .read .return_value = b'Test message content'
135+ message .read .return_value = create_fhir_content ()
84136 message .acknowledge = Mock ()
137+
85138 return message
86139
87140
@@ -142,7 +195,7 @@ def test_process_sqs_message_success(self, mock_datetime):
142195 document_store .store_document .assert_called_once_with (
143196 sender_id = 'TEST_SENDER' ,
144197 message_reference = 'ref_001' ,
145- content = b'Test message content'
198+ content = create_fhir_content ()
146199 )
147200
148201 mesh_message .acknowledge .assert_called_once ()
@@ -177,6 +230,75 @@ def test_process_sqs_message_success(self, mock_datetime):
177230 assert event_data ['messageUri' ] == 's3://test-pii-bucket/document-reference/SENDER_001_ref_001'
178231 assert set (event_data .keys ()) == {'senderId' , 'messageReference' , 'messageUri' , 'meshMessageId' }
179232
233+ @patch ('mesh_download.processor.datetime' )
234+ def test_process_sqs_message_invalid_fhir_content (self , mock_datetime ):
235+ from mesh_download .processor import MeshDownloadProcessor
236+
237+ config , log , event_publisher , document_store = setup_mocks ()
238+
239+ fixed_time = datetime (2025 , 11 , 19 , 15 , 30 , 45 , tzinfo = timezone .utc )
240+ mock_datetime .now .return_value = fixed_time
241+
242+ document_store .store_document .return_value = 'document-reference/SENDER_001_ref_001'
243+
244+ event_publisher .send_events .return_value = []
245+
246+ processor = MeshDownloadProcessor (
247+ config = config ,
248+ log = log ,
249+ mesh_client = config .mesh_client ,
250+ download_metric = config .download_metric ,
251+ document_store = document_store ,
252+ event_publisher = event_publisher
253+ )
254+
255+ mesh_message = create_mesh_message ()
256+ mesh_message .read .return_value = '{}' # invalid FHIR content (empty JSON)}
257+ config .mesh_client .retrieve_message .return_value = mesh_message
258+
259+ sqs_record = create_sqs_record ()
260+
261+ processor .process_sqs_message (sqs_record )
262+
263+ config .mesh_client .retrieve_message .assert_called_once_with ('test_message_123' )
264+
265+ mesh_message .read .assert_called_once ()
266+
267+ document_store .store_document .assert_not_called ()
268+
269+ mesh_message .acknowledge .assert_called_once ()
270+
271+ config .download_metric .record .assert_not_called ()
272+
273+ event_publisher .send_events .assert_called_once ()
274+
275+ # Verify the published event content
276+ published_events = event_publisher .send_events .call_args [0 ][0 ]
277+ assert len (published_events ) == 1
278+
279+ published_event = published_events [0 ]
280+
281+ # Verify CloudEvent envelope fields
282+ assert published_event ['type' ] == 'uk.nhs.notify.digital.letters.mesh.inbox.message.invalid.v1'
283+ assert published_event ['source' ] == '/nhs/england/notify/development/primary/data-plane/digitalletters/mesh'
284+ assert published_event ['subject' ] == 'customer/00000000-0000-0000-0000-000000000000/recipient/00000000-0000-0000-0000-000000000000'
285+ assert published_event ['time' ] == '2025-11-19T15:30:45+00:00'
286+ assert 'id' in published_event
287+ assert 'tracestate' not in published_event
288+ assert 'partitionkey' not in published_event
289+ assert 'sequence' not in published_event
290+ assert 'dataclassification' not in published_event
291+ assert 'dataregulation' not in published_event
292+ assert 'datacategory' not in published_event
293+
294+ # Verify CloudEvent data payload
295+ event_data = published_event ['data' ]
296+ assert event_data ['senderId' ] == 'TEST_SENDER'
297+ assert event_data ['messageReference' ] == 'ref_001'
298+ assert event_data ['meshMessageId' ] == 'test_message_123'
299+ assert event_data ['failureCode' ] == 'DL_CLIV_005'
300+ assert set (event_data .keys ()) == {'senderId' , 'messageReference' , 'meshMessageId' , 'failureCode' }
301+
180302 def test_process_sqs_message_validation_failure (self ):
181303 """Malformed CloudEvents should be rejected by pydantic and not trigger downloads"""
182304 from mesh_download .processor import MeshDownloadProcessor
0 commit comments