Skip to content

Commit 2b42e30

Browse files
committed
record-processor
1 parent f59ecc8 commit 2b42e30

File tree

7 files changed

+80
-88
lines changed

7 files changed

+80
-88
lines changed

recordprocessor/poetry.lock

Lines changed: 11 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

recordprocessor/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ readme = "README.md"
77
packages = [{include = "src"}]
88

99
[tool.poetry.dependencies]
10-
python = "~3.10"
10+
python = "~3.11"
1111
"fhir.resources" = "~7.0.2"
1212
boto3 = "~1.38.42"
1313
boto3-stubs-lite = {extras = ["dynamodb"], version = "~1.38.42"}

recordprocessor/src/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Constants for recordprocessor"""
22

33
import os
4+
from enum import Enum, auto
45

56
SOURCE_BUCKET_NAME = os.getenv("SOURCE_BUCKET_NAME")
67
ACK_BUCKET_NAME = os.getenv("ACK_BUCKET_NAME")
@@ -84,3 +85,15 @@ class Urls:
8485
NHS_NUMBER = "https://fhir.nhs.uk/Id/nhs-number"
8586
NULL_FLAVOUR_CODES = "http://terminology.hl7.org/CodeSystem/v3-NullFlavor"
8687
VACCINATION_PROCEDURE = "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure"
88+
89+
class Permission(Enum):
90+
CREATE = "C"
91+
UPDATE = "U"
92+
DELETE = "D"
93+
94+
95+
96+
class ActionFlag(Enum):
97+
NEW = Permission.CREATE
98+
UPDATE = Permission.UPDATE
99+
DELETE = Permission.DELETE

recordprocessor/src/file_level_validation.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from errors import InvalidHeaders, NoOperationPermissions
1111
from logging_decorator import file_level_validation_logging_decorator
1212
from audit_table import change_audit_table_status_to_processed, get_next_queued_file_details
13-
from constants import SOURCE_BUCKET_NAME, EXPECTED_CSV_HEADERS
13+
from constants import SOURCE_BUCKET_NAME, EXPECTED_CSV_HEADERS, Permission, ActionFlag
1414

1515

1616
def validate_content_headers(csv_content_reader) -> None:
@@ -27,29 +27,50 @@ def validate_action_flag_permissions(
2727
vaccine type and returns the set of allowed operations for that vaccine type.
2828
Raises a NoPermissionsError if the supplier does not have permission to perform any of the requested operations.
2929
"""
30-
# If the supplier has full permissions for the vaccine type, return a permission list containing full permissions
31-
if f"{vaccine_type}_FULL" in allowed_permissions_list:
32-
return {"CREATE", "UPDATE", "DELETE"}
3330

3431
# Get unique ACTION_FLAG values from the S3 file
3532
operations_requested = get_unique_action_flags_from_s3(csv_data)
3633

37-
# Convert action flags into the expected operation names
38-
requested_permissions_set = {
39-
f"{vaccine_type}_{'CREATE' if action == 'NEW' else action}" for action in operations_requested
40-
}
41-
42-
# Check if any of the CSV permissions match the allowed permissions
43-
if not requested_permissions_set.intersection(allowed_permissions_list):
34+
# Map ACTION_FLAGs to single-letter permissions
35+
requested_permissions = {ActionFlag[flag].value.value for flag in operations_requested if flag in ActionFlag.__members__}
36+
37+
if not requested_permissions:
38+
logger.warning("No valid ACTION_FLAGs found in file. Skipping permission validation.")
39+
return set()
40+
41+
42+
# Get allowed permission in single letters from allowed_permissions_list
43+
allowed_ops = set()
44+
for perm in allowed_permissions_list:
45+
if perm.startswith(f"{vaccine_type}."):
46+
allowed_ops.update(perm.split(".")[1])
47+
48+
allowed_ops = set()
49+
for perm in allowed_permissions_list:
50+
if not perm.startswith(f"{vaccine_type}."):
51+
continue
52+
53+
_, op_code = perm.split(".")
54+
if op_code == "CRUD":
55+
allowed_ops.update({"C", "R", "U", "D"})
56+
elif op_code == "CRUDS":
57+
allowed_ops.update({"C", "R", "U", "D", "S"})
58+
else:
59+
allowed_ops.add(op_code)
60+
61+
if not requested_permissions.intersection(allowed_ops):
4462
raise NoOperationPermissions(f"{supplier} does not have permissions to perform any of the requested actions.")
4563

