aboutsummaryrefslogtreecommitdiff
"""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",
    (("(AND priv1 (or priv2 priv3))",
      ("AND", ("priv1",), ("OR", ("priv2", "priv3")))),))
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",
    (("(OR priv1 priv2 priv3 (and priv4 priv5))",
      ("OR", ("priv1", "priv2", "priv3"), ("AND", ("priv4", "priv5")))),))
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",
    ("()",
     "this is invalid",
     "priv1 AND priv2",
     "(priv1 AND priv2)",
     "(AND priv1 priv2 priv3"))
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",
    (("(AND priv1 (or priv2 priv3) priv4 (and priv5 priv6))",
      ("AND",
       ("priv1", "priv4", "priv5", "priv6"),
       ("OR", ("priv2", "priv3")))),))
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


@pytest.mark.unit_test
@pytest.mark.parametrize(
    "spec,expected",
    (
        # -- We need to be careful about reduction --
        # -- Please revisit your boolean logic to verify --
        # -- how to reduce boolean statements. --
        # ("(AND priv1 (or priv2 priv3) priv4 (or priv5 priv6))",
        #  ("AND",
        #   ("priv1", "priv4"),
        #   ("OR", ("priv2", "priv3", "priv5", "priv6")))),
        ("(OR priv1 (or priv2 priv3 (or priv4 priv5)) (or priv6 priv7))",
         ("OR", ("priv1", "priv6", "priv7", "priv2", "priv3", "priv4", "priv5"))),))
def test_reduction(spec, expected):
    """
    GIVEN: A spec that can be reduced
    WHEN: The specification is parsed
    THEN:
    - Verify that after parsing, it is reduced to the minimum number of levels
    - possible.
    """
    assert parse(spec) == expected