Skip to content

Commit 9e18d90

Browse files
committed
setup schema and validation for 15 fields
1 parent b08c49f commit 9e18d90

File tree

3 files changed

+220
-35
lines changed

3 files changed

+220
-35
lines changed

lambdas/shared/tests/test_common/validator/test_expression_checker.py

Lines changed: 113 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -103,39 +103,120 @@ def test_datetime_valid_and_invalid(self):
103103
checker.validate_expression("DATETIME", "", "DATE_AND_TIME", "2026-01-01T10:00:00Z"), ErrorReport
104104
)
105105

106+
# STRING with SITE_CODE
107+
def test_site_code_string_valid_and_invalid(self):
108+
checker = self.make_checker()
109+
field_path = "performer|#:Organization|actor|identifier|value"
110+
# Valid: non-empty, no spaces
111+
self.assertIsNone(checker.validate_expression("STRING", "", field_path, "RJ1"))
112+
# Invalid: empty
113+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, ""), ErrorReport)
114+
# Invalid: contains spaces
115+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, 1234), ErrorReport)
116+
117+
# STRING with SITE_CODE_TYPE_URI rule
118+
def test_site_code_type_uri_string_valid_and_invalid(self):
119+
checker = self.make_checker()
120+
field_path = "performer|#:Organization|actor|identifier|system"
121+
valid_uri = "https://fhir.nhs.uk/Id/ods-organization-code"
122+
# Valid: non-empty, no spaces
123+
self.assertIsNone(
124+
checker.validate_expression("STRING", "", field_path, valid_uri),
125+
)
126+
# Invalid: empty
127+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, ""), ErrorReport)
128+
# Invalid: contains spaces
129+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, 123), ErrorReport)
130+
131+
# BOOLEAN
132+
133+
# STRING with UNIQUE_ID rule (empty rule -> generic non-empty string)
134+
def test_unique_id_string_valid_and_invalid(self):
135+
checker = self.make_checker()
136+
field_path = "identifier|0|value"
137+
# Valid: non-empty string
138+
self.assertIsNone(checker.validate_expression("STRING", "", field_path, "ABC-123-XYZ"))
139+
# Invalid: empty string
140+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, ""), ErrorReport)
141+
# Invalid: non-string value
142+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, 987654), ErrorReport)
143+
144+
# STRING with UNIQUE_ID_URI rule (empty rule -> generic non-empty string)
145+
def test_unique_id_uri_string_valid_and_invalid(self):
146+
checker = self.make_checker()
147+
field_path = "identifier|0|system"
148+
valid_system = "https://example.org/unique-id-system"
149+
# Valid: non-empty string
150+
self.assertIsNone(checker.validate_expression("STRING", "", field_path, valid_system))
151+
# Invalid: empty string
152+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, ""), ErrorReport)
153+
# Invalid: non-string value
154+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, 42), ErrorReport)
155+
156+
# STRING with GENDER rule on real field
157+
def test_gender_string_rule_valid_and_invalid(self):
158+
checker = self.make_checker()
159+
field_path = "contained|#:Patient|gender"
160+
# Valid genders per schema constants (male, female, other, unknown)
161+
self.assertIsNone(checker.validate_expression("STRING", "GENDER", field_path, "male"))
162+
self.assertIsNone(checker.validate_expression("STRING", "GENDER", field_path, "female"))
163+
# Invalid values should error
164+
self.assertIsInstance(checker.validate_expression("STRING", "GENDER", field_path, "M"), ErrorReport)
165+
166+
# LIST with PERFORMING_PROFESSIONAL_FORENAME (empty rule -> non-empty list)
167+
def test_practitioner_forename_list_valid_and_invalid(self):
168+
checker = self.make_checker()
169+
field_path = "contained|#:Practitioner|name|0|given|0"
170+
# Valid: non-empty list
171+
self.assertIsNone(checker.validate_expression("LIST", "", field_path, ["Alice"]))
172+
# Invalid: empty list
173+
self.assertIsInstance(checker.validate_expression("LIST", "", field_path, []), ErrorReport)
174+
# Invalid: non-list value
175+
self.assertIsInstance(checker.validate_expression("LIST", "", field_path, "Alice"), ErrorReport)
176+
177+
# STRING with PERFORMING_PROFESSIONAL_SURNAME (empty rule -> non-empty string)
178+
def test_practitioner_surname_string_valid_and_invalid(self):
179+
checker = self.make_checker()
180+
field_path = "contained|#:Practitioner|name|0|family"
181+
# Valid: non-empty string
182+
self.assertIsNone(checker.validate_expression("STRING", "", field_path, "Smith"))
183+
# Invalid: empty string
184+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, ""), ErrorReport)
185+
# Invalid: non-string
186+
self.assertIsInstance(checker.validate_expression("STRING", "", field_path, 123), ErrorReport)
187+
188+
# DATETIME with RECORDED_DATE (schema rule says 'false-strict-timezone' but we use default non-strict here)
189+
def test_recorded_date_datetime_valid_and_invalid(self):
190+
checker = self.make_checker()
191+
field_path = "recorded"
192+
# Valid: timezone offset other than +00:00 or +01:00 should be allowed when non-strict
193+
self.assertIsNone(checker.validate_expression("DATETIME", "", field_path, "2025-01-01T10:00:00+02:00"))
194+
# Valid: full date only also allowed per formats
195+
self.assertIsNone(checker.validate_expression("DATETIME", "", field_path, "2025-01-01"))
196+
# Invalid: Zulu timezone not in accepted formats
197+
self.assertIsInstance(
198+
checker.validate_expression("DATETIME", "", field_path, "2026-01-01T10:00:00Z"), ErrorReport
199+
)
200+
201+
# STRING with no rule for PERSON_POSTCODE on real field
202+
def test_postcode_string_rule_valid_and_invalid(self):
203+
checker = self.make_checker()
204+
field_path = "contained|#:Patient|address|#:postalCode|postalCode"
205+
# With empty rule, generic string constraints apply: non-empty and no spaces
206+
self.assertIsNone(checker.validate_expression("STRING", "", field_path, "SW1A 1AA"))
207+
# Real-world postcode with a space should fail as spaces are not allowed without a rule override
208+
field_path = "POST_CODE"
209+
self.assertIsInstance(
210+
checker.validate_expression("STRING", "", field_path, 123),
211+
ErrorReport,
212+
)
213+
# Empty should also fail
214+
self.assertIsInstance(
215+
checker.validate_expression("STRING", "", field_path, ""),
216+
ErrorReport,
217+
)
218+
106219

