Skip to content

Commit dca9117

Browse files
authored
VED-372: Query Redis for valid vaccine types. (#612)
* VED-372: Query Redis for valid vaccine types. * VED-372: Tidy up.
1 parent ff323d8 commit dca9117

File tree

6 files changed

+39
-26
lines changed

6 files changed

+39
-26
lines changed

filenameprocessor/src/constants.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
AUDIT_TABLE_FILENAME_GSI = "filename_index"
1818

1919
PERMISSIONS_CONFIG_FILE_KEY = "permissions_config.json"
20+
VACCINE_TYPE_TO_DISEASES_HASH_KEY = "vacc_to_diseases"
2021

2122
ERROR_TYPE_TO_STATUS_CODE_MAP = {
2223
VaccineTypePermissionsError: 403,
@@ -50,9 +51,6 @@ class AuditTableKeys:
5051

5152
class Constants:
5253
"""Constants for the filenameprocessor lambda"""
53-
54-
VALID_VACCINE_TYPES = ["FLU", "COVID19", "MMR", "RSV"]
55-
5654
VALID_VERSIONS = ["V5"]
5755

5856
# Mappings from ODS code to supplier name.

filenameprocessor/src/elasticache.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44
from clients import s3_client, redis_client
5-
from constants import PERMISSIONS_CONFIG_FILE_KEY
5+
from constants import PERMISSIONS_CONFIG_FILE_KEY, VACCINE_TYPE_TO_DISEASES_HASH_KEY
66

77

88
def upload_to_elasticache(file_key: str, bucket_name: str) -> None:
@@ -16,3 +16,7 @@ def upload_to_elasticache(file_key: str, bucket_name: str) -> None:
1616
def get_permissions_config_json_from_cache() -> dict:
1717
"""Gets and returns the permissions config file content from ElastiCache (Redis)."""
1818
return json.loads(redis_client.get(PERMISSIONS_CONFIG_FILE_KEY))
19+
20+
21+
def get_valid_vaccine_types_from_cache() -> list[str]:
22+
return redis_client.hkeys(VACCINE_TYPE_TO_DISEASES_HASH_KEY)

filenameprocessor/src/file_key_validation.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from re import match
44
from datetime import datetime
55
from constants import Constants
6+
from elasticache import get_valid_vaccine_types_from_cache
67
from utils_for_filenameprocessor import identify_supplier
78
from errors import InvalidFileKeyError
89

@@ -47,9 +48,11 @@ def validate_file_key(file_key: str) -> tuple[str, str]:
4748
extension = file_key.split(".")[1]
4849
supplier = identify_supplier(ods_code)
4950

51+
valid_vaccine_types = get_valid_vaccine_types_from_cache()
52+
5053
# Validate each file key element
5154
if not (
52-
vaccine_type in Constants.VALID_VACCINE_TYPES
55+
vaccine_type in valid_vaccine_types
5356
and vaccination == "VACCINATIONS"
5457
and version in Constants.VALID_VERSIONS
5558
and supplier # Note that if supplier could be identified, this also implies that ODS code is valid

filenameprocessor/tests/test_file_key_validation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
class TestFileKeyValidation(TestCase):
1919
"""Tests for file_key_validation functions"""
20-
2120
def test_is_valid_datetime(self):
2221
"Tests that is_valid_datetime returns True for valid datetimes, and false otherwise"
2322
# Test case tuples are stuctured as (date_time_string, expected_result)
@@ -39,7 +38,8 @@ def test_is_valid_datetime(self):
3938
with self.subTest():
4039
self.assertEqual(is_valid_datetime(date_time_string), expected_result)
4140

42-
def test_validate_file_key(self):
41+
@patch("elasticache.redis_client.hkeys", return_value=["FLU", "RSV"])
42+
def test_validate_file_key(self, _mock_hkeys):
4343
"""Tests that file_key_validation returns True if all elements pass validation, and False otherwise"""
4444
# Test case tuples are structured as (file_key, expected_result)
4545
test_cases_for_success_scenarios = [
@@ -56,6 +56,7 @@ def test_validate_file_key(self):
5656
for file_key, expected_result in test_cases_for_success_scenarios:
5757
with self.subTest(f"SubTest for file key: {file_key}"):
5858
self.assertEqual(validate_file_key(file_key), expected_result)
59+
_mock_hkeys.assert_called_with("vacc_to_diseases")
5960

6061
key_format_error_message = "Initial file validation failed: invalid file key format"
6162
invalid_file_key_error_message = "Initial file validation failed: invalid file key"
@@ -101,3 +102,4 @@ def test_validate_file_key(self):
101102
with self.assertRaises(InvalidFileKeyError) as context:
102103
validate_file_key(file_key)
103104
self.assertEqual(str(context.exception), expected_result)
105+
_mock_hkeys.assert_called_with("vacc_to_diseases")

filenameprocessor/tests/test_lambda_handler.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from json import loads as json_loads
66
from contextlib import ExitStack
77
from copy import deepcopy
8+
from constants import VACCINE_TYPE_TO_DISEASES_HASH_KEY
9+
from elasticache import get_valid_vaccine_types_from_cache
810
import fakeredis
911
from boto3 import client as boto3_client
1012
from moto import mock_s3, mock_sqs, mock_firehose, mock_dynamodb
@@ -67,6 +69,7 @@ def run(self, result=None):
6769
patch("elasticache.redis_client", new=fakeredis.FakeStrictRedis()),
6870
# Patch the permissions config to allow all suppliers full permissions for all vaccine types.
6971
patch("elasticache.redis_client.get", return_value=all_permissions_config_content),
72+
patch("elasticache.redis_client.hkeys", return_value=all_vaccine_types_in_this_test_file),
7073
]
7174

7275
with ExitStack() as stack:
@@ -457,12 +460,18 @@ class TestLambdaHandlerConfig(TestCase):
457460
"""Tests for lambda_handler when a config file is uploaded."""
458461

459462
config_event = {
460-
"Records": [{"s3": {"bucket": {"name": BucketNames.CONFIG}, "object": {"key": (PERMISSIONS_CONFIG_FILE_KEY)}}}]
463+
"Records": [{"s3": {"bucket": {"name": BucketNames.CONFIG}, "object": {"key": PERMISSIONS_CONFIG_FILE_KEY}}}]
461464
}
462465

463466
def setUp(self):
464467
GenericSetUp(s3_client, firehose_client, sqs_client, dynamodb_client)
465468

469+
self.fake_redis = fakeredis.FakeStrictRedis(decode_responses=True)
470+
self.fake_redis.hmset(VACCINE_TYPE_TO_DISEASES_HASH_KEY, {"RSV": "[]"})
471+
redis_patcher = patch("elasticache.redis_client", new=self.fake_redis)
472+
self.addCleanup(redis_patcher.stop)
473+
redis_patcher.start()
474+
466475
def tearDown(self):
467476
GenericTearDown(s3_client, firehose_client, sqs_client, dynamodb_client)
468477

@@ -475,8 +484,10 @@ def test_elasticcache_failure_handled(self):
475484
}
476485
}
477486

478-
with patch("file_name_processor.upload_to_elasticache", side_effect=Exception("Upload failed")), \
479-
patch("file_name_processor.logger") as mock_logger:
487+
with (
488+
patch("file_name_processor.upload_to_elasticache", side_effect=Exception("Upload failed")),
489+
patch("file_name_processor.logger") as mock_logger
490+
):
480491

481492
result = handle_record(event)
482493

@@ -491,8 +502,6 @@ def test_elasticcache_failure_handled(self):
491502

492503
def test_successful_processing_from_configs(self):
493504
"""Tests that the permissions config file content is uploaded to elasticache successfully"""
494-
fake_redis = fakeredis.FakeStrictRedis()
495-
496505
ravs_rsv_file_details_1 = MockFileDetails.ravs_rsv_1
497506
ravs_rsv_file_details_2 = MockFileDetails.ravs_rsv_2
498507
s3_client.put_object(Bucket=BucketNames.SOURCE, Key=ravs_rsv_file_details_1.file_key)
@@ -509,17 +518,13 @@ def test_successful_processing_from_configs(self):
509518
Key=PERMISSIONS_CONFIG_FILE_KEY,
510519
Body=generate_permissions_config_content(ravs_rsv_permissions),
511520
)
512-
with patch("elasticache.redis_client", new=fake_redis):
513-
lambda_handler(self.config_event, None)
521+
lambda_handler(self.config_event, None)
514522
self.assertEqual(
515-
json_loads(fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_rsv_permissions}
523+
json_loads(self.fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_rsv_permissions}
516524
)
517525

