diff --git a/dsp_permissions_scripts/models/scope.py b/dsp_permissions_scripts/models/scope.py index 2acead56..111b1365 100644 --- a/dsp_permissions_scripts/models/scope.py +++ b/dsp_permissions_scripts/models/scope.py @@ -64,6 +64,23 @@ def add( if perm != permission: kwargs[perm] = getattr(self, perm) return PermissionScope.create(**kwargs) + + def remove( + self, + permission: Literal["CR", "D", "M", "V", "RV"], + group: str | BuiltinGroup, + ) -> PermissionScope: + """Return a copy of the PermissionScope instance with group removed from permission.""" + groups = [g.value if isinstance(g, BuiltinGroup) else g for g in getattr(self, permission)] + group = group.value if isinstance(group, BuiltinGroup) else group + if group not in groups: + raise ValueError(f"Group '{group}' is not in permission '{permission}'") + groups.remove(group) + kwargs: dict[str, list[str]] = {permission: groups} + for perm in ["CR", "D", "M", "V", "RV"]: + if perm != permission: + kwargs[perm] = getattr(self, perm) + return PermissionScope.create(**kwargs) PUBLIC = PermissionScope.create( diff --git a/tests/test_scope.py b/tests/test_scope.py index bd5fb853..69b79602 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -2,6 +2,7 @@ from dsp_permissions_scripts.models.groups import BuiltinGroup from dsp_permissions_scripts.models.scope import PermissionScope +from tests.test_scope_serialization import compare_scopes class TestScope(unittest.TestCase): @@ -39,13 +40,44 @@ def test_add_to_scope(self) -> None: M={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.KNOWN_USER}, ) scope_added = scope.add("CR", BuiltinGroup.PROJECT_ADMIN) - self.assertEqual( - scope_added.model_dump_json(), - PermissionScope.create( + compare_scopes( + scope1=scope_added, + scope2=PermissionScope.create( CR={BuiltinGroup.PROJECT_ADMIN}, D={BuiltinGroup.SYSTEM_ADMIN}, M={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.KNOWN_USER}, - ).model_dump_json(), + ), + ) + + def test_remove_inexisting_group(self) -> None: + scope = PermissionScope.create( + D={BuiltinGroup.SYSTEM_ADMIN}, + M={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.KNOWN_USER}, + ) + with self.assertRaisesRegex(ValueError, "is not in permission 'D'"): + _ = scope.remove("D", BuiltinGroup.UNKNOWN_USER) + + def test_remove_from_empty_perm(self) -> None: + scope = PermissionScope.create( + D={BuiltinGroup.PROJECT_ADMIN}, + V={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.UNKNOWN_USER}, + ) + with self.assertRaisesRegex(ValueError, "is not in permission 'CR'"): + _ = scope.remove("CR", BuiltinGroup.PROJECT_ADMIN) + + def test_remove_from_scope(self) -> None: + scope = PermissionScope.create( + CR={BuiltinGroup.PROJECT_ADMIN}, + D={BuiltinGroup.SYSTEM_ADMIN}, + M={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.KNOWN_USER}, + ) + scope_removed = scope.remove("CR", BuiltinGroup.PROJECT_ADMIN) + compare_scopes( + scope1=scope_removed, + scope2=PermissionScope.create( + D={BuiltinGroup.SYSTEM_ADMIN}, + M={BuiltinGroup.PROJECT_MEMBER, BuiltinGroup.KNOWN_USER}, + ), ) if __name__ == "__main__": diff --git a/tests/test_scope_serialization.py b/tests/test_scope_serialization.py index 873dd536..866d3bf5 100644 --- a/tests/test_scope_serialization.py +++ b/tests/test_scope_serialization.py @@ -12,6 +12,18 @@ ) +def compare_scopes( + scope1: PermissionScope, + scope2: PermissionScope, + msg: str | None = None, +) -> None: + scope1_dict = json.loads(scope1.model_dump_json()) + scope1_dict = {k: sorted(v) for k, v in scope1_dict.items()} + scope2_dict = json.loads(scope2.model_dump_json()) + scope2_dict = {k: sorted(v) for k, v in scope2_dict.items()} + unittest.TestCase().assertDictEqual(scope1_dict, scope2_dict, msg=msg) + + class TestScopeSerialization(unittest.TestCase): perm_strings = [ "CR knora-admin:SystemAdmin|V knora-admin:CustomGroup", @@ -64,25 +76,17 @@ class TestScopeSerialization(unittest.TestCase): def test_create_scope_from_string(self) -> None: for perm_string, scope in zip(self.perm_strings, self.scopes): - returned = json.loads(create_scope_from_string(perm_string).model_dump_json()) - returned = {k: sorted(v) for k, v in returned.items()} - expected = json.loads(scope.model_dump_json()) - expected = {k: sorted(v) for k, v in expected.items()} - self.assertDictEqual( - returned, - expected, + compare_scopes( + scope1=create_scope_from_string(perm_string), + scope2=scope, msg=f"Failed with permission string '{perm_string}'", ) def test_create_scope_from_admin_route_object(self) -> None: for admin_route_object, scope, index in zip(self.admin_route_objects, self.scopes, range(len(self.scopes))): - returned = json.loads(create_scope_from_admin_route_object(admin_route_object).model_dump_json()) - returned = {k: sorted(v) for k, v in returned.items()} - expected = json.loads(scope.model_dump_json()) - expected = {k: sorted(v) for k, v in expected.items()} - self.assertDictEqual( - returned, - expected, + compare_scopes( + scope1=create_scope_from_admin_route_object(admin_route_object), + scope2=scope, msg=f"Failed with admin group object no. {index}", )