Skip to content

Commit 4731204

Browse files
committed
Merge branch 'master' into VED-788-refactor-ack-backend
2 parents aebd613 + 48fd36d commit 4731204

File tree

95 files changed

+1603
-1512
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1603
-1512
lines changed

.github/dependabot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ updates:
5858
- "/filenameprocessor"
5959
- "/mesh_processor"
6060
- "/recordprocessor"
61-
- "/redis_sync"
61+
- "/lambdas/redis_sync"
6262
- "/lambdas/id_sync"
63+
- "/lambdas/mns_subscription"
6364
- "/lambdas/shared"
64-
- "/mns_subscription"
6565
schedule:
6666
interval: "daily"
6767
open-pull-requests-limit: 1

.github/workflows/sonarcloud.yml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,27 @@ jobs:
123123
poetry run coverage xml -o ../mesh_processor-coverage.xml
124124
125125
- name: Run unittest with coverage-mns-subscription
126-
working-directory: mns_subscription
127-
env:
128-
PYTHONPATH: ${{ github.workspace }}/mns_subscription/src:${{ github.workspace }}/mns_subscription/tests
126+
working-directory: lambdas/mns_subscription
129127
id: mns_subscription
128+
env:
129+
PYTHONPATH: ${{ env.LAMBDA_PATH }}/mns_subscription/src:${{ env.SHARED_PATH }}/src
130130
continue-on-error: true
131131
run: |
132132
poetry install
133-
poetry run coverage run -m unittest discover || echo "mns_subscription tests failed" >> ../failed_tests.txt
133+
poetry run coverage run -m unittest discover || echo "mns_subscription tests failed" >> ../../failed_tests.txt
134134
poetry run coverage report -m
135-
poetry run coverage xml -o ../mns_subscription-coverage.xml
135+
poetry run coverage xml -o ../../mns_subscription-coverage.xml
136136
137137
- name: Run unittest with redis_sync
138-
working-directory: redis_sync
138+
working-directory: lambdas/redis_sync
139139
id: redis_sync
140140
env:
141-
PYTHONPATH: ${{ github.workspace }}/redis_sync/src:${{ github.workspace }}/redis_sync/tests
141+
PYTHONPATH: ${{ env.LAMBDA_PATH }}/redis_sync/src:${{ env.SHARED_PATH }}/src
142142
continue-on-error: true
143143
run: |
144-
poetry install
145-
poetry run coverage run -m unittest discover || echo "redis_sync tests failed" >> ../failed_tests.txt
146-
poetry run coverage xml -o ../redis_sync-coverage.xml
144+
poetry install
145+
poetry run coverage run --source=src -m unittest discover || echo "redis_sync tests failed" >> ../../failed_tests.txt
146+
poetry run coverage xml -o ../../redis_sync-coverage.xml
147147
148148
- name: Run unittest with shared
149149
working-directory: lambdas/shared
@@ -152,9 +152,9 @@ jobs:
152152
PYTHONPATH: ${{ env.SHARED_PATH }}/src
153153
continue-on-error: true
154154
run: |
155-
poetry install
156-
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover -s tests -p "test_*.py" -v || echo "shared tests failed" >> ../../failed_tests.txt
157-
poetry run coverage xml -o ../../shared-coverage.xml
155+
poetry install
156+
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover -s tests -p "test_*.py" -v || echo "shared tests failed" >> ../../failed_tests.txt
157+
poetry run coverage xml -o ../../shared-coverage.xml
158158
159159
- name: Run unittest with id_sync
160160
working-directory: lambdas/id_sync
@@ -163,9 +163,9 @@ jobs:
163163
PYTHONPATH: ${{ env.LAMBDA_PATH }}/id_sync/src:${{ env.SHARED_PATH }}/src
164164
continue-on-error: true
165165
run: |
166-
poetry install
167-
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover || echo "id_sync tests failed" >> ../../failed_tests.txt
168-
poetry run coverage xml -o ../../id_sync-coverage.xml
166+
poetry install
167+
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover || echo "id_sync tests failed" >> ../../failed_tests.txt
168+
poetry run coverage xml -o ../../id_sync-coverage.xml
169169
170170
- name: Run Test Failure Summary
171171
id: check_failure

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL=/usr/bin/env bash -euo pipefail
22

