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