@@ -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))
0 commit comments