Skip to content

Commit 3e8f6a9

Browse files
committed
VED-386: Handle the new format for supplier permissions.
1 parent 793544d commit 3e8f6a9

File tree

10 files changed

+53
-300
lines changed

10 files changed

+53
-300
lines changed

filenameprocessor/src/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
AUDIT_TABLE_QUEUE_NAME_GSI = "queue_name_index"
1717
AUDIT_TABLE_FILENAME_GSI = "filename_index"
1818

19-
PERMISSIONS_CONFIG_FILE_KEY = "permissions_config.json"
19+
SUPPLIER_PERMISSIONS_HASH_KEY = "supplier_permissions"
2020
VACCINE_TYPE_TO_DISEASES_HASH_KEY = "vacc_to_diseases"
2121

2222
ERROR_TYPE_TO_STATUS_CODE_MAP = {

filenameprocessor/src/elasticache.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
"Upload the content from a config file in S3 to ElastiCache (Redis)"
22

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

77

8-
def upload_to_elasticache(file_key: str, bucket_name: str) -> None:
9-
"""Uploads the config file content from S3 to ElastiCache (Redis)."""
10-
config_file = s3_client.get_object(Bucket=bucket_name, Key=file_key)
11-
config_file_content = config_file["Body"].read().decode("utf-8")
12-
# Use the file_key as the Redis key and file content as the value
13-
redis_client.set(file_key, config_file_content)
14-
15-
16-
def get_permissions_config_json_from_cache() -> dict:
8+
def get_supplier_permissions_from_cache(supplier_system: str) -> list[str]:
179
"""Gets and returns the permissions config file content from ElastiCache (Redis)."""
18-
return json.loads(redis_client.get(PERMISSIONS_CONFIG_FILE_KEY))
10+
permissions_str = redis_client.hget(SUPPLIER_PERMISSIONS_HASH_KEY, supplier_system)
11+
return json.loads(permissions_str) if permissions_str else []
1912

2013

2114
def get_valid_vaccine_types_from_cache() -> list[str]:

filenameprocessor/src/file_name_processor.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from make_and_upload_ack_file import make_and_upload_the_ack_file
1515
from audit_table import upsert_audit_table, get_next_queued_file_details, ensure_file_is_not_a_duplicate
1616
from clients import logger
17-
from elasticache import upload_to_elasticache
1817
from logging_decorator import logging_decorator
1918
from supplier_permissions import validate_vaccine_type_permissions
2019
from errors import (
@@ -140,17 +139,6 @@ def handle_record(record) -> dict:
140139
"supplier": supplier
141140
}
142141

143-
elif "config" in bucket_name:
144-
try:
145-
upload_to_elasticache(file_key, bucket_name)
146-
logger.info("%s content successfully uploaded to cache", file_key)
147-
message = "File content successfully uploaded to cache"
148-
return {"statusCode": 200, "message": message, "file_key": file_key}
149-
except Exception as error: # pylint: disable=broad-except
150-
logger.error("Error uploading to cache for file '%s': %s", file_key, error)
151-
message = "Failed to upload file content to cache"
152-
return {"statusCode": 500, "message": message, "file_key": file_key, "error": str(error)}
153-
154142
else:
155143
try:
156144
vaccine_type, supplier = validate_file_key(file_key)

filenameprocessor/src/send_sqs_message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def send_to_supplier_queue(message_body: dict, vaccine_type: str, supplier: str)
2727

2828

2929
def make_and_send_sqs_message(
30-
file_key: str, message_id: str, permission: str, vaccine_type: str, supplier: str, created_at_formatted_string: str
30+
file_key: str, message_id: str, permission: list[str], vaccine_type: str, supplier: str, created_at_formatted_string: str
3131
) -> None:
3232
"""Attempts to send a message to the SQS queue. Raises an exception if the message is not successfully sent."""
3333
message_body = {

filenameprocessor/src/supplier_permissions.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,18 @@
22

33
from clients import logger
44
from errors import VaccineTypePermissionsError
5-
from elasticache import get_permissions_config_json_from_cache
6-
7-
8-
def get_supplier_permissions(supplier: str) -> list:
9-
"""
10-
Returns the permissions for the given supplier.
11-
Defaults return value is an empty list, including when the supplier has no permissions.
12-
"""
13-
permissions_config = get_permissions_config_json_from_cache()
14-
return permissions_config.get("all_permissions", {}).get(supplier, [])
5+
from elasticache import get_supplier_permissions_from_cache
156

167

178
def validate_vaccine_type_permissions(vaccine_type: str, supplier: str) -> list:
189
"""
1910
Returns the list of permissions for the given supplier.
2011
Raises an exception if the supplier does not have at least one permission for the vaccine type.
2112
"""
22-
supplier_permissions = get_supplier_permissions(supplier)
13+
supplier_permissions = get_supplier_permissions_from_cache(supplier)
2314

2415
# Validate that supplier has at least one permissions for the vaccine type
25-
if not any(vaccine_type in permission for permission in supplier_permissions):
16+
if not any(permission.split(".")[0] == vaccine_type for permission in supplier_permissions):
2617
error_message = f"Initial file validation failed: {supplier} does not have permissions for {vaccine_type}"
2718
logger.error(error_message)
2819
raise VaccineTypePermissionsError(error_message)
Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
"""Tests for elasticache functions"""
2-
2+
import json
33
from unittest import TestCase
44
from unittest.mock import patch
5-
import fakeredis
65
from boto3 import client as boto3_client
76
from moto import mock_s3
87

9-
from tests.utils_for_tests.mock_environment_variables import MOCK_ENVIRONMENT_DICT, BucketNames
8+
from tests.utils_for_tests.mock_environment_variables import MOCK_ENVIRONMENT_DICT
109
from tests.utils_for_tests.generic_setup_and_teardown import GenericSetUp, GenericTearDown
11-
from tests.utils_for_tests.utils_for_filenameprocessor_tests import generate_permissions_config_content
1210

1311
# Ensure environment variables are mocked before importing from src files
1412
with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT):
15-
from elasticache import upload_to_elasticache, get_permissions_config_json_from_cache
13+
from elasticache import get_supplier_permissions_from_cache
1614
from clients import REGION_NAME
17-
from constants import PERMISSIONS_CONFIG_FILE_KEY
1815

1916
s3_client = boto3_client("s3", region_name=REGION_NAME)
2017

@@ -32,32 +29,16 @@ def tearDown(self):
3229
"""Tear down the S3 buckets"""
3330
GenericTearDown(s3_client)
3431

35-
def test_permissions_caching(self):
36-
"""
37-
Test that upload_to_elasticache successfully uploads the file to elasticache, which is then successfully read
38-
by get_permissions_config_json_from_cache
39-
"""
40-
mock_permissions_1 = {"test_supplier_1": ["RSV_FULL"], "test_supplier_2": ["FLU_CREATE", "FLU_UPDATE"]}
41-
mock_permissions_config_1 = generate_permissions_config_content(mock_permissions_1)
42-
43-
mock_permissions_2 = {
44-
"test_supplier_1": ["FLU_FULL"],
45-
"test_supplier_2": ["RSV_CREATE"],
46-
"test_supplier_3": ["RSV_UPDATE"],
47-
}
48-
mock_permissions_config_2 = generate_permissions_config_content(mock_permissions_2)
49-
50-
with patch("elasticache.redis_client", fakeredis.FakeStrictRedis()):
51-
# Test that the permissions config is successfully uploaded to elasticache
52-
s3_client.put_object(
53-
Bucket=BucketNames.CONFIG, Key=PERMISSIONS_CONFIG_FILE_KEY, Body=mock_permissions_config_1
54-
)
55-
upload_to_elasticache(PERMISSIONS_CONFIG_FILE_KEY, BucketNames.CONFIG)
56-
self.assertEqual(get_permissions_config_json_from_cache(), {"all_permissions": mock_permissions_1})
57-
58-
# Test that the cache is updated with the new permissions config
59-
s3_client.put_object(
60-
Bucket=BucketNames.CONFIG, Key=PERMISSIONS_CONFIG_FILE_KEY, Body=mock_permissions_config_2
61-
)
62-
upload_to_elasticache(PERMISSIONS_CONFIG_FILE_KEY, BucketNames.CONFIG)
63-
self.assertEqual(get_permissions_config_json_from_cache(), {"all_permissions": mock_permissions_2})
32+
@patch("elasticache.redis_client")
33+
def test_get_supplier_permissions_from_cache(self, mock_redis_client):
34+
mock_redis_client.hget.return_value = json.dumps(["COVID19.CRUDS", "RSV.CRUDS"])
35+
result = get_supplier_permissions_from_cache("TEST_SUPPLIER")
36+
self.assertEqual(result, ["COVID19.CRUDS", "RSV.CRUDS"])
37+
mock_redis_client.hget.assert_called_once_with("supplier_permissions", "TEST_SUPPLIER")
38+
39+
@patch("elasticache.redis_client")
40+
def test_get_supplier_permissions_from_cache_not_found(self, mock_redis_client):
41+
mock_redis_client.hget.return_value = None
42+
result = get_supplier_permissions_from_cache("TEST_SUPPLIER")
43+
self.assertEqual(result, [])
44+
mock_redis_client.hget.assert_called_once_with("supplier_permissions", "TEST_SUPPLIER")

filenameprocessor/tests/test_lambda_handler.py

Lines changed: 8 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
"""Tests for lambda_handler"""
2+
import json
23
import sys
34
from unittest.mock import patch
45
from unittest import TestCase
56
from json import loads as json_loads
67
from contextlib import ExitStack
78
from copy import deepcopy
8-
from constants import VACCINE_TYPE_TO_DISEASES_HASH_KEY
9-
from elasticache import get_valid_vaccine_types_from_cache
109
import fakeredis
1110
from boto3 import client as boto3_client
1211
from moto import mock_s3, mock_sqs, mock_firehose, mock_dynamodb
1312

1413
from tests.utils_for_tests.generic_setup_and_teardown import GenericSetUp, GenericTearDown
1514
from tests.utils_for_tests.utils_for_filenameprocessor_tests import (
16-
generate_permissions_config_content,
17-
generate_dict_full_permissions_all_suppliers_and_vaccine_types,
1815
add_entry_to_table,
1916
assert_audit_table_entry,
2017
)
@@ -25,7 +22,7 @@
2522
with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT):
2623
from file_name_processor import lambda_handler, handle_record
2724
from clients import REGION_NAME
28-
from constants import PERMISSIONS_CONFIG_FILE_KEY, AUDIT_TABLE_NAME, FileStatus, AuditTableKeys
25+
from constants import AUDIT_TABLE_NAME, FileStatus, AuditTableKeys
2926