107-
# # BOOLEAN
108-
109-
# # STRING with GENDER rule on real field
110-
# def test_gender_string_rule_valid_and_invalid(self):
111-
# checker = self.make_checker()
112-
# field_path = "contained|#:Patient|gender"
113-
# # Valid genders per schema constants (male, female, other, unknown)
114-
# self.assertIsNone(checker.validate_expression("STRING", "GENDER", field_path, "male"))
115-
# self.assertIsNone(checker.validate_expression("STRING", "GENDER", field_path, "female"))
116-
# # Invalid values should error
117-
# self.assertIsInstance(
118-
# checker.validate_expression("STRING", "GENDER", field_path, "M"),
119-
# ErrorReport,
120-
# )
121-
122-
# # STRING with no rule for PERSON_POSTCODE on real field
123-
# def test_postcode_string_rule_valid_and_invalid(self):
124-
# checker = self.make_checker()
125-
# field_path = "contained|#:Patient|address|#:postalCode|postalCode"
126-
# # With empty rule, generic string constraints apply: non-empty and no spaces
127-
# self.assertIsNone(checker.validate_expression("STRING", "", field_path, "SW1A1AA"))
128-
# # Real-world postcode with a space should fail as spaces are not allowed without a rule override
129-
# field_path = "POST_CODE"
130-
# self.assertIsInstance(
131-
# checker.validate_expression("STRING", "", field_path, "AB12 3CD"),
132-
# ErrorReport,
133-
# )
134-
# # Empty should also fail
135-
# self.assertIsInstance(
136-
# checker.validate_expression("STRING", "", field_path, ""),
137-
# ErrorReport,
138-
# )
139220
# def test_boolean_valid_and_invalid(self):
140221
# checker = self.make_checker()
141222
# self.assertIsNone(checker.validate_expression("BOOLEAN", "", "bool_field", True, 1))

