Skip to content

Commit 7ffa206

Browse files
authored
Merge branch 'main' into feature/te-auto-test-deployments
2 parents aae0c1b + 44c9463 commit 7ffa206

File tree

66 files changed

+3047
-466
lines changed

Some content is hidden

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

66 files changed

+3047
-466
lines changed

infrastructure/stacks/iams-developer-roles/github_actions_policies.tf

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,12 @@ resource "aws_iam_policy" "lambda_management" {
6565
"lambda:GetPolicy",
6666
"lambda:GetAlias",
6767
"lambda:GetFunction",
68-
"lambda:GetProvisionedConcurrencyConfig",
6968
"lambda:GetLayerVersion",
70-
"lambda:PutProvisionedConcurrencyConfig"
69+
"lambda:GetProvisionedConcurrencyConfig",
70+
"lambda:PutProvisionedConcurrencyConfig",
71+
"lambda:DeleteProvisionedConcurrencyConfig",
72+
"lambda:ListProvisionedConcurrencyConfigs",
73+
7174
],
7275
Resource = [
7376
"arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:function:eligibility_signposting_api",
@@ -109,7 +112,7 @@ resource "aws_iam_policy" "dynamodb_management" {
109112
}
110113
],
111114
# to create test users in preprod
112-
var.environment == "preprod" ? [
115+
var.environment == "preprod" ? [
113116
{
114117
Effect = "Allow",
115118
Action = [

infrastructure/stacks/iams-developer-roles/iams_permissions_boundary.tf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,11 @@ data "aws_iam_policy_document" "permissions_boundary" {
152152
"lambda:RemovePermission",
153153
"lambda:GetPolicy",
154154
"lambda:GetAlias",
155-
"lambda:GetProvisionedConcurrencyConfig",
156155
"lambda:GetLayerVersion",
156+
"lambda:GetProvisionedConcurrencyConfig",
157157
"lambda:PutProvisionedConcurrencyConfig",
158+
"lambda:DeleteProvisionedConcurrencyConfig",
159+
"lambda:ListProvisionedConcurrencyConfigs",
158160

159161
# CloudWatch Logs - log management
160162
"logs:CreateLogGroup",

src/eligibility_signposting_api/common/error_handler.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,26 @@
66
from werkzeug.exceptions import HTTPException
77

88
from eligibility_signposting_api.common.api_error_response import INTERNAL_SERVER_ERROR
9+
from eligibility_signposting_api.services.processors.token_processor import TokenError
910

1011
logger = logging.getLogger(__name__)
1112

1213

1314
def handle_exception(e: Exception) -> ResponseReturnValue | HTTPException:
14-
logger.exception("Unexpected Exception", exc_info=e)
15-
16-
# Let Flask handle its own exceptions for now.
1715
if isinstance(e, HTTPException):
1816
return e
1917

20-
full_traceback = "".join(traceback.format_exception(e))
18+
if isinstance(e, TokenError):
19+
tb = traceback.extract_tb(e.__traceback__)
20+
clean_traceback = "".join(traceback.format_list(tb))
21+
logger.error("A ValueError occurred (value redacted). Traceback follows:\n%s", clean_traceback)
22+
log_msg = f"An unexpected error occurred: {clean_traceback}"
23+
else:
24+
full_traceback = "".join(traceback.format_exception(e))
25+
logger.exception("Unexpected Exception", exc_info=e)
26+
log_msg = f"An unexpected error occurred: {full_traceback}"
27+
2128
response = INTERNAL_SERVER_ERROR.log_and_generate_response(
22-
log_message=f"An unexpected error occurred: {full_traceback}", diagnostics="An unexpected error occurred."
29+
log_message=log_msg, diagnostics="An unexpected error occurred."
2330
)
2431
return make_response(response.get("body"), response.get("statusCode"), response.get("headers"))

src/eligibility_signposting_api/services/processors/token_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def parse(token: str) -> ParsedToken:
6060

6161
format_str = format_match.group(1) if format_match else None
6262

63-
last_part = re.sub(r":DATE\(.*?\)", "", token_name, flags=re.IGNORECASE)
63+
last_part = re.sub(r":DATE\([^)]*\)", "", token_name, flags=re.IGNORECASE)
6464

6565
if len(token_parts) == TokenParser.MIN_TOKEN_PARTS:
6666
name = last_part.upper()

src/eligibility_signposting_api/services/processors/token_processor.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import re
22
from dataclasses import Field, fields, is_dataclass
33
from datetime import UTC, datetime
4-
from typing import Any, Never, TypeVar
4+
from typing import Any, Never
55

66
from wireup import service
77

88
from eligibility_signposting_api.config.contants import ALLOWED_CONDITIONS
99
from eligibility_signposting_api.model.person import Person
1010
from eligibility_signposting_api.services.processors.token_parser import ParsedToken, TokenParser
1111

12-
T = TypeVar("T")
13-
14-
1512
TARGET_ATTRIBUTE_LEVEL = "TARGET"
1613
PERSON_ATTRIBUTE_LEVEL = "PERSON"
1714
ALLOWED_TARGET_ATTRIBUTES = {
@@ -27,10 +24,14 @@
2724
}
2825

2926

27+
class TokenError(Exception):
28+
"""Person value error."""
29+
30+
3031
@service
3132
class TokenProcessor:
3233
@staticmethod
33-
def find_and_replace_tokens(person: Person, data_class: T) -> T:
34+
def find_and_replace_tokens[T](person: Person, data_class: T) -> T:
3435
if not is_dataclass(data_class):
3536
return data_class
3637
for class_field in fields(data_class):
@@ -95,8 +96,9 @@ def replace_token(text: str, person: Person) -> str:
9596
for attribute in person.data:
9697
is_person_attribute = attribute.get("ATTRIBUTE_TYPE") == PERSON_ATTRIBUTE_LEVEL
9798
is_allowed_target = parsed_token.attribute_name.upper() in ALLOWED_CONDITIONS.__args__
99+
is_correct_target = parsed_token.attribute_name.upper() == attribute.get("ATTRIBUTE_TYPE")
98100

99-
if (is_allowed_target or is_person_attribute) and key_to_find in attribute:
101+
if ((is_allowed_target and is_correct_target) or is_person_attribute) and key_to_find in attribute:
100102
found_attribute = attribute
101103
key_to_replace = key_to_find
102104
break
@@ -120,9 +122,9 @@ def handle_token_not_found(parsed_token: ParsedToken, token: str) -> Never:
120122
raise ValueError(message)
121123

122124
@staticmethod
123-
def apply_formatting(attribute: dict[str, T], attribute_value: str, date_format: str | None) -> str:
125+
def apply_formatting[T](attributes: dict[str, T], attribute_name: str, date_format: str | None) -> str:
124126
try:
125-
attribute_data = attribute.get(attribute_value)
127+
attribute_data = attributes.get(attribute_name)
126128
if (date_format or date_format == "") and attribute_data:
127129
replace_with_date_object = datetime.strptime(str(attribute_data), "%Y%m%d").replace(tzinfo=UTC)
128130
replace_with = replace_with_date_object.strftime(str(date_format))
@@ -132,3 +134,6 @@ def apply_formatting(attribute: dict[str, T], attribute_value: str, date_format:
132134
except AttributeError as error:
133135
message = "Invalid token format"
134136
raise AttributeError(message) from error
137+
except ValueError as error:
138+
message = "Invalid value error"
139+
raise TokenError(message) from error

tests/e2e/data/configs/inProgressTestConfigs/ELI-376-405/AUTO_RSV_ELI-376-02.json

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"CampaignConfig": {
3+
"ID": "AUTO_RSV_ELI-223-01-Campaign-ID",
4+
"Version": 1,
5+
"Name": "ELI-223-01-Iteration-Config-Name",
6+
"Type": "V",
7+
"Target": "RSV",
8+
"Manager": [
9+
10+
],
11+
"Approver": [
12+
13+
],
14+
"Reviewer": [
15+
16+
],
17+
"IterationFrequency": "X",
18+
"IterationType": "O",
19+
"IterationTime": "07:00:00",
20+
"StartDate": "20250717",
21+
"EndDate": "20350717",
22+
"ApprovalMinimum": 0,
23+
"ApprovalMaximum": 0,
24+
"DefaultCommsRouting": "BOOK_NBS",
25+
"Iterations": [
26+
{
27+
"ID": "AUTO_RSV_ELI-223-01-Iteration-ID",
28+
"DefaultCommsRouting": "TEST_ACTION",
29+
"DefaultNotActionableRouting": "TEST_NOT_ACTION",
30+
"DefaultNotEligibleRouting": "TEST_NOT_ELI",
31+
"Version": 1,
32+
"Name": "ELI-223-01-Iteration-Config-Name",
33+
"IterationDate": "20240808",
34+
"IterationNumber": 1,
35+
"CommsType": "I",
36+
"ApprovalMinimum": 0,
37+
"ApprovalMaximum": 0,
38+
"Type": "O",
39+
"IterationCohorts": [
40+
{
41+
"CohortLabel": "rsv_eli_223_cohort_1",
42+
"CohortGroup": "rsv_eli_223_cohort_group",
43+
"PositiveDescription": "are a member of eli_223_cohort_group",
44+
"NegativeDescription": "are not a member of eli_223_cohort_group",
45+
"Priority": 0
46+
}
47+
],
48+
"IterationRules": [
49+
{
50+
"Type": "S",
51+
"Name": "Already Vaccinated",
52+
"Description": "## You've had your RSV vaccination\\n\\nWe believe you had the RSV vaccination on [[TARGET.RSV.LAST_SUCCESSFUL_DATE:DATE(%d %B %Y)]]",
53+
"Priority": 200,
54+
"AttributeLevel": "TARGET",
55+
"AttributeTarget": "RSV",
56+
"AttributeName": "LAST_SUCCESSFUL_DATE",
57+
"Operator": "Y>=",
58+
"Comparator": "-25",
59+
"RuleStop": "Y"
60+
}
61+
],
62+
"ActionsMapper": {
63+
"TEST_ACTION": {
64+
"ExternalRoutingCode": "TestActionDefault",
65+
"ActionDescription": "TestActionDefault Description",
66+
"ActionType": "ButtonWithAuthLink",
67+
"UrlLink": "http://www.nhs.uk/book-rsv",
68+
"UrlLabel": "TestActionDefault"
69+
},
70+
"TEST_NOT_ACTION": {
71+
"ExternalRoutingCode": "TestNotAction",
72+
"ActionDescription": "TestNotAction Description",
73+
"ActionType": "ButtonWithAuthLink",
74+
"UrlLink": null,
75+
"UrlLabel": ""
76+
},
77+
"AMEND_NBS": {
78+
"ExternalRoutingCode": "AmendNBS",
79+
"ActionDescription": "## You have an RSV vaccination appointment\n You can view, change or cancel your appointment below.",
80+
"ActionType": "ButtonWithAuthLink",
81+
"UrlLink": "http://www.nhs.uk/book-rsv",
82+
"UrlLabel": "Manage your appointment"
83+
}
84+
}
85+
}
86+
]
87+
}
88+
}

0 commit comments

Comments
 (0)