64+
4665
logger.info(
4766
"%s permissions %s match one of the requested permissions required to %s",
4867
supplier,
4968
allowed_permissions_list,
50-
requested_permissions_set,
69+
requested_permissions,
5170
)
52-
return {perm.split("_")[1].upper() for perm in allowed_permissions_list if perm.startswith(vaccine_type)}
71+
72+
# Return allowed ops in full-word format for downstream logic
73+
return {perm.name for perm in Permission if perm.value in allowed_ops }
5374

5475

5576
def move_file(bucket_name: str, source_file_key: str, destination_file_key: str) -> None:

recordprocessor/tests/test_file_level_validation.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,27 @@ def test_validate_action_flag_permissions(self):
6262
# Test case tuples are stuctured as (vaccine_type, vaccine_permissions, file_content, expected_output)
6363
test_cases = [
6464
# FLU, full permissions, lowercase action flags
65-
("FLU", ["FLU_FULL"], valid_content_new_and_update_lowercase, {"CREATE", "UPDATE", "DELETE"}),
65+
("FLU", ["FLU.CRUD"], valid_content_new_and_update_lowercase, {"CREATE", "UPDATE", "DELETE"}),
6666
# FLU, partial permissions, uppercase action flags
67-
("FLU", ["FLU_CREATE"], valid_content_new_and_update_uppercase, {"CREATE"}),
67+
("FLU", ["FLU.C"], valid_content_new_and_update_uppercase, {"CREATE"}),
6868
# FLU, full permissions, mixed case action flags
69-
("FLU", ["FLU_FULL"], valid_content_new_and_update_mixedcase, {"CREATE", "UPDATE", "DELETE"}),
69+
("FLU", ["FLU.CRUD"], valid_content_new_and_update_mixedcase, {"CREATE", "UPDATE", "DELETE"}),
7070
# FLU, partial permissions (create)
71-
("FLU", ["FLU_DELETE", "FLU_CREATE"], valid_content_new_and_update_lowercase, {"CREATE", "DELETE"}),
71+
("FLU", ["FLU.D", "FLU.C"], valid_content_new_and_update_lowercase, {"CREATE", "DELETE"}),
7272
# FLU, partial permissions (update)
73-
("FLU", ["FLU_UPDATE"], valid_content_new_and_update_lowercase, {"UPDATE"}),
73+
("FLU", ["FLU.U"], valid_content_new_and_update_lowercase, {"UPDATE"}),
7474
# FLU, partial permissions (delete)
75-
("FLU", ["FLU_DELETE"], valid_content_new_and_delete_lowercase, {"DELETE"}),
75+
("FLU", ["FLU.D"], valid_content_new_and_delete_lowercase, {"DELETE"}),
7676
# COVID19, full permissions
77-
("COVID19", ["COVID19_FULL"], valid_content_new_and_delete_lowercase, {"CREATE", "UPDATE", "DELETE"}),
77+
("COVID19", ["COVID19.CRUD"], valid_content_new_and_delete_lowercase, {"CREATE", "UPDATE", "DELETE"}),
7878
# COVID19, partial permissions
79-
("COVID19", ["COVID19_UPDATE"], valid_content_update_and_delete_lowercase, {"UPDATE"}),
79+
("COVID19", ["COVID19.U"], valid_content_update_and_delete_lowercase, {"UPDATE"}),
8080
# RSV, full permissions
81-
("RSV", ["RSV_FULL"], valid_content_new_and_delete_lowercase, {"CREATE", "UPDATE", "DELETE"}),
81+
("RSV", ["RSV.CRUD"], valid_content_new_and_delete_lowercase, {"CREATE", "UPDATE", "DELETE"}),
8282
# RSV, partial permissions
83-
("RSV", ["RSV_UPDATE"], valid_content_update_and_delete_lowercase, {"UPDATE"}),
83+
("RSV", ["RSV.U"], valid_content_update_and_delete_lowercase, {"UPDATE"}),
8484
# RSV, full permissions, mixed case action flags
85-
("RSV", ["RSV_FULL"], valid_content_new_and_update_mixedcase, {"CREATE", "UPDATE", "DELETE"}),
85+
("RSV", ["RSV.CRUD"], valid_content_new_and_update_mixedcase, {"CREATE", "UPDATE", "DELETE"}),
8686
]
8787

