-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathtest_model_attributes.py
More file actions
305 lines (252 loc) · 9.58 KB
/
test_model_attributes.py
File metadata and controls
305 lines (252 loc) · 9.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
import uuid
from typing import Annotated
from typing import Optional
from scim2_models.annotations import Required
from scim2_models.annotations import Returned
from scim2_models.attributes import ComplexAttribute
from scim2_models.base import BaseModel
from scim2_models.context import Context
from scim2_models.rfc7643.enterprise_user import EnterpriseUser
from scim2_models.rfc7643.resource import Extension
from scim2_models.rfc7643.resource import Meta
from scim2_models.rfc7643.resource import Resource
from scim2_models.rfc7643.user import User
from scim2_models.rfc7644.error import Error
from scim2_models.urn import validate_attribute_urn
class Sub(ComplexAttribute):
dummy: str
class Sup(Resource):
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Sup"]
dummy: str
sub: Sub
subs: list[Sub]
def test_guess_root_type():
assert Sup.get_field_root_type("dummy") is str
assert Sup.get_field_root_type("sub") == Sub
assert Sup.get_field_root_type("subs") == Sub
class ReturnedModel(BaseModel):
always: Annotated[Optional[str], Returned.always] = None
never: Annotated[Optional[str], Returned.never] = None
default: Annotated[Optional[str], Returned.default] = None
request: Annotated[Optional[str], Returned.request] = None
class Baz(ComplexAttribute):
baz_snake_case: str
class Foo(Resource):
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Foo"]
sub: Annotated[ReturnedModel, Returned.default]
bar: str
snake_case: str
baz: Optional[Baz] = None
class Bar(Resource):
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Bar"]
sub: Annotated[ReturnedModel, Returned.default]
bar: str
snake_case: str
baz: Optional[Baz] = None
class MyExtension(Extension):
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:MyExtension"]
baz: str
def test_validate_attribute_urn():
"""Test the method that validates and normalizes attribute URNs."""
assert validate_attribute_urn("bar", Foo) == "urn:example:2.0:Foo:bar"
assert (
validate_attribute_urn("urn:example:2.0:Foo:bar", Foo)
== "urn:example:2.0:Foo:bar"
)
assert validate_attribute_urn("sub", Foo) == "urn:example:2.0:Foo:sub"
assert (
validate_attribute_urn("urn:example:2.0:Foo:sub", Foo)
== "urn:example:2.0:Foo:sub"
)
assert validate_attribute_urn("sub.always", Foo) == "urn:example:2.0:Foo:sub.always"
assert (
validate_attribute_urn("urn:example:2.0:Foo:sub.always", Foo)
== "urn:example:2.0:Foo:sub.always"
)
assert validate_attribute_urn("snakeCase", Foo) == "urn:example:2.0:Foo:snakeCase"
assert (
validate_attribute_urn("urn:example:2.0:Foo:snakeCase", Foo)
== "urn:example:2.0:Foo:snakeCase"
)
assert (
validate_attribute_urn("urn:example:2.0:MyExtension:baz", Foo[MyExtension])
== "urn:example:2.0:MyExtension:baz"
)
assert validate_attribute_urn("urn:InvalidResource:bar", Foo) is None
assert validate_attribute_urn("urn:example:2.0:Foo:invalid", Foo) is None
assert validate_attribute_urn("bar.invalid", Foo) is None
assert (
validate_attribute_urn("urn:example:2.0:MyExtension:invalid", Foo[MyExtension])
is None
)
def test_payload_attribute_case_sensitivity():
"""RFC7643 §2.1 indicates that attribute names should be case insensitive.
Attribute names are case insensitive and are often "camel-cased"
(e.g., "camelCase").
Reported by issue #39.
"""
payload = {
"UserName": "UserName123",
"Active": True,
"displayname": "BobIsAmazing",
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": uuid.uuid4().hex,
"name": {
"formatted": "Ryan Leenay",
"familyName": "Leenay",
"givenName": "Ryan",
},
"emails": [
{"Primary": True, "type": "work", "value": "testing@bob.com"},
{"Primary": False, "type": "home", "value": "testinghome@bob.com"},
],
}
user = User.model_validate(payload)
assert user.user_name == "UserName123"
assert user.display_name == "BobIsAmazing"
def test_attribute_inclusion_case_sensitivity():
"""Test that attribute inclusion supports any attribute case.
Reported by #45.
"""
user = User.model_validate({"userName": "foobar"})
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE, attributes=["userName"]
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE, attributes=["username"]
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE, attributes=["USERNAME"]
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=["urn:ietf:params:scim:schemas:core:2.0:User:userName"],
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=["urn:ietf:params:scim:schemas:core:2.0:User:username"],
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
assert user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=["URN:IETF:PARAMS:SCIM:SCHEMAS:CORE:2.0:USER:USERNAME"],
) == {
"userName": "foobar",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
}
def test_attribute_inclusion_schema_extensions():
"""Verifies that attributes from schema extensions work."""
user = User[EnterpriseUser].model_validate(
{
"userName": "foobar",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "12345"
},
}
)
expected = {
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
],
"userName": "foobar",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "12345",
},
}
assert (
user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=[
"urn:ietf:params:scim:schemas:core:2.0:User:userName",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber",
],
)
== expected
)
assert (
user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=[
"urn:ietf:params:scim:schemas:core:2.0:User:userName",
"URN:IETF:PARAMS:SCIM:SCHEMAS:EXTENSION:ENTERPRISE:2.0:USER:EMPLOYEENUMBER",
],
)
== expected
)
def test_dump_after_assignment():
"""Test that attribute assignment does not break model dump."""
user = User(id="1", user_name="ABC")
user.meta = Meta(
resource_type="User",
location="/v2/Users/foo",
)
assert user.model_dump(scim_ctx=Context.RESOURCE_CREATION_RESPONSE) == {
"id": "1",
"meta": {
"location": "/v2/Users/foo",
"resourceType": "User",
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
],
"userName": "ABC",
}
def test_binary_attributes():
decoded = b"This is a very long line with a lot of characters, enough to create newlines when encoded."
encoded = "VGhpcyBpcyBhIHZlcnkgbG9uZyBsaW5lIHdpdGggYSBsb3Qgb2YgY2hhcmFjdGVycywgZW5vdWdoIHRvIGNyZWF0ZSBuZXdsaW5lcyB3aGVuIGVuY29kZWQu"
user = User.model_validate(
{"userName": "foobar", "x509Certificates": [{"value": encoded}]}
)
assert user.x509_certificates[0].value == decoded
assert user.model_dump()["x509Certificates"][0]["value"] == encoded
encoded_without_newlines = "VGhpcyBpcyBhIHZlcnkgbG9uZyBsaW5lIHdpdGggYSBsb3Qgb2YgY2hhcmFjdGVycywgZW5vdWdoIHRvIGNyZWF0ZSBuZXdsaW5lcyB3aGVuIGVuY29kZWQu"
user = User.model_validate(
{
"userName": "foobar",
"x509Certificates": [{"value": encoded_without_newlines}],
}
)
assert user.x509_certificates[0].value == decoded
assert user.model_dump()["x509Certificates"][0]["value"] == encoded
encoded_with_padding = "VGhpcyBpcyBhIHZlcnkgbG9uZyBsaW5lIHdpdGggYSBsb3Qgb2YgY2hhcmFjdGVycywgZW5vdWdoIHRvIGNyZWF0ZSBuZXdsaW5lcyB3aGVuIGVuY29kZWQu=================="
user = User.model_validate(
{"userName": "foobar", "x509Certificates": [{"value": encoded_with_padding}]}
)
assert user.x509_certificates[0].value == decoded
assert user.model_dump()["x509Certificates"][0]["value"] == encoded
def test_scim_object_model_dump_coverage():
"""Test ScimObject.model_dump for coverage of mode setting."""
# Test with scim_ctx=None (no mode setting)
error = Error(status="400", detail="Test error")
result = error.model_dump(scim_ctx=None)
assert isinstance(result, dict)
# Test model_dump_json coverage
json_result = error.model_dump_json(scim_ctx=None)
assert isinstance(json_result, str)