518526
# Check that a RAVS RSV file processes successfully (as RAVS has permissions for RSV)
519-
with (
520-
patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_1.message_id),
521-
patch("elasticache.redis_client", new=fake_redis),
522-
):
527+
with patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_1.message_id):
523528
result = handle_record(record_1)
524529
expected_result = {
525530
"statusCode": 200,
@@ -537,17 +542,13 @@ def test_successful_processing_from_configs(self):
537542
Key=PERMISSIONS_CONFIG_FILE_KEY,
538543
Body=generate_permissions_config_content(ravs_no_rsv_permissions),
539544
)
540-
with patch("elasticache.redis_client", new=fake_redis):
541-
lambda_handler(self.config_event, None)
545+
lambda_handler(self.config_event, None)
542546
self.assertEqual(
543-
json_loads(fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_no_rsv_permissions}
547+
json_loads(self.fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_no_rsv_permissions}
544548
)
545549

546550
# Check that a RAVS RSV file fails to process (as RAVS now does not have permissions for RSV)
547-
with (
548-
patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_2.message_id),
549-
patch("elasticache.redis_client", new=fake_redis),
550-
):
551+
with patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_2.message_id):
551552
result = handle_record(record_2)
552553
expected_result = {
553554
"statusCode": 403,
@@ -572,6 +573,10 @@ class TestUnexpectedBucket(TestCase):
572573
def setUp(self):
573574
GenericSetUp(s3_client, firehose_client, sqs_client, dynamodb_client)
574575

576+
redis_patcher = patch("elasticache.redis_client.hkeys", return_value=all_vaccine_types_in_this_test_file)
577+
self.addCleanup(redis_patcher.stop)
578+
redis_patcher.start()
579+
575580
def tearDown(self):
576581
GenericTearDown(s3_client, firehose_client, sqs_client, dynamodb_client)
577582

filenameprocessor/tests/test_logging_decorator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def run(self, result=None):
7373
# Time is incremented by 1.0 for each call to time.time for ease of testing.
7474
# Range is set to a large number (100) due to many calls being made to time.time for some tests.
7575
patch("logging_decorator.time.time", side_effect=[0.0 + i for i in range(100)]),
76+
patch("clients.redis_client.hkeys", return_value=["FLU"])
7677
]
7778

7879
# Set up the ExitStack. Note that patches need to be explicitly started so that they will be applied even when

0 commit comments

Comments
 (0)