3027

3128
s3_client = boto3_client("s3", region_name=REGION_NAME)
@@ -37,11 +34,9 @@
3734
# for all vaccine types. This default is overridden for some specific tests.
3835
all_vaccine_types_in_this_test_file = ["RSV", "FLU"]
3936
all_suppliers_in_this_test_file = ["RAVS", "EMIS"]
40-
all_permissions_config_content = generate_permissions_config_content(
41-
generate_dict_full_permissions_all_suppliers_and_vaccine_types(
42-
all_suppliers_in_this_test_file, all_vaccine_types_in_this_test_file
43-
)
44-
)
37+
all_permissions_in_this_test_file = [
38+
f"{vaccine_type}.CRUDS" for vaccine_type in all_vaccine_types_in_this_test_file
39+
]
4540

4641

4742
@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT)
@@ -68,7 +63,7 @@ def run(self, result=None):
6863
# Patch redis_client to use a fake redis client.
6964
patch("elasticache.redis_client", new=fakeredis.FakeStrictRedis()),
7065
# Patch the permissions config to allow all suppliers full permissions for all vaccine types.
71-
patch("elasticache.redis_client.get", return_value=all_permissions_config_content),
66+
patch("elasticache.redis_client.hget", return_value=json.dumps(all_permissions_in_this_test_file)),
7267
patch("elasticache.redis_client.hkeys", return_value=all_vaccine_types_in_this_test_file),
7368
]
7469

