From f3f6080f2525c9b162e9bd43478ad18af54bd497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 27 Jul 2025 15:32:05 +0200 Subject: [PATCH] fix: behavior when patch removes null uneditable fields --- scim2_server/operators.py | 7 ++++--- tests/test_operators.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scim2_server/operators.py b/scim2_server/operators.py index 975ab22..602d32a 100644 --- a/scim2_server/operators.py +++ b/scim2_server/operators.py @@ -258,9 +258,6 @@ class RemoveOperator(Operator): @classmethod def operation(cls, model: BaseModel, attribute: str, value: Any): alias = get_by_alias(type(model), attribute) - existing_value = getattr(model, alias) - if not existing_value: - return if model.get_field_annotation(alias, Mutability) in ( Mutability.read_only, @@ -268,6 +265,10 @@ def operation(cls, model: BaseModel, attribute: str, value: Any): ): raise SCIMException(Error.make_mutability_error()) + existing_value = getattr(model, alias) + if not existing_value: + return + if model.get_field_annotation(alias, Required) == Required.true: raise SCIMException(Error.make_invalid_value_error()) diff --git a/tests/test_operators.py b/tests/test_operators.py index 472c501..21c81ae 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -451,6 +451,16 @@ def test_simple_remove_operator_immutable(self): with pytest.raises(SCIMException, match="immutable"): RemoveOperator.operation(u, "groups", None) + def test_remove_operator_mutability_validation_on_empty_fields(self): + """Test that mutability constraints are enforced even on empty/unset fields.""" + u = User(id="123") # groups field is None/empty by default + with pytest.raises(SCIMException, match="mutability"): + RemoveOperator.operation(u, "groups", None) + + u2 = User() # id field is None/empty by default + with pytest.raises(SCIMException, match="mutability"): + RemoveOperator.operation(u2, "id", None) + def test_remove_operator_root_object(self): u = User() with pytest.raises(SCIMException, match="noTarget"):