about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn_libs/privileges.py28
-rw-r--r--tests/unit/test_privileges_checking.py16
-rw-r--r--tests/unit/test_privileges_spec_parsing.py141
3 files changed, 185 insertions, 0 deletions
diff --git a/gn_libs/privileges.py b/gn_libs/privileges.py
new file mode 100644
index 0000000..47cb735
--- /dev/null
+++ b/gn_libs/privileges.py
@@ -0,0 +1,28 @@
+"""Utilities for handling privileges."""
+from typing import Union, TypeAlias
+
+PrivilegesList = tuple[str, ...]
+CheckObj = tuple[str, PrivilegesList] # where the first item is either "OR" or "AND"
+Checks: TypeAlias = Union[
+    CheckObj,
+    tuple[str, tuple['Checks', ...]]]
+
+
+class SpecificationValueError(ValueError):
+    """Raised when there is an error in the specification string."""
+    pass
+
+
+def parse(spec: str) -> Checks:
+    """Parse a string specification for privileges and return a tree of data
+    objects of the form (<operator> (<check>))"""
+    # if(spec.strip() == ""):
+    #     raise SpecificationValueError(
+    #         "You passed an empty specification. I do not know what to do.")
+
+    return tuple()
+
+
+def check(spec: str, privileges: tuple[str, ...]) -> bool:
+    """Check that the sequence of `privileges` satisfies `spec`."""
+    return False
diff --git a/tests/unit/test_privileges_checking.py b/tests/unit/test_privileges_checking.py
new file mode 100644
index 0000000..460344f
--- /dev/null
+++ b/tests/unit/test_privileges_checking.py
@@ -0,0 +1,16 @@
+"""Tests to verify the privileges check works."""
+import pytest
+
+from gn_libs.privileges import check
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,privileges,expected",
+    ())
+def test_check(spec, privileges, expected):
+    """
+    GIVEN: A privileges-check specification, and a tuple of privileges
+    WHEN: A check is performed
+    THEN: Verify that the check returns the expected value
+    """
+    assert check(spec, privileges) == expected
diff --git a/tests/unit/test_privileges_spec_parsing.py b/tests/unit/test_privileges_spec_parsing.py
new file mode 100644
index 0000000..7b563e1
--- /dev/null
+++ b/tests/unit/test_privileges_spec_parsing.py
@@ -0,0 +1,141 @@
+"""Tests for parsing the privileges checks specification."""
+import pytest
+
+from gn_libs.privileges import parse, SpecificationValueError
+
+
+## NOTE: Should we limit depth of nesting of checks, e.g. don't do more than
+## 3 levels or so?
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec",
+    ("",
+     "(AND)",
+     "(AND (OR))",
+     "(OR (AND))",
+     "(OR (AND (OR (AND ))))"))
+def test_empty_spec(spec):
+    """
+    GIVEN: An effectively empty specification
+    WHEN: The specification is parsed
+    THEN: Raise a `SpecificationValueError`
+    """
+    with pytest.raises(SpecificationValueError):
+        parse(spec)
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    (("(AND priv1)", ("AND", ("priv1",))),
+     ("(AND priv1 priv2)", ("AND", ("priv1", "priv2"))),
+     ("(AND priv1 priv2 priv3)", ("AND", ("priv1", "priv2", "priv3"))),
+     ("(and priv1)", ("AND", ("priv1",))),
+     ("(and priv1 priv2)", ("AND", ("priv1", "priv2"))),
+     ("(and priv1 priv2 priv3)", ("AND", ("priv1", "priv2", "priv3"))),
+     ("(and priv1 priv2 (and priv3 priv4))",
+      ("AND", ("priv1", "priv2", "priv3", "priv4")))))
+def test_and(spec, expected):
+    """
+    GIVEN: A simple 'AND' privileges check specification `spec`
+    WHEN: The specification is parsed
+    THEN: Verify the parsed output gives an 'AND' check object
+    """
+    assert parse(spec) == expected
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    (("(OR priv1)", ("OR", ("priv1",))),
+     ("(OR priv1 priv2)", ("OR", ("priv1", "priv2"))),
+     ("(OR priv1 priv2 priv3)", ("OR", ("priv1", "priv2", "priv3"))),
+     ("(or priv1)", ("OR", ("priv1",))),
+     ("(or priv1 priv2)", ("OR", ("priv1", "priv2"))),
+     ("(or priv1 priv2 priv3)", ("OR", ("priv1", "priv2", "priv3")))))
+def test_or(spec, expected):
+    """
+    GIVEN: A simple 'OR' privileges check specification `spec`
+    WHEN: The specification is parsed
+    THEN: Verify the parsed output gives an 'OR' check object
+    """
+    assert parse(spec) == expected
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    (("(or priv1 priv2 (or priv3 priv4))",
+      ("OR", ("priv1", "priv2", "priv3", "priv4"))),
+     ("(and priv1 priv2 (and priv3 priv4))",
+      ("AND", ("priv1", "priv2", "priv3", "priv4")))))
+def test_merging(spec, expected):
+    """
+    GIVEN:
+    - A nested specification where 2 or more of subsequent operators are
+    - the same
+    WHEN: The specification is parsed
+    THEN: Verify the parsed output merges the checks into a single object
+    """
+    # NOTE: The "given-when-then" description above does not please me.
+    assert parse(spec) == expected
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    ())
+def test_and_or(spec, expected):
+    """
+    GIVEN:
+    - A specification beginning with an "AND" operator followed by an "OR"
+    - operator
+    WHEN: The specification is parsed
+    THEN: Verify the parsed output is correct
+    """
+    assert parse(spec) == expected
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    ())
+def test_or_and(spec, expected):
+    """
+    GIVEN:
+    - A specification beginning with an "OR" operator followed by an "AND"
+    - operator
+    WHEN: The specification is parsed
+    THEN: Verify the parsed output is correct
+    """
+    assert parse(spec) == expected
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec",
+    ())
+def test_invalid(spec):
+    """
+    GIVEN: An invalid specification
+    WHEN: The specification is parsed
+    THEN: Verify that the `SpecificationValueError` is raised
+    """
+    # NOTE: Maybe use hypothesis to generate random strings?
+    with pytest.raises(SpecificationValueError):
+        assert parse(spec)
+
+
+@pytest.mark.unit_test
+@pytest.mark.parametrize(
+    "spec,expected",
+    ())
+def test_complex(spec, expected):
+    """
+    GIVEN: An valid, but more complex specification
+    WHEN: The specification is parsed
+    THEN: Verify that the specification parses correctly
+    """
+    assert parse(spec) == expected