3-
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = lambdas/ack_backend backend batch_processor_filter delta_backend filenameprocessor mesh_processor recordprocessor redis_sync lambdas/id_sync lambdas/shared mns_subscription
3+
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = backend batch_processor_filter delta_backend filenameprocessor mesh_processor recordprocessor lambdas/ack_backend lambdas/redis_sync lambdas/id_sync lambdas/mns_subscription lambdas/shared
44
PYTHON_PROJECT_DIRS = e2e e2e_batch $(PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS)
55

66
#Installs dependencies using poetry.

backend/src/log_structure.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def function_info(func):
4242
@wraps(func)
4343
def wrapper(*args, **kwargs):
4444
event = args[0] if args else {}
45-
logger.info(f"Event: {event}")
4645
headers = event.get("headers", {})
4746
correlation_id = headers.get("X-Correlation-ID", "X-Correlation-ID not passed")
4847
request_id = headers.get("X-Request-ID", "X-Request-ID not passed")
@@ -64,7 +63,6 @@ def wrapper(*args, **kwargs):
6463
start = time.time()
6564
try:
6665
result = func(*args, **kwargs)
67-
logger.info(f"Result:{result}")
6866
end = time.time()
6967
log_data["time_taken"] = f"{round(end - start, 5)}s"
7068
log_data.update(_log_data_from_body(event))

backend/tests/test_log_structure_wrapper.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ def mock_success_function(_event, _context):
2323
def mock_function_raises(_event, _context):
2424
raise ValueError("Test error")
2525

26+
def extract_all_call_args_for_logger(self, mock_logger) -> list:
27+
"""Extracts all arguments for logger.*."""
28+
return (
29+
[ args[0] for args, _ in mock_logger.info.call_args_list ]
30+
+ [ args[0] for args, _ in mock_logger.warning.call_args_list ]
31+
+ [ args[0] for args, _ in mock_logger.error.call_args_list ]
32+
)
33+
2634
def test_successful_execution(self, mock_logger, mock_firehose_logger):
2735
# Arrange
2836
test_correlation = "test_correlation"
@@ -65,6 +73,37 @@ def test_successful_execution(self, mock_logger, mock_firehose_logger):
6573
self.assertEqual(logged_info['local_id'], '12345^http://test')
6674
self.assertEqual(logged_info['vaccine_type'], 'FLU')
6775

76+
def test_successful_execution_pii(self, mock_logger, mock_firehose_logger):
77+
"""Pass personally identifiable information in an event, and ensure that it is not logged anywhere."""
78+
# Arrange
79+
test_correlation = "test_correlation"
80+
test_request = "test_request"
81+
test_supplier = "test_supplier"
82+
test_actual_path = "/test"
83+
test_resource_path = "/test"
84+
85+
self.mock_redis_client.hget.return_value = "FLU"
86+
wrapped_function = function_info(self.mock_success_function)
87+
event = {
88+
'headers': {
89+
'X-Correlation-ID': test_correlation,
90+
'X-Request-ID': test_request,
91+
'SupplierSystem': test_supplier
92+
},
93+
'path': test_actual_path,
94+
'requestContext': {'resourcePath': test_resource_path},
95+
'body': "{\"identifier\": [{\"system\": \"http://test\", \"value\": \"12345\"}], \"contained\": [{\"resourceType\": \"Patient\", \"id\": \"Pat1\", \"identifier\": [{\"system\": \"https://fhir.nhs.uk/Id/nhs-number\", \"value\": \"9693632109\"}]}], \"protocolApplied\": [{\"targetDisease\": [{\"coding\": [{\"system\": \"http://snomed.info/sct\", \"code\": \"840539006\", \"display\": \"Disease caused by severe acute respiratory syndrome coronavirus 2\"}]}]}]}"
96+
}
97+
98+
# Act
99+
result = wrapped_function(event, {})
100+
101+
# Assert
102+
self.assertEqual(result, "Success")
103+
104+
for logger_info in self.extract_all_call_args_for_logger(mock_logger):
105+
self.assertNotIn("9693632109", str(logger_info))
106+
68107
def test_exception_handling(self, mock_logger, mock_firehose_logger):
69108
# Arrange
70109
test_correlation = "failed_test_correlation"