lambdas/shared/tests/test_common/validator/test_schemas/test_schema.json

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@
5656
},
5757
"errorGroup": "consistency"
5858
},
59+
{
60+
"expressionId": "01K8RW2ATXRM572BFG19S8TFJ2",
61+
"fieldNameFHIR": "contained|#:Patient|gender",
62+
"fieldNameFlat": "PERSON_GENDER_CODE",
63+
"fieldNumber": 5,
64+
"errorLevel": 1,
65+
"expression": {
66+
"expressionName": "Gender Valid Check",
67+
"expressionType": "STRING",
68+
"expressionRule": "GENDER"
69+
},
70+
"errorGroup": "consistency"
71+
},
5972
{
6073
"expressionId": "01K8RW2MYFNE6YZJ99RC8B3XES",
6174
"fieldNameFHIR": "contained|#:Patient|address|#:postalCode|postalCode",
@@ -81,6 +94,97 @@
8194
"expressionRule": "DATETIME"
8295
},
8396
"errorGroup": "consistency"
97+
},
98+
{
99+
"expressionId": "01K5EGR0C8M1MVNKTQCE6MSG68",
100+
"fieldNameFHIR": "performer|#:Organization|actor|identifier|value",
101+
"fieldNameFlat": "SITE_CODE",
102+
"fieldNumber": 8,
103+
"errorLevel": 0,
104+
"expression": {
105+
"expressionName": "Organisation String Check",
106+
"expressionType": "STRING",
107+
"expressionRule": ""
108+
},
109+
"errorGroup": "consistency"
110+
},
111+
{
112+
"expressionId": "01K8S0X5AJ9048PAFEN3XVZ7YC",
113+
"fieldNameFHIR": "performer|#:Organization|actor|identifier|system",
114+
"fieldNameFlat": "SITE_CODE_TYPE_URI",
115+
"fieldNumber": 9,
116+
"errorLevel": 1,
117+
"expression": {
118+
"expressionName": "Organisation Code Type URI Check",
119+
"expressionType": "STRING",
120+
"expressionRule": ""
121+
},
122+
"errorGroup": "consistency"
123+
},
124+
{
125+
"expressionId": "01K8S0XF2Y2WP22017N9KE6VJA",
126+
"fieldNameFHIR": "identifier|0|value",
127+
"fieldNameFlat": "UNIQUE_ID",
128+
"fieldNumber": 10,
129+
"errorLevel": 0,
130+
"expression": {
131+
"expressionName": "Unique ID Not Empty Check",
132+
"expressionType": "STRING",
133+
"expressionRule": ""
134+
},
135+
"errorGroup": "validity"
136+
},
137+
{
138+
"expressionId": "01K8S0XQYHDKMCA1P1GK4W5JHP",
139+
"fieldNameFHIR": "identifier|0|system",
140+
"fieldNameFlat": "UNIQUE_ID_URI",
141+
"fieldNumber": 11,
142+
"errorLevel": 0,
143+
"expression": {
144+
"expressionName": "Unique ID URI Not Empty Check",
145+
"expressionType": "STRING",
146+
"expressionRule": ""
147+
},
148+
"errorGroup": "validity"
149+
},
150+
{
151+
"expressionId": "01K5EGR0C8SDQBTNCEP8TJNCCW",
152+
"fieldNameFHIR": "contained|#:Practitioner|name|0|given|0",
153+
"fieldNameFlat": "PERFORMING_PROFESSIONAL_FORENAME",
154+
"fieldNumber": 13,
155+
"errorLevel": 1,
156+
"expression": {
157+
"expressionName": "Practitioner Forename Not Empty Check",
158+
"expressionType": "LIST",
159+
"expressionRule": ""
160+
},
161+
"errorGroup": "completeness"
162+
},
163+
{
164+
"expressionId": "01K5EGR0C8T3Z6X6h3W7D1F4VY",
165+
"fieldNameFHIR": "contained|#:Practitioner|name|0|family",
166+
"fieldNameFlat": "PERFORMING_PROFESSIONAL_SURNAME",
167+
"fieldNumber": 14,
168+
"errorLevel": 1,
169+
"expression": {
170+
"expressionName": "Practitioner Surname Not Empty Check",
171+
"expressionType": "STRING",
172+
"expressionRule": ""
173+
},
174+
"errorGroup": "completeness"
175+
},
176+
{
177+
"expressionId": "01K8S0Y8TX8HTX6YGW61RDCATK",
178+
"fieldNameFHIR": "recorded",
179+
"fieldNameFlat": "RECORDED_DATE",
180+
"fieldNumber": 15,
181+
"errorLevel": 1,
182+
"expression": {
183+
"expressionName": "Recorded Date Convert",
184+
"expressionType": "DATETIME",
185+
"expressionRule": ""
186+
},
187+
"errorGroup": "consistency"
84188
}
85189
]
86190
}

lambdas/shared/tests/test_common/validator/testing_utils/constants.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
"PERSON_FORENAME": ["JOHN"],
1616
"PERSON_SURNAME": "DOE",
1717
"PERSON_DOB": "1980-01-01",
18-
"PERSON_GENDER_CODE": "Male",
18+
"PERSON_GENDER_CODE": "male",
1919
"PERSON_POSTCODE": "AB12 3CD",
2020
"DATE_AND_TIME": "2025-03-06T05:10:25+00:00",
2121
"SITE_CODE": "RJ1",
2222
"SITE_CODE_TYPE_URI": "https://fhir.nhs.uk/Id/ods-organization-code",
2323
"UNIQUE_ID": "ACME-vacc123456",
2424
"UNIQUE_ID_URI": "https://supplierABC/identifiers/vacc",
2525
"ACTION_FLAG": "UPDATE",
26-
"PERFORMING_PROFESSIONAL_FORENAME": "ALICE",
26+
"PERFORMING_PROFESSIONAL_FORENAME": ["ALICE"],
2727
"PERFORMING_PROFESSIONAL_SURNAME": "SMITH",
28-
"RECORDED_DATE": "20250306",
28+
"RECORDED_DATE": "2025-03-06",
2929
"PRIMARY_SOURCE": "true",
3030
"VACCINATION_PROCEDURE_CODE": "PROC123",
3131
"VACCINATION_PROCEDURE_TERM": "Procedure Term",

0 commit comments

Comments
 (0)