|
13 | 13 | from tests.test_utils import UnitTestContextValidator |
14 | 14 | from validataclass.dataclasses import Default, DefaultFactory, DefaultUnset, validataclass, validataclass_field |
15 | 15 | from validataclass.exceptions import ( |
| 16 | + AdditionalPropertiesError, |
16 | 17 | DataclassInvalidPreValidateSignatureException, |
17 | 18 | DataclassPostValidationError, |
18 | 19 | DataclassValidatorFieldException, |
@@ -58,6 +59,17 @@ class UnitTestNestedDataclass: |
58 | 59 | validataclass_field(DataclassValidator(UnitTestDataclass), default=None) |
59 | 60 |
|
60 | 61 |
|
| 62 | +# Dataclass with prevent_additional_properties=True |
| 63 | + |
| 64 | +@validataclass(prevent_additional_properties=True) |
| 65 | +class UnitTestStrictDataclass: |
| 66 | + """ |
| 67 | + Dataclass that does not allow additional properties in the input dictionary. |
| 68 | + """ |
| 69 | + name: str = StringValidator() |
| 70 | + color: str = StringValidator(), Default('unknown color') |
| 71 | + |
| 72 | + |
61 | 73 | # Dataclass with non-init field and __post_init__() method |
62 | 74 |
|
63 | 75 | @validataclass |
@@ -1131,3 +1143,95 @@ class IncompatibleDataclass: |
1131 | 1143 | DataclassValidator(IncompatibleDataclass) |
1132 | 1144 |
|
1133 | 1145 | assert str(exception_info.value) == 'Default specified for dataclass field "foo" is not of type "Default".' |
| 1146 | + |
| 1147 | + # Tests for prevent_additional_properties option |
| 1148 | + |
| 1149 | + @staticmethod |
| 1150 | + def test_strict_dataclass_valid(): |
| 1151 | + """ Validate a strict dataclass with no extra keys. """ |
| 1152 | + validator = DataclassValidator(UnitTestStrictDataclass) |
| 1153 | + validated_data = validator.validate({ |
| 1154 | + 'name': 'banana', |
| 1155 | + 'color': 'yellow', |
| 1156 | + }) |
| 1157 | + |
| 1158 | + assert type(validated_data) is UnitTestStrictDataclass |
| 1159 | + assert validated_data.name == 'banana' |
| 1160 | + assert validated_data.color == 'yellow' |
| 1161 | + |
| 1162 | + @staticmethod |
| 1163 | + def test_strict_dataclass_valid_with_optional_field_omitted(): |
| 1164 | + """ Validate a strict dataclass with optional field omitted. """ |
| 1165 | + validator = DataclassValidator(UnitTestStrictDataclass) |
| 1166 | + validated_data = validator.validate({ |
| 1167 | + 'name': 'apple', |
| 1168 | + }) |
| 1169 | + |
| 1170 | + assert type(validated_data) is UnitTestStrictDataclass |
| 1171 | + assert validated_data.name == 'apple' |
| 1172 | + assert validated_data.color == 'unknown color' |
| 1173 | + |
| 1174 | + @staticmethod |
| 1175 | + def test_strict_dataclass_with_additional_properties(): |
| 1176 | + """ Test that a strict dataclass raises AdditionalPropertiesError for unknown keys. """ |
| 1177 | + validator = DataclassValidator(UnitTestStrictDataclass) |
| 1178 | + |
| 1179 | + with pytest.raises(AdditionalPropertiesError) as exception_info: |
| 1180 | + validator.validate({ |
| 1181 | + 'name': 'banana', |
| 1182 | + 'unknown_field': 'unknown_value', |
| 1183 | + }) |
| 1184 | + |
| 1185 | + assert exception_info.value.to_dict() == { |
| 1186 | + 'code': 'additional_properties', |
| 1187 | + 'additional_properties': ['unknown_field'], |
| 1188 | + } |
| 1189 | + |
| 1190 | + @staticmethod |
| 1191 | + def test_strict_dataclass_with_multiple_additional_properties(): |
| 1192 | + """ Test that additional properties are sorted in the error. """ |
| 1193 | + validator = DataclassValidator(UnitTestStrictDataclass) |
| 1194 | + |
| 1195 | + with pytest.raises(AdditionalPropertiesError) as exception_info: |
| 1196 | + validator.validate({ |
| 1197 | + 'name': 'banana', |
| 1198 | + 'zebra': 1, |
| 1199 | + 'alpha': 2, |
| 1200 | + 'mango': 3, |
| 1201 | + }) |
| 1202 | + |
| 1203 | + assert exception_info.value.to_dict() == { |
| 1204 | + 'code': 'additional_properties', |
| 1205 | + 'additional_properties': ['alpha', 'mango', 'zebra'], |
| 1206 | + } |
| 1207 | + |
| 1208 | + @staticmethod |
| 1209 | + def test_default_allows_additional_properties(): |
| 1210 | + """ Test that by default (prevent_additional_properties=False), unknown keys are silently ignored. """ |
| 1211 | + validator = DataclassValidator(UnitTestDataclass) |
| 1212 | + validated_data = validator.validate({ |
| 1213 | + 'name': 'banana', |
| 1214 | + 'color': 'yellow', |
| 1215 | + 'amount': 10, |
| 1216 | + 'weight': '1.234', |
| 1217 | + 'unknown_field': 'should be ignored', |
| 1218 | + }) |
| 1219 | + |
| 1220 | + assert type(validated_data) is UnitTestDataclass |
| 1221 | + assert validated_data.name == 'banana' |
| 1222 | + |
| 1223 | + @staticmethod |
| 1224 | + def test_explicit_prevent_additional_properties_false(): |
| 1225 | + """ Test that prevent_additional_properties=False explicitly allows unknown keys. """ |
| 1226 | + |
| 1227 | + @validataclass(prevent_additional_properties=False) |
| 1228 | + class ExplicitAllowDataclass: |
| 1229 | + name: str = StringValidator() |
| 1230 | + |
| 1231 | + validator = DataclassValidator(ExplicitAllowDataclass) |
| 1232 | + validated_data = validator.validate({ |
| 1233 | + 'name': 'banana', |
| 1234 | + 'extra': 'ignored', |
| 1235 | + }) |
| 1236 | + |
| 1237 | + assert validated_data.name == 'banana' |
0 commit comments