batch_processor_filter/src/batch_processor_filter_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def apply_filter(self, batch_file_created_event: BatchFileCreatedEvent) -> None:
3232
supplier = batch_file_created_event["supplier"]
3333
vaccine_type = batch_file_created_event["vaccine_type"]
3434

35+
logger.info("Received batch file event for filename: %s with message id: %s", filename, message_id)
36+
3537
if self._is_duplicate_file(filename):
3638
# Mark as processed and return without error so next event will be picked up from queue
3739
logger.error("A duplicate file has already been processed. Filename: %s", filename)
@@ -52,12 +54,12 @@ def apply_filter(self, batch_file_created_event: BatchFileCreatedEvent) -> None:
5254
raise EventAlreadyProcessingForSupplierAndVaccTypeError(f"Batch event already processing for supplier: "
5355
f"{supplier} and vacc type: {vaccine_type}")
5456

57+
self._batch_audit_repository.update_status(message_id, FileStatus.PROCESSING)
5558
self._queue_client.send_message(
5659
QueueUrl=QUEUE_URL,
5760
MessageBody=json.dumps(batch_file_created_event),
5861
MessageGroupId=f"{supplier}_{vaccine_type}"
5962
)
60-
self._batch_audit_repository.update_status(message_id, FileStatus.PROCESSING)
6163

6264
successful_log_message = f"File forwarded for processing by ECS. Filename: {filename}"
6365
logger.info(successful_log_message)

batch_processor_filter/tests/test_lambda_handler.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import boto3
55
import copy
66
from unittest import TestCase
7-
from unittest.mock import patch, Mock, ANY
7+
from unittest.mock import patch, Mock, ANY, call
88

99
import botocore
1010
from moto import mock_aws
@@ -206,10 +206,17 @@ def test_lambda_handler_raises_error_when_event_already_processing_for_supplier_
206206
sqs_messages = sqs_client.receive_message(QueueUrl=self.mock_queue_url)
207207
self.assertEqual(sqs_messages.get("Messages", []), [])
208208

209-
self.mock_logger.info.assert_called_once_with(
210-
"Batch event already processing for supplier and vacc type. Filename: %s",
211-
"Menacwy_Vaccinations_v5_TEST_20250826T15003000.csv"
212-
)
209+
self.mock_logger.info.assert_has_calls([
210+
call(
211+
"Received batch file event for filename: %s with message id: %s",
212+
"Menacwy_Vaccinations_v5_TEST_20250826T15003000.csv",
213+
"3b60c4f7-ef67-43c7-8f0d-4faee04d7d0e"
214+
),
215+
call(
216+
"Batch event already processing for supplier and vacc type. Filename: %s",
217+
"Menacwy_Vaccinations_v5_TEST_20250826T15003000.csv"
218+
)
219+
])
213220

214221
def test_lambda_handler_processes_event_successfully(self):
215222
"""Should update the audit entry status to Processing and forward to SQS"""
@@ -225,11 +232,20 @@ def test_lambda_handler_processes_event_successfully(self):
225232
self.assertEqual(len(sqs_messages.get("Messages", [])), 1)
226233
self.assertDictEqual(json.loads(sqs_messages["Messages"][0]["Body"]), dict(self.default_batch_file_event))
227234

