From 4855aff4e6c9a3acd09dc6a70c12393c24309fd1 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Thu, 7 May 2026 14:10:01 +0100 Subject: [PATCH 01/12] CCM-17531: add some temp logging --- lambdas/mesh-download/mesh_download/handler.py | 10 ++++++++++ lambdas/mesh-download/mesh_download/processor.py | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lambdas/mesh-download/mesh_download/handler.py b/lambdas/mesh-download/mesh_download/handler.py index 67c99c760..8e6dcd671 100644 --- a/lambdas/mesh-download/mesh_download/handler.py +++ b/lambdas/mesh-download/mesh_download/handler.py @@ -25,6 +25,14 @@ def handler(event, context): 'failed': 0 } + def _get_memory_mb() -> float: + with open('/proc/self/status') as f: + for line in f: + if line.startswith('VmRSS:'): + # Value is in kB, e.g. "VmRSS: 12345 kB" + return int(line.split()[1]) / 1024 + return 0.0 + try: with Config() as config: doc_store_config = DocumentStoreConfig( @@ -60,8 +68,10 @@ def handler(event, context): continue try: + log.info("Memory before process_sqs_message", message_id=message_id, memory_mb=_get_memory_mb()) outcome = processor.process_sqs_message(record) processed[outcome] += 1 + log.info("Memory after process_sqs_message", message_id=message_id, memory_mb=_get_memory_mb()) except Exception as exc: processed['failed'] += 1 diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index 8903c3b9b..423942183 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -24,13 +24,26 @@ def __init__(self, **kwargs): self.__storage_bucket = self.__config.transactional_data_bucket + def _get_memory_mb() -> float: + with open('/proc/self/status') as f: + for line in f: + if line.startswith('VmRSS:'): + # Value is in kB, e.g. "VmRSS: 12345 kB" + return int(line.split()[1]) / 1024 + return 0.0 + def process_sqs_message(self, sqs_record): try: + logger.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) validated_event = self._parse_and_validate_event(sqs_record) + logger.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) logger = self.__log.bind(mesh_message_id=validated_event.data.meshMessageId) logger.info("Processing MESH download request") - return self._handle_download(validated_event, logger) + logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + result = self._handle_download(validated_event, logger) + logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + return result except Exception as exc: self.__log.error( From d28aea538b0d04af08527476ce065de2478def98 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Thu, 7 May 2026 14:31:48 +0100 Subject: [PATCH 02/12] CCM-17531: add some temp logging --- lambdas/mesh-download/mesh_download/processor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index 423942183..8b2391ae3 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -34,15 +34,15 @@ def _get_memory_mb() -> float: def process_sqs_message(self, sqs_record): try: - logger.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + self.__log.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) validated_event = self._parse_and_validate_event(sqs_record) - logger.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + self.__log.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) logger = self.__log.bind(mesh_message_id=validated_event.data.meshMessageId) logger.info("Processing MESH download request") - logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) result = self._handle_download(validated_event, logger) - logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) + logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) return result except Exception as exc: From ce271afad43141479af143e10dae8d2936de4dd0 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Thu, 7 May 2026 14:38:02 +0100 Subject: [PATCH 03/12] CCM-17531: add some temp logging --- lambdas/mesh-download/mesh_download/processor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index 8b2391ae3..d91069b69 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -24,6 +24,7 @@ def __init__(self, **kwargs): self.__storage_bucket = self.__config.transactional_data_bucket + @staticmethod def _get_memory_mb() -> float: with open('/proc/self/status') as f: for line in f: @@ -34,15 +35,15 @@ def _get_memory_mb() -> float: def process_sqs_message(self, sqs_record): try: - self.__log.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) + self.__log.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) validated_event = self._parse_and_validate_event(sqs_record) - self.__log.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) + self.__log.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) logger = self.__log.bind(mesh_message_id=validated_event.data.meshMessageId) logger.info("Processing MESH download request") - logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) + logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) result = self._handle_download(validated_event, logger) - logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb) + logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) return result except Exception as exc: From 5d846a1cbbcac3ef7e05c6fc5d48375f31543f93 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Mon, 11 May 2026 09:44:02 +0100 Subject: [PATCH 04/12] CCM-17531: clean up memory logging --- .../mesh-download/mesh_download/handler.py | 19 ++++++++++++------- .../mesh-download/mesh_download/processor.py | 16 +--------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/lambdas/mesh-download/mesh_download/handler.py b/lambdas/mesh-download/mesh_download/handler.py index 8e6dcd671..ea80bab5a 100644 --- a/lambdas/mesh-download/mesh_download/handler.py +++ b/lambdas/mesh-download/mesh_download/handler.py @@ -1,6 +1,5 @@ """lambda handler for mesh download""" -import json from dl_utils import EventPublisher from .config import Config, log @@ -25,13 +24,19 @@ def handler(event, context): 'failed': 0 } - def _get_memory_mb() -> float: + def _get_memory_mb() -> dict: + current_kb = 0 + peak_kb = 0 with open('/proc/self/status') as f: for line in f: if line.startswith('VmRSS:'): - # Value is in kB, e.g. "VmRSS: 12345 kB" - return int(line.split()[1]) / 1024 - return 0.0 + current_kb = int(line.split()[1]) + elif line.startswith('VmHWM:'): + peak_kb = int(line.split()[1]) + return { + 'current_mb': current_kb / 1024, + 'peak_mb': peak_kb / 1024, + } try: with Config() as config: @@ -68,10 +73,8 @@ def _get_memory_mb() -> float: continue try: - log.info("Memory before process_sqs_message", message_id=message_id, memory_mb=_get_memory_mb()) outcome = processor.process_sqs_message(record) processed[outcome] += 1 - log.info("Memory after process_sqs_message", message_id=message_id, memory_mb=_get_memory_mb()) except Exception as exc: processed['failed'] += 1 @@ -86,6 +89,8 @@ def _get_memory_mb() -> float: skipped=processed['skipped'], failed=processed['failed']) + log.info("Memory after processing SQS event", memory=_get_memory_mb()) + return {"batchItemFailures": batch_item_failures} except Exception as exc: diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index d91069b69..8903c3b9b 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -24,27 +24,13 @@ def __init__(self, **kwargs): self.__storage_bucket = self.__config.transactional_data_bucket - @staticmethod - def _get_memory_mb() -> float: - with open('/proc/self/status') as f: - for line in f: - if line.startswith('VmRSS:'): - # Value is in kB, e.g. "VmRSS: 12345 kB" - return int(line.split()[1]) / 1024 - return 0.0 - def process_sqs_message(self, sqs_record): try: - self.__log.info("Memory before _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) validated_event = self._parse_and_validate_event(sqs_record) - self.__log.info("Memory after _parse_and_validate_event", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) logger = self.__log.bind(mesh_message_id=validated_event.data.meshMessageId) logger.info("Processing MESH download request") - logger.info("Memory before _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) - result = self._handle_download(validated_event, logger) - logger.info("Memory after _handle_download", message_id=sqs_record.get('messageId'), memory_mb=self._get_memory_mb()) - return result + return self._handle_download(validated_event, logger) except Exception as exc: self.__log.error( From 287835a38100b1e9a987b41187f15b12cd2b123a Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Mon, 11 May 2026 09:44:41 +0100 Subject: [PATCH 05/12] CCM-17531: update nhs-notify-digital-letters-onboarding to v0.2.0 --- lambdas/mesh-download/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/mesh-download/requirements.txt b/lambdas/mesh-download/requirements.txt index 4fcd21674..56bf045e7 100644 --- a/lambdas/mesh-download/requirements.txt +++ b/lambdas/mesh-download/requirements.txt @@ -3,7 +3,7 @@ structlog>=21.5.0 pydantic>=2.0.0 boto3>=1.28.62 pyopenssl>=24.2.1 -nhs-notify-digital-letters-onboarding @ git+https://github.com/NHSDigital/nhs-notify-digital-letters-onboarding@0.1.0 +nhs-notify-digital-letters-onboarding @ git+https://github.com/NHSDigital/nhs-notify-digital-letters-onboarding@0.2.0 -e ../../src/digital-letters-events -e ../../utils/py-mock-mesh -e ../../utils/py-utils From ef5378d4ff64911831890e401ad062767cd0ccbb Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 09:06:19 +0100 Subject: [PATCH 06/12] CCM-17531: update error logging to omit base64 pdf string --- lambdas/pdm-uploader-lambda/src/app/upload-to-pdm.ts | 1 + utils/utils/src/pdm-client/pdm-client.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lambdas/pdm-uploader-lambda/src/app/upload-to-pdm.ts b/lambdas/pdm-uploader-lambda/src/app/upload-to-pdm.ts index 3de0cdca9..57fc37eb6 100644 --- a/lambdas/pdm-uploader-lambda/src/app/upload-to-pdm.ts +++ b/lambdas/pdm-uploader-lambda/src/app/upload-to-pdm.ts @@ -35,6 +35,7 @@ export class UploadToPdm { } catch (error) { this.logger.error({ description: 'Error sending request to PDM', + messageReference: event.data.messageReference, err: error instanceof Error ? { message: error.message, name: error.name, stack: error.stack } diff --git a/utils/utils/src/pdm-client/pdm-client.ts b/utils/utils/src/pdm-client/pdm-client.ts index 7b50fc3db..134a12a8b 100644 --- a/utils/utils/src/pdm-client/pdm-client.ts +++ b/utils/utils/src/pdm-client/pdm-client.ts @@ -82,7 +82,10 @@ export class PdmClient implements IPdmClient { this.logger.error({ description: 'Failed sending PDM request', requestId, - err: error, + err: + error instanceof Error + ? { message: error.message, name: error.name, stack: error.stack } + : error, }); throw error; From ff4b206fc005d047bd7036ca99aaff4d9c921f04 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 11:30:09 +0100 Subject: [PATCH 07/12] CCM-17531: add base64 validation to mesh downloader --- .../mesh_download/__tests__/test_processor.py | 60 +++++++++++++++++++ .../mesh-download/mesh_download/processor.py | 20 ++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/lambdas/mesh-download/mesh_download/__tests__/test_processor.py b/lambdas/mesh-download/mesh_download/__tests__/test_processor.py index 2821c12b9..edc116b14 100644 --- a/lambdas/mesh-download/mesh_download/__tests__/test_processor.py +++ b/lambdas/mesh-download/mesh_download/__tests__/test_processor.py @@ -612,3 +612,63 @@ def test_trust_duplicate_publishes_invalid_event_and_acknowledges(self, mock_dat assert event_data['failureCode'] == 'DL_CLIV_004' mesh_message.acknowledge.assert_called_once() + + +class TestValidateFhirContent: + """Tests for _validate_fhir_content base64 checks""" + + def make_processor(self): + from mesh_download.processor import MeshDownloadProcessor + config, log, event_publisher, document_store = setup_mocks() + return MeshDownloadProcessor( + config=config, + log=log, + mesh_client=config.mesh_client, + download_metric=config.download_metric, + internal_duplicate_download_metric=config.internal_duplicate_download_metric, + trust_duplicate_download_metric=config.trust_duplicate_download_metric, + document_store=document_store, + event_publisher=event_publisher, + ) + + def fhir_with_data(self, data: str) -> str: + content = json.loads(create_fhir_content()) + content['content'][0]['attachment']['data'] = data + return json.dumps(content) + + def test_valid_base64_passes(self): + """Valid base64 data should not raise""" + processor = self.make_processor() + # 'SGVsbG8gV29ybGQ=' is 'Hello World' in base64 — valid padding and characters + with patch('mesh_download.processor.validate'): + processor._validate_fhir_content(self.fhir_with_data('SGVsbG8gV29ybGQ=')) + + def test_invalid_padding_raises(self): + """base64 string whose length is not a multiple of 4 should raise""" + processor = self.make_processor() + # Length 5 — not a multiple of 4 + with pytest.raises(ValueError, match="incorrect padding"): + processor._validate_fhir_content(self.fhir_with_data('AAAAA')) + + def test_invalid_characters_raises(self): + """base64 string containing characters outside the alphabet should raise""" + processor = self.make_processor() + # '!' is not in the base64 alphabet + with pytest.raises(ValueError, match="invalid characters"): + processor._validate_fhir_content(self.fhir_with_data('!AAA')) + + def test_no_data_field_skips_checks(self): + """Attachments without a data field should pass through without error""" + processor = self.make_processor() + content = json.loads(create_fhir_content()) + del content['content'][0]['attachment']['data'] + with patch('mesh_download.processor.validate'): + processor._validate_fhir_content(json.dumps(content)) + + def test_data_field_present_during_schema_validation(self): + """The data field should be passed through to the FHIR schema validator""" + processor = self.make_processor() + with patch('mesh_download.processor.validate') as mock_validate: + processor._validate_fhir_content(self.fhir_with_data('SGVsbG8gV29ybGQ=')) + called_with = mock_validate.call_args[0][0] + assert 'data' in called_with['content'][0]['attachment'] diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index 8903c3b9b..0e1b2ac9f 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -1,4 +1,5 @@ import json +import re from datetime import datetime, timezone from uuid import uuid4 @@ -58,6 +59,19 @@ def _parse_and_validate_event(self, sqs_record): def _validate_fhir_content(self, content): json_content = json.loads(content) + + for item in json_content.get('content', []): + b64 = item.get('attachment', {}).get('data') + if b64 is not None: + if len(b64) % 4 != 0: + raise ValueError( + f"Attachment base64 data has incorrect padding (length {len(b64)} is not a multiple of 4)" + ) + if not re.fullmatch(r'[A-Za-z0-9+/]*={0,2}', b64): + raise ValueError( + "Attachment base64 data contains invalid characters" + ) + validate(json_content) def _handle_download(self, event, logger): @@ -83,7 +97,11 @@ def _handle_download(self, event, logger): try: self._validate_fhir_content(content) except Exception as e: - logger.error("FHIR content is invalid", error=str(e)) + logger.error( + "FHIR content is invalid", + error=str(e), + mesh_message_id=data.meshMessageId + ) self._publish_message_invalid_event(incoming_event=event, failure_code='DL_CLIV_005') message.acknowledge() logger.info("Acknowledged message") From 9620025eedc0ae80a733f697a1186e6fcbde9fbc Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 11:30:38 +0100 Subject: [PATCH 08/12] CCM-17531: fix unit tests --- .../src/__tests__/app/upload-to-pdm.test.ts | 3 +++ utils/utils/src/__tests__/pdm-client/pdm-client.test.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lambdas/pdm-uploader-lambda/src/__tests__/app/upload-to-pdm.test.ts b/lambdas/pdm-uploader-lambda/src/__tests__/app/upload-to-pdm.test.ts index a861570a0..be4f14b3f 100644 --- a/lambdas/pdm-uploader-lambda/src/__tests__/app/upload-to-pdm.test.ts +++ b/lambdas/pdm-uploader-lambda/src/__tests__/app/upload-to-pdm.test.ts @@ -95,6 +95,7 @@ describe('UploadToPdm', () => { message: error.message, name: error.name, }), + messageReference: mockEvent.data.messageReference, }); expect(mockPdmClient.createDocumentReference).not.toHaveBeenCalled(); }); @@ -113,6 +114,7 @@ describe('UploadToPdm', () => { err: expect.objectContaining({ message: error.message, }), + messageReference: mockEvent.data.messageReference, }); expect(mockPdmClient.createDocumentReference).not.toHaveBeenCalled(); }); @@ -131,6 +133,7 @@ describe('UploadToPdm', () => { message: error.message, name: error.name, }), + messageReference: mockEvent.data.messageReference, }); }); }); diff --git a/utils/utils/src/__tests__/pdm-client/pdm-client.test.ts b/utils/utils/src/__tests__/pdm-client/pdm-client.test.ts index aa7858f84..d102c48bb 100644 --- a/utils/utils/src/__tests__/pdm-client/pdm-client.test.ts +++ b/utils/utils/src/__tests__/pdm-client/pdm-client.test.ts @@ -173,7 +173,11 @@ describe('PdmClient', () => { expect(mockLogger.error).toHaveBeenCalledWith({ description: 'Failed sending PDM request', requestId: mockRequestId, - err: mockError, + err: expect.objectContaining({ + message: mockError.message, + name: mockError.name, + stack: mockError.stack, + }), }); }); From 4f8bbad46a4febfe4b795125cb74339bbb96c089 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 12:31:53 +0100 Subject: [PATCH 09/12] CCM-17531: disallow empty data field --- .../mesh_download/__tests__/test_processor.py | 24 +++++-------------- .../mesh-download/mesh_download/processor.py | 4 ++++ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lambdas/mesh-download/mesh_download/__tests__/test_processor.py b/lambdas/mesh-download/mesh_download/__tests__/test_processor.py index edc116b14..e698edaf0 100644 --- a/lambdas/mesh-download/mesh_download/__tests__/test_processor.py +++ b/lambdas/mesh-download/mesh_download/__tests__/test_processor.py @@ -33,7 +33,6 @@ def setup_mocks(): return config, log, event_publisher, document_store - def create_valid_cloud_event(): """ Create a valid CloudEvent for testing @@ -60,7 +59,6 @@ def create_valid_cloud_event(): } } - def create_sqs_record(cloud_event=None): """ Create a mock SQS record containing a CloudEvent @@ -643,6 +641,12 @@ def test_valid_base64_passes(self): with patch('mesh_download.processor.validate'): processor._validate_fhir_content(self.fhir_with_data('SGVsbG8gV29ybGQ=')) + def test_invalid_characters_raises(self): + """Empty base64 data string should raise""" + processor = self.make_processor() + with pytest.raises(ValueError, match="must not be empty"): + processor._validate_fhir_content(self.fhir_with_data('')) + def test_invalid_padding_raises(self): """base64 string whose length is not a multiple of 4 should raise""" processor = self.make_processor() @@ -656,19 +660,3 @@ def test_invalid_characters_raises(self): # '!' is not in the base64 alphabet with pytest.raises(ValueError, match="invalid characters"): processor._validate_fhir_content(self.fhir_with_data('!AAA')) - - def test_no_data_field_skips_checks(self): - """Attachments without a data field should pass through without error""" - processor = self.make_processor() - content = json.loads(create_fhir_content()) - del content['content'][0]['attachment']['data'] - with patch('mesh_download.processor.validate'): - processor._validate_fhir_content(json.dumps(content)) - - def test_data_field_present_during_schema_validation(self): - """The data field should be passed through to the FHIR schema validator""" - processor = self.make_processor() - with patch('mesh_download.processor.validate') as mock_validate: - processor._validate_fhir_content(self.fhir_with_data('SGVsbG8gV29ybGQ=')) - called_with = mock_validate.call_args[0][0] - assert 'data' in called_with['content'][0]['attachment'] diff --git a/lambdas/mesh-download/mesh_download/processor.py b/lambdas/mesh-download/mesh_download/processor.py index 0e1b2ac9f..7800c0983 100644 --- a/lambdas/mesh-download/mesh_download/processor.py +++ b/lambdas/mesh-download/mesh_download/processor.py @@ -63,6 +63,10 @@ def _validate_fhir_content(self, content): for item in json_content.get('content', []): b64 = item.get('attachment', {}).get('data') if b64 is not None: + if len(b64) == 0: + raise ValueError( + "Attachment base64 data must not be empty" + ) if len(b64) % 4 != 0: raise ValueError( f"Attachment base64 data has incorrect padding (length {len(b64)} is not a multiple of 4)" From 537274669a6b0d2763ff61620e70926d1ecc5bb0 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 13:47:47 +0100 Subject: [PATCH 10/12] CCM-17531: give the integration test files unique names --- .github/actions/acceptance-tests/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index d6735f77a..4e176b89f 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -81,5 +81,5 @@ runs: if: ${{ inputs.testType == 'integration' }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: - name: Integration test report + name: Integration test report${{ inputs.shard != '' && format(' (shard {0})', inputs.shard) || '' }} path: "tests/playwright/playwright-report" From 6ba60e277515c041cfd244a955b4ec74207dbb5d Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 14:28:46 +0100 Subject: [PATCH 11/12] CCM-17531: give the integration test files unique names --- .github/actions/acceptance-tests/action.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index 4e176b89f..3df9cc38e 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -77,9 +77,16 @@ runs: TEST_TYPE: ${{ inputs.testType }} ENVIRONMENT: ${{ inputs.targetEnvironment }} PLAYWRIGHT_SHARD: ${{ inputs.shard }} + - name: Sanitise shard for artifact name + id: shard_label + if: ${{ inputs.testType == 'integration' && inputs.shard != '' }} + shell: bash + run: echo "value=${SHARD//\// of }" >> $GITHUB_OUTPUT + env: + SHARD: ${{ inputs.shard }} - name: Archive integration test results if: ${{ inputs.testType == 'integration' }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: - name: Integration test report${{ inputs.shard != '' && format(' (shard {0})', inputs.shard) || '' }} + name: Integration test report${{ inputs.shard != '' && format(' (shard {0})', steps.shard_label.outputs.value) || '' }} path: "tests/playwright/playwright-report" From ac01c374be38ac279287d0f89f3417610567cb78 Mon Sep 17 00:00:00 2001 From: Ian Hodges Date: Tue, 12 May 2026 14:57:17 +0100 Subject: [PATCH 12/12] CCM-17531: give the integration test files unique names --- .github/actions/acceptance-tests/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/acceptance-tests/action.yaml b/.github/actions/acceptance-tests/action.yaml index 3df9cc38e..e063dab6b 100644 --- a/.github/actions/acceptance-tests/action.yaml +++ b/.github/actions/acceptance-tests/action.yaml @@ -88,5 +88,5 @@ runs: if: ${{ inputs.testType == 'integration' }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: - name: Integration test report${{ inputs.shard != '' && format(' (shard {0})', steps.shard_label.outputs.value) || '' }} + name: Integration test report${{ inputs.shard != '' && format(' ({0})', steps.shard_label.outputs.value) || '' }} path: "tests/playwright/playwright-report"