@@ -148,7 +143,7 @@ def assert_sqs_message(self, file_details: MockFileDetails) -> None:
148143
self.assertEqual(len(received_messages), 1)
149144
expected_sqs_message = {
150145
**file_details.sqs_message_body,
151-
"permission": [f"{vaccine_type.upper()}_FULL" for vaccine_type in all_vaccine_types_in_this_test_file],
146+
"permission": all_permissions_in_this_test_file,
152147
}
153148
self.assertEqual(json_loads(received_messages[0]["Body"]), expected_sqs_message)
154149

@@ -356,10 +351,9 @@ def test_lambda_invalid_permissions_other_files_in_queue(self):
356351
add_entry_to_table(queued_file_details, FileStatus.QUEUED)
357352

358353
# Mock the supplier permissions with a value which doesn't include the requested Flu permissions
359-
permissions_config_content = generate_permissions_config_content({"EMIS": ["RSV_DELETE"]})
360354
with ( # noqa: E999
361355
patch("file_name_processor.uuid4", return_value=file_details.message_id), # noqa: E999
362-
patch("elasticache.redis_client.get", return_value=permissions_config_content), # noqa: E999
356+
patch("elasticache.redis_client.hget", return_value=None), # noqa: E999
363357
patch("file_name_processor.invoke_filename_lambda") as mock_invoke_filename_lambda, # noqa: E999
364358
): # noqa: E999
365359
lambda_handler(self.make_event([self.make_record(file_details.file_key)]), None)
@@ -451,117 +445,6 @@ def test_lambda_handler_multiple_records_for_same_queue(self):
451445
mock_invoke_filename_lambda.assert_not_called()
452446