8888
for vaccine_type, vaccine_permissions, file_content, expected_output in test_cases:
@@ -96,11 +96,11 @@ def test_validate_action_flag_permissions(self):
9696
# Test case tuples are stuctured as (vaccine_type, vaccine_permissions, file_content)
9797
test_cases = [
9898
# FLU, no permissions
99-
("FLU", ["FLU_UPDATE", "COVID19_FULL"], valid_content_new_and_delete_lowercase),
99+
("FLU", ["FLU.U", "COVID19.CRUDS"], valid_content_new_and_delete_lowercase),
100100
# COVID19, no permissions
101-
("COVID19", ["FLU_CREATE", "FLU_UPDATE"], valid_content_update_and_delete_lowercase),
101+
("COVID19", ["FLU.C", "FLU.U"], valid_content_update_and_delete_lowercase),
102102
# RSV, no permissions
103-
("RSV", ["FLU_CREATE", "FLU_UPDATE"], valid_content_update_and_delete_lowercase),
103+
("RSV", ["FLU.C", "FLU.U"], valid_content_update_and_delete_lowercase),
104104
]
105105

106106
for vaccine_type, vaccine_permissions, file_content in test_cases:

recordprocessor/tests/test_recordprocessor_main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ def make_inf_ack_assertions(self, file_details: FileDetails, passed_validation:
9191
"created_at_formatted_string", file_details.created_at_formatted_string
9292
)
9393

94+
print(f"actual_rows: {actual_rows,}")
95+
print(f"expected_rows: {expected_row}")
96+
9497
self.assertEqual(actual_rows, [InfAckFileRows.HEADERS, expected_row])
9598

9699
def make_kinesis_assertions(self, test_cases):

recordprocessor/tests/utils_for_recordprocessor_tests/values_for_recordprocessor_tests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,10 @@ def __init__(self, vaccine_type: str, supplier: str, ods_code: str, file_number:
150150
self.file_date_and_time_string = f"20000101T0000000{file_number}"
151151
self.message_id = f"{vaccine_type.lower()}_{supplier.lower()}_test_id"
152152
self.message_id_order = f"{vaccine_type.lower()}_{supplier.lower()}_test_id_{file_number}"
153-
self.full_permissions_list = [f"{vaccine_type}_FULL"]
154-
self.create_permissions_only = [f"{vaccine_type}_CREATE"]
155-
self.update_permissions_only = [f"{vaccine_type}_UPDATE"]
156-
self.delete_permissions_only = [f"{vaccine_type}_DELETE"]
153+
self.full_permissions_list = [f"{vaccine_type}.CRUD"]
154+
self.create_permissions_only = [f"{vaccine_type}.C"]
155+
self.update_permissions_only = [f"{vaccine_type}.U"]
156+
self.delete_permissions_only = [f"{vaccine_type}.D"]
157157

158158
self.queue_name = f"{supplier}_{vaccine_type}"
159159

0 commit comments

Comments
 (0)