diff --git a/dsp_permissions_scripts/models/errors.py b/dsp_permissions_scripts/models/errors.py index 67617057..7d229d2d 100644 --- a/dsp_permissions_scripts/models/errors.py +++ b/dsp_permissions_scripts/models/errors.py @@ -32,3 +32,13 @@ class SpecifiedPropsNotEmptyError(ValueError): @dataclass class OapRetrieveConfigEmptyError(ValueError): message: str = "retrieve_resources cannot be False if retrieve_values is 'none'" + + +@dataclass +class EmptyScopeError(Exception): + message: str = "PermissionScope must not be empty" + + +@dataclass +class InvalidGroupError(Exception): + message: str diff --git a/dsp_permissions_scripts/models/group.py b/dsp_permissions_scripts/models/group.py index 3814963d..ec917ac7 100644 --- a/dsp_permissions_scripts/models/group.py +++ b/dsp_permissions_scripts/models/group.py @@ -6,6 +6,8 @@ from pydantic import ConfigDict from pydantic import model_validator +from dsp_permissions_scripts.models.errors import InvalidGroupError + class Group(BaseModel): model_config = ConfigDict(frozen=True) @@ -16,7 +18,7 @@ class Group(BaseModel): def _check_regex(self) -> Group: common_part = re.escape("http://www.knora.org/ontology/knora-admin#") if not re.search(f"{common_part}.+", self.val): - raise ValueError(f"{self.val} is not a valid group IRI") + raise InvalidGroupError(f"{self.val} is not a valid group IRI") return self diff --git a/dsp_permissions_scripts/models/scope.py b/dsp_permissions_scripts/models/scope.py index 6d311095..cf1ebbbc 100644 --- a/dsp_permissions_scripts/models/scope.py +++ b/dsp_permissions_scripts/models/scope.py @@ -8,6 +8,7 @@ from pydantic import ConfigDict from pydantic import model_validator +from dsp_permissions_scripts.models.errors import EmptyScopeError from dsp_permissions_scripts.models.group import CREATOR from dsp_permissions_scripts.models.group import KNOWN_USER from dsp_permissions_scripts.models.group import PROJECT_ADMIN @@ -73,6 +74,12 @@ def check_group_occurs_only_once(self) -> PermissionScope: raise ValueError(f"Group {group} must not occur in more than one field") return self + @model_validator(mode="after") + def check_scope_not_empty(self) -> PermissionScope: + if not any([len(self.get(field)) > 0 for field in self.model_fields]): + raise EmptyScopeError() + return self + def get(self, permission: str) -> frozenset[Group]: """Retrieve the groups that have the given permission.""" if permission not in self.model_fields: diff --git a/tests/test_group.py b/tests/test_group.py new file mode 100644 index 00000000..83f35947 --- /dev/null +++ b/tests/test_group.py @@ -0,0 +1,24 @@ +import pytest + +from dsp_permissions_scripts.models import group +from dsp_permissions_scripts.models.errors import InvalidGroupError + + +def test_builtin_groups() -> None: + assert group.UNKNOWN_USER == group.Group(val="http://www.knora.org/ontology/knora-admin#UnknownUser") + assert group.KNOWN_USER == group.Group(val="http://www.knora.org/ontology/knora-admin#KnownUser") + assert group.PROJECT_MEMBER == group.Group(val="http://www.knora.org/ontology/knora-admin#ProjectMember") + assert group.PROJECT_ADMIN == group.Group(val="http://www.knora.org/ontology/knora-admin#ProjectAdmin") + assert group.CREATOR == group.Group(val="http://www.knora.org/ontology/knora-admin#Creator") + assert group.SYSTEM_ADMIN == group.Group(val="http://www.knora.org/ontology/knora-admin#SystemAdmin") + + +def test_custom_group() -> None: + group_iri = "http://www.knora.org/ontology/knora-admin#my-custom-group" + custom_group = group.Group(val=group_iri) + assert custom_group.val == group_iri + + +def test_invalid_group() -> None: + with pytest.raises(InvalidGroupError): + group.Group(val="http://www.knora.org/v2/resources/foo") diff --git a/tests/test_scope.py b/tests/test_scope.py index f9436adb..d5658427 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -3,16 +3,67 @@ import pytest from dsp_permissions_scripts.models import group +from dsp_permissions_scripts.models.errors import EmptyScopeError from dsp_permissions_scripts.models.scope import PermissionScope -def test_scope_validation_on_creation() -> None: - with pytest.raises(ValueError, match=re.escape("must not occur in more than one field")): - PermissionScope.create( - CR={group.PROJECT_ADMIN}, +class TestScopeCreation: + def test_valid_scope(self) -> None: + scope = PermissionScope( + CR=frozenset({group.SYSTEM_ADMIN}), + D=frozenset({group.PROJECT_ADMIN}), + M=frozenset({group.PROJECT_MEMBER, group.KNOWN_USER}), + V=frozenset({group.UNKNOWN_USER}), + ) + assert scope.CR == frozenset({group.SYSTEM_ADMIN}) + assert scope.D == frozenset({group.PROJECT_ADMIN}) + assert scope.M == frozenset({group.PROJECT_MEMBER, group.KNOWN_USER}) + assert scope.V == frozenset({group.UNKNOWN_USER}) + assert scope.RV == frozenset() + + def test_valid_scope_create(self) -> None: + scope = PermissionScope.create( + CR={group.SYSTEM_ADMIN}, D={group.PROJECT_ADMIN}, - V={group.UNKNOWN_USER, group.KNOWN_USER}, + M={group.PROJECT_MEMBER, group.KNOWN_USER}, + V={group.UNKNOWN_USER}, ) + assert scope.CR == frozenset({group.SYSTEM_ADMIN}) + assert scope.D == frozenset({group.PROJECT_ADMIN}) + assert scope.M == frozenset({group.PROJECT_MEMBER, group.KNOWN_USER}) + assert scope.V == frozenset({group.UNKNOWN_USER}) + assert scope.RV == frozenset() + + def test_valid_scope_from_dict(self) -> None: + scope = PermissionScope.from_dict( + { + "CR": [group.SYSTEM_ADMIN.val], + "D": [group.PROJECT_ADMIN.val], + "M": [group.PROJECT_MEMBER.val, group.KNOWN_USER.val], + "V": [group.UNKNOWN_USER.val], + } + ) + assert scope.CR == frozenset({group.SYSTEM_ADMIN}) + assert scope.D == frozenset({group.PROJECT_ADMIN}) + assert scope.M == frozenset({group.PROJECT_MEMBER, group.KNOWN_USER}) + assert scope.V == frozenset({group.UNKNOWN_USER}) + assert scope.RV == frozenset() + + def test_create_empty_scope(self) -> None: + with pytest.raises(EmptyScopeError): + PermissionScope.create() + with pytest.raises(EmptyScopeError): + PermissionScope() + with pytest.raises(EmptyScopeError): + PermissionScope.from_dict({}) + + def test_same_group_in_multiple_fields(self) -> None: + with pytest.raises(ValueError, match=re.escape("must not occur in more than one field")): + PermissionScope.create( + CR={group.PROJECT_ADMIN}, + D={group.PROJECT_ADMIN}, + V={group.UNKNOWN_USER, group.KNOWN_USER}, + ) class TestAdd: @@ -78,6 +129,31 @@ def test_remove_from_empty_perm(self) -> None: _ = scope.remove("CR", group.PROJECT_ADMIN) +class TestGet: + def test_get(self) -> None: + scope = PermissionScope.create( + CR={group.PROJECT_ADMIN}, + D={group.SYSTEM_ADMIN}, + M={group.PROJECT_MEMBER, group.KNOWN_USER}, + V={group.UNKNOWN_USER}, + ) + assert scope.get("CR") == frozenset({group.PROJECT_ADMIN}) + assert scope.get("D") == frozenset({group.SYSTEM_ADMIN}) + assert scope.get("M") == frozenset({group.PROJECT_MEMBER, group.KNOWN_USER}) + assert scope.get("V") == frozenset({group.UNKNOWN_USER}) + assert scope.get("RV") == frozenset() + + def test_get_inexisting_perm(self) -> None: + scope = PermissionScope.create( + CR={group.PROJECT_ADMIN}, + D={group.SYSTEM_ADMIN}, + M={group.PROJECT_MEMBER, group.KNOWN_USER}, + V={group.UNKNOWN_USER}, + ) + with pytest.raises(ValueError, match=re.escape("Permission 'foo' not in")): + _ = scope.get("foo") + + class TestRemoveDuplicatesFromKwargs: def test_remove_duplicates_from_kwargs_CR(self) -> None: original: dict[str, list[str]] = {