228-
expected_log_message = (f"File forwarded for processing by ECS. Filename: "
229-
f"{self.default_batch_file_event['filename']}")
230-
self.mock_logger.info.assert_called_once_with(expected_log_message)
235+
expected_success_log_message = (f"File forwarded for processing by ECS. Filename: "
236+
f"{self.default_batch_file_event['filename']}")
237+
self.mock_logger.info.assert_has_calls([
238+
call(
239+
"Received batch file event for filename: %s with message id: %s",
240+
"Menacwy_Vaccinations_v5_TEST_20250820T10210000.csv",
241+
"df0b745c-b8cb-492c-ba84-8ea28d9f51d5"
242+
),
243+
call(
244+
expected_success_log_message
245+
)
246+
])
231247
self.mock_firehose_send_log.assert_called_once_with(
232-
{**self.default_batch_file_event, "message": expected_log_message}
248+
{**self.default_batch_file_event, "message": expected_success_log_message}
233249
)
234250

235251
def test_lambda_handler_processes_event_successfully_when_event_for_same_supplier_and_vacc_already_processed(self):
@@ -256,8 +272,17 @@ def test_lambda_handler_processes_event_successfully_when_event_for_same_supplie
256272
self.assertEqual(len(sqs_messages.get("Messages", [])), 1)
257273
self.assertDictEqual(json.loads(sqs_messages["Messages"][0]["Body"]), dict(test_event))
258274

259-
expected_log_message = f"File forwarded for processing by ECS. Filename: {test_event['filename']}"
260-
self.mock_logger.info.assert_called_once_with(expected_log_message)
275+
expected_success_log_message = f"File forwarded for processing by ECS. Filename: {test_event['filename']}"
276+
self.mock_logger.info.assert_has_calls([
277+
call(
278+
"Received batch file event for filename: %s with message id: %s",
279+
"Menacwy_Vaccinations_v5_TEST_20250826T15003000.csv",
280+
"3b60c4f7-ef67-43c7-8f0d-4faee04d7d0e"
281+
),
282+
call(
283+
expected_success_log_message
284+
)
285+
])
261286
self.mock_firehose_send_log.assert_called_once_with(
262-
{**test_event, "message": expected_log_message}
287+
{**test_event, "message": expected_success_log_message}
263288
)

config/prod/permissions_config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
},
2222
{
2323
"supplier": "RAVS",
24-
"permissions": ["RSV.RS"],
24+
"permissions": [
25+
"MMR.CRUDS",
26+
"RSV.RS"
27+
],
2528
"ods_codes": ["X26", "X8E5B"]
2629
},
2730
{

filenameprocessor/src/constants.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
VaccineTypePermissionsError,
88
InvalidFileKeyError,
99
UnhandledAuditTableError,
10-
EmptyFileError,
1110
UnhandledSqsError
1211
)
1312

@@ -23,15 +22,11 @@
2322
ERROR_TYPE_TO_STATUS_CODE_MAP = {
2423
VaccineTypePermissionsError: 403,
2524
InvalidFileKeyError: 400, # Includes invalid ODS code, therefore unable to identify supplier
26-
EmptyFileError: 400,
2725
UnhandledAuditTableError: 500,
2826
UnhandledSqsError: 500,
2927
Exception: 500,
3028
}
3129

32-
# The size in bytes of an empty batch file containing only the headers row
33-
EMPTY_BATCH_FILE_SIZE_IN_BYTES = 700
34-
3530

3631
class FileStatus(StrEnum):
3732
"""File status constants"""

filenameprocessor/src/errors.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
"""Custom exceptions for the Filename Processor."""
22

33

4-
class EmptyFileError(Exception):
5-
"""A custom exception for when the batch file contains only the header row or is completely empty"""
6-
7-
84
class UnhandledAuditTableError(Exception):
95
"""A custom exception for when an unexpected error occurs whilst adding the file to the audit table."""
106

0 commit comments

Comments
 (0)