Skip to content

Commit dd63b54

Browse files
committed
Merge branch 'master' into VED-779-refactor-mns
2 parents 38c33e5 + 12ecaec commit dd63b54

File tree

73 files changed

+1158
-708
lines changed

Some content is hidden

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

73 files changed

+1158
-708
lines changed

.github/dependabot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ updates:
5858
- "/filenameprocessor"
5959
- "/mesh_processor"
6060
- "/recordprocessor"
61-
- "/redis_sync"
61+
- "/lambdas/redis_sync"
6262
- "/lambdas/id_sync"
6363
- "/lambdas/mns_subscription"
6464
- "/lambdas/shared"

.github/workflows/sonarcloud.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,15 @@ jobs:
135135
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: |
144144
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
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

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 = ack_backend backend batch_processor_filter delta_backend filenameprocessor mesh_processor recordprocessor redis_sync lambdas/id_sync lambdas/mns_subscription lambdas/shared
3+
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = ack_backend backend batch_processor_filter delta_backend filenameprocessor mesh_processor recordprocessor 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"

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

filenameprocessor/src/file_name_processor.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import argparse
1010
from uuid import uuid4
1111
from utils_for_filenameprocessor import get_creation_and_expiry_times, move_file
12-
from file_validation import validate_file_key, is_file_in_directory_root, validate_file_not_empty
12+
from file_validation import validate_file_key, is_file_in_directory_root
1313
from send_sqs_message import make_and_send_sqs_message
1414
from make_and_upload_ack_file import make_and_upload_the_ack_file
1515
from audit_table import upsert_audit_table
@@ -20,8 +20,7 @@
2020
VaccineTypePermissionsError,
2121
InvalidFileKeyError,
2222
UnhandledAuditTableError,
23-
UnhandledSqsError,
24-
EmptyFileError
23+
UnhandledSqsError
2524
)
2625
from constants import FileNotProcessedReason, FileStatus, ERROR_TYPE_TO_STATUS_CODE_MAP, SOURCE_BUCKET_NAME
2726

@@ -68,8 +67,6 @@ def handle_record(record) -> dict:
6867
created_at_formatted_string, expiry_timestamp = get_creation_and_expiry_times(s3_response)
6968

7069
vaccine_type, supplier = validate_file_key(file_key)
71-
# VED-757: Known issue with suppliers sometimes sending empty files
72-
validate_file_not_empty(s3_response)
7370
permissions = validate_vaccine_type_permissions(vaccine_type=vaccine_type, supplier=supplier)
7471

7572
queue_name = f"{supplier}_{vaccine_type}"
@@ -94,17 +91,12 @@ def handle_record(record) -> dict:
9491

9592
except ( # pylint: disable=broad-exception-caught
9693
VaccineTypePermissionsError,
97-
EmptyFileError,
9894
InvalidFileKeyError,
9995
UnhandledAuditTableError,
10096
UnhandledSqsError,
10197
Exception,
10298
) as error:
103-
if isinstance(error, EmptyFileError):
104-
# Avoid error log noise for accepted scenario in which supplier provides a batch file with no records
105-
logger.warning("Error processing file '%s': %s", file_key, str(error))
106-
else:
107-
logger.error("Error processing file '%s': %s", file_key, str(error))
99+
logger.error("Error processing file '%s': %s", file_key, str(error))
108100

109101
queue_name = f"{supplier}_{vaccine_type}"
110102
file_status = get_file_status_for_error(error)
@@ -137,8 +129,6 @@ def get_file_status_for_error(error: Exception) -> str:
137129
"""Creates a file status based on the type of error that was thrown"""
138130
if isinstance(error, VaccineTypePermissionsError):
139131
return f"{FileStatus.NOT_PROCESSED} - {FileNotProcessedReason.UNAUTHORISED}"
140-
elif isinstance(error, EmptyFileError):
141-
return f"{FileStatus.NOT_PROCESSED} - {FileNotProcessedReason.EMPTY}"
142132

143133
return FileStatus.FAILED
144134

filenameprocessor/src/file_validation.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from re import match
44
from datetime import datetime
5-
from constants import VALID_VERSIONS, EMPTY_BATCH_FILE_SIZE_IN_BYTES
5+
from constants import VALID_VERSIONS
66
from elasticache import get_valid_vaccine_types_from_cache, get_supplier_system_from_cache
7-
from errors import InvalidFileKeyError, EmptyFileError
7+
from errors import InvalidFileKeyError
88

99

1010
def is_file_in_directory_root(file_key: str) -> bool:
@@ -72,9 +72,3 @@ def validate_file_key(file_key: str) -> tuple[str, str]:
7272
raise InvalidFileKeyError("Initial file validation failed: invalid file key")
7373

7474
return vaccine_type, supplier
75-
76-
77-
def validate_file_not_empty(s3_response: dict) -> None:
78-
"""Checks that the batch file from S3 is not empty or containing only the header row"""
79-
if s3_response.get("ContentLength", 0) <= EMPTY_BATCH_FILE_SIZE_IN_BYTES:
80-
raise EmptyFileError("Initial file validation failed: batch file was empty")

0 commit comments

Comments
 (0)