Skip to content

Commit a9782f8

Browse files
committed
fix: full test coverage
1 parent 50539ca commit a9782f8

File tree

4 files changed

+242
-26
lines changed

4 files changed

+242
-26
lines changed

tests/test_patch_add.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,70 @@ def patch_handler(request):
341341
results = check_add_attribute(testing_context, User[EnterpriseUser])
342342
unexpected = [r for r in results if r.status != Status.SUCCESS]
343343
assert not unexpected
344+
345+
346+
def test_patch_add_modify_result_incorrect_value(testing_context):
347+
"""Test PATCH add when modify result returns incorrect value."""
348+
from unittest.mock import Mock
349+
350+
mock_client = Mock()
351+
mock_user = User(id="123", user_name="test@example.com")
352+
mock_client.create.return_value = mock_user
353+
354+
# Mock modify to return a different value than expected
355+
mock_modified_user = User(
356+
id="123", user_name="test@example.com", display_name="wrong_value"
357+
)
358+
mock_client.modify.return_value = mock_modified_user
359+
mock_client.query.return_value = mock_modified_user
360+
361+
testing_context.client = mock_client
362+
363+
results = check_add_attribute(testing_context, User)
364+
365+
error_results = [
366+
r for r in results if r.status == Status.ERROR and "incorrect value" in r.reason
367+
]
368+
assert len(error_results) > 0
369+
370+
371+
def test_patch_add_query_failure_after_patch(httpserver, testing_context):
372+
"""Test PATCH add when query fails after successful patch."""
373+
httpserver.expect_request(uri="/Users", method="POST").respond_with_json(
374+
{
375+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
376+
"id": "123",
377+
"userName": "test@example.com",
378+
},
379+
status=201,
380+
)
381+
382+
httpserver.expect_request(uri="/Users/123", method="PATCH").respond_with_json(
383+
{
384+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
385+
"id": "123",
386+
"userName": "test@example.com",
387+
"displayName": "test_display",
388+
},
389+
status=200,
390+
)
391+
392+
httpserver.expect_request(uri="/Users/123", method="GET").respond_with_json(
393+
{
394+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
395+
"id": "123",
396+
"userName": "test@example.com",
397+
# displayName missing - attribute not actually added
398+
},
399+
status=200,
400+
)
401+
402+
results = check_add_attribute(testing_context, User)
403+
404+
error_results = [
405+
r
406+
for r in results
407+
if r.status == Status.ERROR
408+
and "was not added or has incorrect value" in r.reason
409+
]
410+
assert len(error_results) > 0

tests/test_patch_remove.py

Lines changed: 120 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ def patch_handler(request):
5454
}
5555

5656
json_field = field_mapping.get(path, path)
57-
if json_field in resource_state:
58-
del resource_state[json_field]
57+
del resource_state[json_field]
5958