453447

454-
@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT)
455-
@mock_s3
456-
@mock_dynamodb
457-
@mock_sqs
458-
@mock_firehose
459-
class TestLambdaHandlerConfig(TestCase):
460-
"""Tests for lambda_handler when a config file is uploaded."""
461-
462-
config_event = {
463-
"Records": [{"s3": {"bucket": {"name": BucketNames.CONFIG}, "object": {"key": PERMISSIONS_CONFIG_FILE_KEY}}}]
464-
}
465-
466-
def setUp(self):
467-
GenericSetUp(s3_client, firehose_client, sqs_client, dynamodb_client)
468-
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-
475-
def tearDown(self):
476-
GenericTearDown(s3_client, firehose_client, sqs_client, dynamodb_client)
477-
478-
def test_elasticcache_failure_handled(self):
479-
"Tests if elastic cache failure is handled when service fails to send message"
480-
event = {
481-
"s3": {
482-
"bucket": {"name": "my-config-bucket"}, # triggers 'config' branch
483-
"object": {"key": "testfile.csv"}
484-
}
485-
}
486-
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-
):
491-
492-
result = handle_record(event)
493-
494-
self.assertEqual(result["statusCode"], 500)
495-
self.assertEqual(result["message"], "Failed to upload file content to cache")
496-
self.assertEqual(result["file_key"], "testfile.csv")
497-
self.assertIn("error", result)
498-
499-
mock_logger.error.assert_called_once()
500-
logged_msg = mock_logger.error.call_args[0][0]
501-
self.assertIn("Error uploading to cache", logged_msg)
502-
503-
def test_successful_processing_from_configs(self):
504-
"""Tests that the permissions config file content is uploaded to elasticache successfully"""
505-
ravs_rsv_file_details_1 = MockFileDetails.ravs_rsv_1
506-
ravs_rsv_file_details_2 = MockFileDetails.ravs_rsv_2
507-
s3_client.put_object(Bucket=BucketNames.SOURCE, Key=ravs_rsv_file_details_1.file_key)
508-
s3_client.put_object(Bucket=BucketNames.SOURCE, Key=ravs_rsv_file_details_2.file_key)
509-
record_1 = {"s3": {"bucket": {"name": BucketNames.SOURCE}, "object": {"key": ravs_rsv_file_details_1.file_key}}}
510-
record_2 = {"s3": {"bucket": {"name": BucketNames.SOURCE}, "object": {"key": ravs_rsv_file_details_2.file_key}}}
511-
512-
ravs_rsv_permissions = {"RAVS": ["RSV_FULL"], "EMIS": ["FLU_CREATE", "FLU_UPDATE"]}
513-
ravs_no_rsv_permissions = {"RAVS": ["FLU_FULL"], "EMIS": ["RSV_CREATE", "RSV_UPDATE"], "TPP": ["RSV_DELETE"]}
514-
515-
# Test that the permissions config file content is uploaded to elasticache successfully
516-
s3_client.put_object(
517-
Bucket=BucketNames.CONFIG,
518-
Key=PERMISSIONS_CONFIG_FILE_KEY,
519-
Body=generate_permissions_config_content(ravs_rsv_permissions),
520-
)
521-
lambda_handler(self.config_event, None)
522-
self.assertEqual(
523-
json_loads(self.fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_rsv_permissions}
524-
)
525-
526-
# Check that a RAVS RSV file processes successfully (as RAVS has permissions for RSV)
527-
with patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_1.message_id):
528-
result = handle_record(record_1)
529-
expected_result = {
530-
"statusCode": 200,
531-
"message": "Successfully sent to SQS for further processing",
532-
"file_key": ravs_rsv_file_details_1.file_key,
533-
"message_id": ravs_rsv_file_details_1.message_id,
534-
"vaccine_type": ravs_rsv_file_details_1.vaccine_type,
535-
"supplier": ravs_rsv_file_details_1.supplier
536-
}
537-
self.assertEqual(result, expected_result)
538-
539-
# Test that the elasticache is successfully updated when the lambda is invoked with a new permissions config
540-
s3_client.put_object(
541-
Bucket=BucketNames.CONFIG,
542-
Key=PERMISSIONS_CONFIG_FILE_KEY,
543-
Body=generate_permissions_config_content(ravs_no_rsv_permissions),
544-
)
545-
lambda_handler(self.config_event, None)
546-
self.assertEqual(
547-
json_loads(self.fake_redis.get(PERMISSIONS_CONFIG_FILE_KEY)), {"all_permissions": ravs_no_rsv_permissions}
548-
)
549-
550-
# Check that a RAVS RSV file fails to process (as RAVS now does not have permissions for RSV)
551-
with patch("file_name_processor.uuid4", return_value=ravs_rsv_file_details_2.message_id):
552-
result = handle_record(record_2)
553-
expected_result = {
554-
"statusCode": 403,
555-
"message": "Infrastructure Level Response Value - Processing Error",
556-
"file_key": ravs_rsv_file_details_2.file_key,
557-
"message_id": ravs_rsv_file_details_2.message_id,
558-
"error": "Initial file validation failed: RAVS does not have permissions for RSV",
559-
"vaccine_type": ravs_rsv_file_details_2.vaccine_type,
560-
"supplier": ravs_rsv_file_details_2.supplier
561-
}
562-
self.assertEqual(result, expected_result)
563-
564-
565448
@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT)
566449
@mock_s3
567450
@mock_dynamodb

0 commit comments

Comments
 (0)