Skip to content

Commit f9f99ab

Browse files
lint fixes
1 parent 4bf38e9 commit f9f99ab

File tree

8 files changed

+58
-79
lines changed

8 files changed

+58
-79
lines changed

src/rules_validation_api/app.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import json
2+
from pathlib import Path
23

34
from pydantic import ValidationError
45

56
from rules_validation_api.validators.campaign_config_validator import CampaignConfigValidation
67

78

89
def main() -> None:
9-
print("Starting rules validation")
10-
with open("campaign_config.json") as file:
10+
with Path.open(Path("campaign_config.json")) as file:
1111
json_data = json.load(file) # this validates json
1212

1313
try:
14-
user = CampaignConfigValidation(**json_data["CampaignConfig"])
15-
print("validation successful")
14+
CampaignConfigValidation(**json_data["CampaignConfig"])
1615
except ValidationError as e:
17-
print(e)
16+
print(e) # noqa: T201
1817

1918

2019
if __name__ == "__main__":
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,8 @@
1-
from pydantic import Field, field_validator
1+
from pydantic import Field
22

33
from eligibility_signposting_api.model.campaign_config import CampaignConfig
44
from rules_validation_api.validators.iteration_validator import IterationValidation
55

66

77
class CampaignConfigValidation(CampaignConfig):
88
iterations: list[IterationValidation] = Field(..., min_length=1, alias="Iterations")
9-
10-
# @field_validator("id")
11-
# def validate_name(cls, value: str) -> str:
12-
# if not value.strip():
13-
# raise ValueError("campaign ID must not be empty")
14-
# return value
15-
#
16-
# @field_validator("type")
17-
# def validate_type(cls, value: str) -> str:
18-
# allowed_values = {"V", "S"}
19-
# if value not in allowed_values:
20-
# raise ValueError(f"type must be one of {allowed_values}")
21-
# return value
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
from pydantic import field_validator
2-
31
from eligibility_signposting_api.model.campaign_config import IterationRule
42

53

64
class IterationRuleValidation(IterationRule):
7-
@field_validator("type")
8-
def validate_type(cls, value: str) -> str:
9-
if not value.strip():
10-
raise ValueError("type must not be empty")
11-
return value
5+
pass
Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import typing
2+
13
from pydantic import Field, ValidationError, model_validator
24
from pydantic_core import InitErrorDetails
35

@@ -11,21 +13,17 @@ class IterationValidation(Iteration):
1113
actions_mapper: ActionsMapperValidator = Field(..., alias="ActionsMapper")
1214

1315
@model_validator(mode="after")
14-
def validate_default_comms_routing_in_actions_mapper(self):
16+
def validate_default_comms_routing_in_actions_mapper(self) -> typing.Self:
1517
default_routing = self.default_comms_routing
1618
actions_mapper = self.actions_mapper.root.keys()
1719

18-
if default_routing:
19-
if not actions_mapper or default_routing not in actions_mapper:
20-
error = InitErrorDetails(
21-
type='value_error',
22-
loc=('actions_mapper',),
23-
input=actions_mapper,
24-
ctx={'error': f"Missing entry for DefaultCommsRouting '{default_routing}' in ActionsMapper"}
25-
)
26-
raise ValidationError.from_exception_data(
27-
title='IterationValidation',
28-
line_errors=[error]
29-
)
20+
if default_routing and (not actions_mapper or default_routing not in actions_mapper):
21+
error = InitErrorDetails(
22+
type="value_error",
23+
loc=("actions_mapper",),
24+
input=actions_mapper,
25+
ctx={"error": f"Missing entry for DefaultCommsRouting '{default_routing}' in ActionsMapper"},
26+
)
27+
raise ValidationError.from_exception_data(title="IterationValidation", line_errors=[error])
3028

3129
return self

tests/unit/validation/conftest.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,15 @@ def valid_campaign_config_with_only_mandatory_fields():
2828
"DefaultNotActionableRouting": "RouteC",
2929
"IterationCohorts": [],
3030
"IterationRules": [],
31-
"ActionsMapper":
32-
{
33-
"BOOK_NBS":
34-
{
35-
"ExternalRoutingCode": "BookNBS",
36-
"ActionDescription": "",
37-
"ActionType": "ButtonWithAuthLink",
38-
"UrlLink": "http://www.nhs.uk/book-rsv",
39-
"UrlLabel": "Continue to booking"
40-
}
31+
"ActionsMapper": {
32+
"BOOK_NBS": {
33+
"ExternalRoutingCode": "BookNBS",
34+
"ActionDescription": "",
35+
"ActionType": "ButtonWithAuthLink",
36+
"UrlLink": "http://www.nhs.uk/book-rsv",
37+
"UrlLabel": "Continue to booking",
4138
}
39+
},
4240
}
4341
],
4442
}
@@ -67,5 +65,5 @@ def valid_available_action():
6765
"ActionDescription": "",
6866
"ActionType": "ButtonWithAuthLink",
6967
"UrlLink": "http://www.nhs.uk/book-rsv",
70-
"UrlLabel": "Continue to booking"
68+
"UrlLabel": "Continue to booking",
7169
}