6059
return Response(
6160
json.dumps(resource_state),
@@ -190,9 +189,7 @@ def patch_handler(request):
190189
operation = patch_data["Operations"][0]
191190
path = operation["path"]
192191

193-
# Remove the field from resource state
194-
if path in resource_state:
195-
del resource_state[path]
192+
del resource_state[path]
196193

197194
return Response(
198195
json.dumps(resource_state),
@@ -297,27 +294,16 @@ def update_patch_handler(request):
297294
operation = patch_data["Operations"][0]
298295
path = operation["path"]
299296

300-
if path.startswith(
301-
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:"
302-
):
303-
field_name = path.split(":")[-1]
304-
extension_key = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
305-
if (
306-
extension_key in resource_state
307-
and field_name in resource_state[extension_key]
308-
):
309-
del resource_state[extension_key][field_name]
310-
else:
311-
field_mapping = {
312-
"display_name": "displayName",
313-
"nick_name": "nickName",
314-
"profile_url": "profileUrl",
315-
"user_type": "userType",
316-
"preferred_language": "preferredLanguage",
317-
}
318-
json_field = field_mapping.get(path, path)
319-
if json_field in resource_state:
320-
del resource_state[json_field]
297+
field_mapping = {
298+
"display_name": "displayName",
299+
"nick_name": "nickName",
300+
"profile_url": "profileUrl",
301+
"user_type": "userType",
302+
"preferred_language": "preferredLanguage",
303+
}
304+
json_field = field_mapping.get(path, path)
305+
if json_field in resource_state:
306+
del resource_state[json_field]
321307

322308
return Response(
323309
json.dumps(resource_state),
@@ -343,3 +329,111 @@ def update_patch_handler(request):
343329
assert not unexpected, (
344330
f"All results should be SUCCESS, got: {[r.reason for r in results if r.status != Status.SUCCESS]}"
345331
)
332+
333+
334+
def test_patch_remove_query_failure_after_patch(httpserver, testing_context):
335+
"""Test PATCH remove when query fails after successful patch."""
336+
httpserver.expect_request(uri="/Users", method="POST").respond_with_json(
337+
{
338+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
339+
"id": "123",
340+
"userName": "test@example.com",
341+
"displayName": "test_display",
342+
},
343+
status=201,
344+
)
345+
346+
httpserver.expect_request(uri="/Users/123", method="PATCH").respond_with_json(
347+
{
348+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
349+
"id": "123",
350+
"userName": "test@example.com",
351+
},
352+
status=200,
353+
)
354+
355+
httpserver.expect_request(uri="/Users/123", method="GET").respond_with_json(
356+
{
357+
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
358+
"detail": "Resource not found after remove",
359+
"status": "404",
360+
},
361+
status=404,
362+
)
363+
364+
httpserver.expect_request(uri="/Users/123", method="DELETE").respond_with_data(
365+
"", status=204
366+
)
367+
368+
results = check_remove_attribute(testing_context, User)
369+
370+
error_results = [
371+
r
372+
for r in results
373+
if r.status == Status.ERROR
374+
and "Failed to query resource after remove" in r.reason
375+
]
376+
assert len(error_results) > 0
377+
378+
379+
def test_patch_remove_attribute_not_removed(testing_context):
380+
"""Test PATCH remove when modify result shows attribute was not removed."""
381+
from unittest.mock import Mock
382+
383+
mock_client = Mock()
384+
mock_user = User(id="123", user_name="test@example.com", display_name="persistent")
385+
mock_client.create.return_value = mock_user
386+
387+
# Mock modify to return a resource where the attribute is still present
388+
mock_modified_user = User(
389+
id="123",
390+
user_name="test@example.com",
391+
display_name="persistent", # Still there after remove attempt
392+
)
393+
mock_client.modify.return_value = mock_modified_user
394+
mock_client.query.return_value = mock_modified_user
395+
396+
testing_context.client = mock_client
397+
398+
results = check_remove_attribute(testing_context, User)
399+
400+
error_results = [
401+
r
402+
for r in results
403+
if r.status == Status.ERROR and "did not remove attribute" in r.reason
404+
]
405+
assert len(error_results) > 0
406+
407+
408+
def test_patch_remove_writeonly_attribute(testing_context):
409+
"""Test PATCH remove when modify result has writeOnly attribute value."""
410+
from unittest.mock import Mock
411+
412+
mock_client = Mock()
413+
mock_user = User(id="123", user_name="test@example.com", password="secret123")
414+
mock_client.create.return_value = mock_user
415+
416+
# Mock modify to return a resource with writeOnly attribute still present
417+
mock_modified_user = User(
418+
id="123",
419+
user_name="test@example.com",
420+
password="secret123", # writeOnly attribute still present
421+
)
422+
mock_client.modify.return_value = mock_modified_user
423+
mock_client.query.return_value = User(
424+
id="123",
425+
user_name="test@example.com",
426+
# password not returned in query (writeOnly)
427+
)
428+
429+
testing_context.client = mock_client
430+
431+
results = check_remove_attribute(testing_context, User)
432+
433+
# For writeOnly attributes, should continue to query step
434+
success_results = [
435+
r
436+
for r in results
437+
if r.status == Status.SUCCESS and "password" in r.data.get("urn", "")
438+
]
439+
assert len(success_results) > 0

tests/test_patch_replace.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,45 @@ def patch_handler(request):
264264
results = check_replace_attribute(testing_context, User[EnterpriseUser])
265265
unexpected = [r for r in results if r.status != Status.SUCCESS]
266266
assert not unexpected
267+
268+
269+
def test_patch_replace_attribute_not_replaced(httpserver, testing_context):
270+
"""Test PATCH replace when attribute is not actually replaced."""
271+
httpserver.expect_request(uri="/Users", method="POST").respond_with_json(
272+
{
273+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
274+
"id": "123",
275+
"userName": "test@example.com",
276+
"displayName": "original_display",
277+
},
278+
status=201,
279+
)
280+
281+
httpserver.expect_request(uri="/Users/123", method="PATCH").respond_with_json(
282+
{
283+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
284+
"id": "123",
285+
"userName": "test@example.com",
286+
"displayName": "original_display",
287+
},
288+
status=200,
289+
)
290+
291+
httpserver.expect_request(uri="/Users/123", method="GET").respond_with_json(
292+
{
293+
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
294+
"id": "123",
295+
"userName": "test@example.com",
296+
"displayName": "original_display",
297+
},
298+
status=200,
299+
)
300+
301+
results = check_replace_attribute(testing_context, User)
302+
303+
error_results = [
304+
r
305+
for r in results
306+
if r.status == Status.ERROR and "was not replaced" in r.reason
307+
]
308+
assert len(error_results) > 0

tests/test_utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from scim2_tester.utils import Status
1515
from scim2_tester.utils import _matches_hierarchical_tags
1616
from scim2_tester.utils import checker
17+
from scim2_tester.utils import compare_field
1718
from scim2_tester.utils import get_registered_tags
1819

1920

@@ -400,3 +401,15 @@ def write_check(context): ...
400401
assert result[0].status == Status.SKIPPED
401402
assert result[0].reason == "Skipped due to tag filtering"
402403
assert result[0].title == "write_check"
404+
405+
406+
def test_compare_field_with_none_values():
407+
"""Test compare_field function with None values."""
408+
# Test when expected is None
409+
assert compare_field(None, "some_value") is False
410+
411+
# Test when actual is None
412+
assert compare_field("some_value", None) is False
413+
414+
# Test when both are None
415+
assert compare_field(None, None) is False

0 commit comments

Comments
 (0)