|
1 | 1 | import datetime
|
2 | 2 |
|
| 3 | +import pytest |
| 4 | + |
3 | 5 | from scim2_models import Address
|
4 | 6 | from scim2_models import Email
|
5 | 7 | from scim2_models import Im
|
6 | 8 | from scim2_models import PhoneNumber
|
7 | 9 | from scim2_models import Photo
|
8 | 10 | from scim2_models import Reference
|
9 | 11 | from scim2_models import User
|
| 12 | +from scim2_models.attributes import ExtensibleStringEnum |
10 | 13 |
|
11 | 14 |
|
12 | 15 | def test_minimal_user(load_sample):
|
@@ -124,3 +127,171 @@ def test_full_user(load_sample):
|
124 | 127 | obj.meta.location
|
125 | 128 | == "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646"
|
126 | 129 | )
|
| 130 | + |
| 131 | + |
| 132 | +class TestExtensibleStringEnum: |
| 133 | + """Test the base ExtensibleStringEnum behavior.""" |
| 134 | + |
| 135 | + def test_canonical_values(self): |
| 136 | + """Test that canonical enum values work as expected.""" |
| 137 | + |
| 138 | + class TestEnum(ExtensibleStringEnum): |
| 139 | + foo = "foo" |
| 140 | + bar = "bar" |
| 141 | + |
| 142 | + assert TestEnum.foo == "foo" |
| 143 | + assert TestEnum.bar == "bar" |
| 144 | + assert str(TestEnum.foo) == "foo" |
| 145 | + |
| 146 | + def test_arbitrary_values(self): |
| 147 | + """Test that arbitrary string values are accepted.""" |
| 148 | + |
| 149 | + class TestEnum(ExtensibleStringEnum): |
| 150 | + foo = "foo" |
| 151 | + bar = "bar" |
| 152 | + |
| 153 | + # Create instances with arbitrary values |
| 154 | + custom = TestEnum("custom_value") |
| 155 | + another = TestEnum("another_value") |
| 156 | + |
| 157 | + assert str(custom) == "custom_value" |
| 158 | + assert str(another) == "another_value" |
| 159 | + assert custom == "custom_value" |
| 160 | + assert another == "another_value" |
| 161 | + |
| 162 | + def test_non_string_values_rejected(self): |
| 163 | + """Test that non-string values are rejected.""" |
| 164 | + |
| 165 | + class TestEnum(ExtensibleStringEnum): |
| 166 | + foo = "foo" |
| 167 | + |
| 168 | + with pytest.raises(ValueError, match="is not a valid string value"): |
| 169 | + TestEnum(123) |
| 170 | + |
| 171 | + with pytest.raises(ValueError, match="is not a valid string value"): |
| 172 | + TestEnum(None) |
| 173 | + |
| 174 | + |
| 175 | +class TestComplexAttributeExtensibleTypes: |
| 176 | + """Test that complex attribute Type classes accept arbitrary values per RFC 7643.""" |
| 177 | + |
| 178 | + def test_email_canonical_and_arbitrary_types(self): |
| 179 | + """Test Email with both canonical and arbitrary type values.""" |
| 180 | + # Canonical types |
| 181 | + email = Email( value="[email protected]", type=Email. Type. work) |
| 182 | + assert email.type == Email.Type.work |
| 183 | + assert str(email.type) == "work" |
| 184 | + |
| 185 | + email = Email( value="[email protected]", type=Email. Type. home) |
| 186 | + assert email.type == Email.Type.home |
| 187 | + assert str(email.type) == "home" |
| 188 | + |
| 189 | + # Arbitrary types per RFC 7643 |
| 190 | + email = Email( value="[email protected]", type="custom_type") |
| 191 | + assert str(email.type) == "custom_type" |
| 192 | + |
| 193 | + email = Email( value="[email protected]", type="business") |
| 194 | + assert str(email.type) == "business" |
| 195 | + |
| 196 | + def test_phone_canonical_and_arbitrary_types(self): |
| 197 | + """Test PhoneNumber with both canonical and arbitrary type values.""" |
| 198 | + # Canonical types |
| 199 | + phone = PhoneNumber(value="+1234567890", type=PhoneNumber.Type.mobile) |
| 200 | + assert phone.type == PhoneNumber.Type.mobile |
| 201 | + assert str(phone.type) == "mobile" |
| 202 | + |
| 203 | + # Arbitrary types per RFC 7643 |
| 204 | + phone = PhoneNumber(value="+1234567890", type="voip") |
| 205 | + assert str(phone.type) == "voip" |
| 206 | + |
| 207 | + phone = PhoneNumber(value="+1234567890", type="satellite") |
| 208 | + assert str(phone.type) == "satellite" |
| 209 | + |
| 210 | + def test_address_canonical_and_arbitrary_types(self): |
| 211 | + """Test Address with both canonical and arbitrary type values.""" |
| 212 | + # Canonical types |
| 213 | + addr = Address(formatted="123 Main St", type=Address.Type.home) |
| 214 | + assert addr.type == Address.Type.home |
| 215 | + assert str(addr.type) == "home" |
| 216 | + |
| 217 | + # Arbitrary types per RFC 7643 |
| 218 | + addr = Address(formatted="123 Main St", type="vacation") |
| 219 | + assert str(addr.type) == "vacation" |
| 220 | + |
| 221 | + addr = Address(formatted="123 Main St", type="shipping") |
| 222 | + assert str(addr.type) == "shipping" |
| 223 | + |
| 224 | + def test_im_canonical_and_arbitrary_types(self): |
| 225 | + """Test Im with both canonical and arbitrary type values.""" |
| 226 | + # Canonical types |
| 227 | + im = Im( value="[email protected]", type=Im. Type. skype) |
| 228 | + assert im.type == Im.Type.skype |
| 229 | + assert str(im.type) == "skype" |
| 230 | + |
| 231 | + # Arbitrary types per RFC 7643 |
| 232 | + im = Im( value="[email protected]", type="discord") |
| 233 | + assert str(im.type) == "discord" |
| 234 | + |
| 235 | + im = Im( value="[email protected]", type="teams") |
| 236 | + assert str(im.type) == "teams" |
| 237 | + |
| 238 | + def test_photo_canonical_and_arbitrary_types(self): |
| 239 | + """Test Photo with both canonical and arbitrary type values.""" |
| 240 | + # Canonical types |
| 241 | + photo = Photo(value="http://example.com/photo.jpg", type=Photo.Type.photo) |
| 242 | + assert photo.type == Photo.Type.photo |
| 243 | + assert str(photo.type) == "photo" |
| 244 | + |
| 245 | + # Arbitrary types per RFC 7643 |
| 246 | + photo = Photo(value="http://example.com/avatar.jpg", type="avatar") |
| 247 | + assert str(photo.type) == "avatar" |
| 248 | + |
| 249 | + photo = Photo(value="http://example.com/profile.jpg", type="profile") |
| 250 | + assert str(photo.type) == "profile" |
| 251 | + |
| 252 | + def test_type_serialization_compatibility(self): |
| 253 | + """Test that serialization works with both canonical and arbitrary values.""" |
| 254 | + # Canonical type |
| 255 | + email_canonical = Email( value="[email protected]", type=Email. Type. work) |
| 256 | + data = email_canonical.model_dump() |
| 257 | + assert data["type"] == "work" |
| 258 | + |
| 259 | + # Arbitrary type |
| 260 | + email_custom = Email( value="[email protected]", type="custom_business") |
| 261 | + data = email_custom.model_dump() |
| 262 | + assert data["type"] == "custom_business" |
| 263 | + |
| 264 | + def test_round_trip_serialization(self): |
| 265 | + """Test that arbitrary values can be serialized and deserialized correctly.""" |
| 266 | + # Test with arbitrary type |
| 267 | + original = Email( value="[email protected]", type="custom_type") |
| 268 | + data = original.model_dump() |
| 269 | + restored = Email.model_validate(data) |
| 270 | + |
| 271 | + assert str(restored.type) == "custom_type" |
| 272 | + assert restored.type == "custom_type" |
| 273 | + assert restored. value == "[email protected]" |
| 274 | + |
| 275 | + def test_rfc_7643_compliance(self): |
| 276 | + """Test compliance with RFC 7643 extensibility requirements. |
| 277 | +
|
| 278 | + RFC 7643 states that service providers MAY allow additional type values |
| 279 | + beyond the canonical ones like "work", "home", and "other". |
| 280 | + """ |
| 281 | + # Should accept canonical values |
| 282 | + email = Email( value="[email protected]", type="work") |
| 283 | + assert str(email.type) == "work" |
| 284 | + |
| 285 | + phone = PhoneNumber(value="+1234567890", type="mobile") |
| 286 | + assert str(phone.type) == "mobile" |
| 287 | + |
| 288 | + # Should accept additional values as specified in RFC |
| 289 | + email = Email( value="[email protected]", type="emergency") |
| 290 | + assert str(email.type) == "emergency" |
| 291 | + |
| 292 | + phone = PhoneNumber(value="+1234567890", type="voip") |
| 293 | + assert str(phone.type) == "voip" |
| 294 | + |
| 295 | + # Test GitHub issue #34 specific case |
| 296 | + email_company = Email( value="[email protected]", type="company") |
| 297 | + assert str(email_company.type) == "company" |
0 commit comments