tests/unit/validation/test_available_action_validator.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
# 🔍 Mandatory Fields
1010
class TestMandatoryFieldsSchemaValidations:
11-
1211
def test_valid_minimal_input(self, valid_available_action):
1312
data = copy.deepcopy(valid_available_action)
1413
data.pop("ActionDescription")
@@ -34,7 +33,6 @@ def test_missing_required_fields(self, valid_available_action):
3433

3534
# 🔍 Optional Fields
3635
class TestOptionalFieldsSchemaValidations:
37-
3836
def test_valid_full_input(self, valid_available_action):
3937
action = AvailableActionValidation(**valid_available_action)
4038
assert action.action_type == "ButtonWithAuthLink"

tests/unit/validation/test_iteration_rules_validator.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def test_invalid_comparator(self, comparator_value, valid_iteration_rule_with_on
124124

125125

126126
class TestOptionalFieldsSchemaValidations:
127-
# AttributeName (Optional)
127+
# AttributeName
128128
@pytest.mark.parametrize("attr_name", ["status", "user_type", None])
129129
def test_valid_attribute_name(self, attr_name, valid_iteration_rule_with_only_mandatory_fields):
130130
data = valid_iteration_rule_with_only_mandatory_fields.copy()
@@ -139,7 +139,7 @@ def test_invalid_attribute_name(self, attr_name, valid_iteration_rule_with_only_
139139
with pytest.raises(ValidationError):
140140
IterationRuleValidation(**data)
141141

142-
# CohortLabel (Optional)
142+
# CohortLabel
143143
@pytest.mark.parametrize("label", ["Cohort_A", "Segment_2025", None, ""])
144144
def test_valid_cohort_label(self, label, valid_iteration_rule_with_only_mandatory_fields):
145145
data = valid_iteration_rule_with_only_mandatory_fields.copy()
@@ -154,7 +154,7 @@ def test_invalid_cohort_label(self, label, valid_iteration_rule_with_only_mandat
154154
with pytest.raises(ValidationError):
155155
IterationRuleValidation(**data)
156156

157-
# AttributeTarget (Optional)
157+
# AttributeTarget
158158
@pytest.mark.parametrize("target", ["target_value", None])
159159
def test_valid_attribute_target(self, target, valid_iteration_rule_with_only_mandatory_fields):
160160
data = valid_iteration_rule_with_only_mandatory_fields.copy()
@@ -169,7 +169,7 @@ def test_invalid_attribute_target(self, target, valid_iteration_rule_with_only_m
169169
with pytest.raises(ValidationError):
170170
IterationRuleValidation(**data)
171171

172-
# RuleStop (Optional boolean with string "Y"/"N")
172+
# RuleStop
173173
@pytest.mark.parametrize("rule_stop_value", [True, False, "Y", "N", "YES", "NO", "YEAH", "ONE"])
174174
def test_valid_rule_stop(self, rule_stop_value, valid_iteration_rule_with_only_mandatory_fields):
175175
data = valid_iteration_rule_with_only_mandatory_fields.copy()
@@ -184,7 +184,7 @@ def test_invalid_rule_stop(self, rule_stop_value, valid_iteration_rule_with_only
184184
with pytest.raises(ValidationError):
185185
IterationRuleValidation(**data)
186186

187-
# CommsRouting (Optional)
187+
# CommsRouting
188188
@pytest.mark.parametrize("routing_value", ["route_A", None])
189189
def test_valid_comms_routing(self, routing_value, valid_iteration_rule_with_only_mandatory_fields):
190190
data = valid_iteration_rule_with_only_mandatory_fields.copy()
@@ -202,7 +202,7 @@ def test_invalid_comms_routing(self, routing_value, valid_iteration_rule_with_on
202202

203203
class TestBUCValidations:
204204
@pytest.mark.parametrize(
205-
"rule_stop_input, expected_bool",
205+
("rule_stop_input", "expected_bool"),
206206
[
207207
(True, True),
208208
(False, False),

tests/unit/validation/test_iteration_validator.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import UTC, datetime
22

33
import pytest
44
from pydantic import ValidationError
@@ -64,7 +64,7 @@ def test_valid_name(self, name_value, valid_campaign_config_with_only_mandatory_
6464
def test_valid_iteration_date(self, date_value, valid_campaign_config_with_only_mandatory_fields):
6565
data = {**valid_campaign_config_with_only_mandatory_fields["Iterations"][0], "IterationDate": date_value}
6666
model = IterationValidation(**data)
67-
expected_date = datetime.strptime(date_value, "%Y%m%d").date()
67+
expected_date = datetime.strptime(str(date_value), "%Y%m%d").replace(tzinfo=UTC).date()
6868
assert model.iteration_date == expected_date, f"Expected {expected_date}, got {model.iteration_date}"
6969

7070
# Type
@@ -143,34 +143,39 @@ def test_approval_maximum(self, approval_maximum, valid_campaign_config_with_onl
143143

144144

145145
class TestIterationCohortsSchemaValidations:
146-
def test_valid_iteration_if_actions_mapper_has_entry_for_the_provided_default_routing_key(self, valid_campaign_config_with_only_mandatory_fields):
146+
def test_valid_iteration_if_actions_mapper_has_entry_for_the_provided_default_routing_key(
147+
self, valid_campaign_config_with_only_mandatory_fields
148+
):
147149
expected_action = {
148150
"ExternalRoutingCode": "BookLocal",
149151
"ActionDescription": "##Getting the vaccine\n"
150-
"You can get an RSV vaccination at your GP surgery.\n"
151-
"Your GP surgery may contact you about getting the RSV vaccine. "
152-
"This may be by letter, text, phone call, email or through the NHS App. "
153-
"You do not need to wait to be contacted before booking your vaccination.",
154-
"ActionType": "InfoText"
152+
"You can get an RSV vaccination at your GP surgery.\n"
153+
"Your GP surgery may contact you about getting the RSV vaccine. "
154+
"This may be by letter, text, phone call, email or through the NHS App. "
155+
"You do not need to wait to be contacted before booking your vaccination.",
156+
"ActionType": "InfoText",
155157
}
156158

157-
data = {**valid_campaign_config_with_only_mandatory_fields["Iterations"][0],
158-
"DefaultCommsRouting": "BOOK_LOCAL", "ActionsMapper": {
159-
"BOOK_LOCAL": expected_action
160-
}}
159+
data = {
160+
**valid_campaign_config_with_only_mandatory_fields["Iterations"][0],
161+
"DefaultCommsRouting": "BOOK_LOCAL",
162+
"ActionsMapper": {"BOOK_LOCAL": expected_action},
163+
}
161164
IterationValidation(**data)
162165

163-
def test_invalid_iteration_if_actions_mapper_has_no_entry_for_the_provided_default_routing_key(self, valid_campaign_config_with_only_mandatory_fields):
164-
data = {**valid_campaign_config_with_only_mandatory_fields["Iterations"][0],
165-
"DefaultCommsRouting": "BOOK_LOCAL", "ActionsMapper": {}} # Missing BOOK_LOCAL in ActionsMapper
166+
def test_invalid_iteration_if_actions_mapper_has_no_entry_for_the_provided_default_routing_key(
167+
self, valid_campaign_config_with_only_mandatory_fields
168+
):
169+
data = {
170+
**valid_campaign_config_with_only_mandatory_fields["Iterations"][0],
171+
"DefaultCommsRouting": "BOOK_LOCAL",
172+
"ActionsMapper": {},
173+
} # Missing BOOK_LOCAL in ActionsMapper
166174

167175
with pytest.raises(ValidationError) as error:
168176
IterationValidation(**data)
169177

170178
errors = error.value.errors()
171-
assert any(
172-
e["loc"][-1] == "actions_mapper" and "BOOK_LOCAL" in str(e["msg"])
173-
for e in errors
174-
), "Expected validation error for missing BOOK_LOCAL entry in ActionsMapper"
175-
176-
179+
assert any(e["loc"][-1] == "actions_mapper" and "BOOK_LOCAL" in str(e["msg"]) for e in errors), (
180+
"Expected validation error for missing BOOK_LOCAL entry in ActionsMapper"
181+
)

0 commit comments

Comments
 (0)