nucypher/tests/unit/conditions/test_if_then_else_condition.py

315 lines
9.6 KiB
Python

import pytest
from web3.exceptions import Web3Exception
from nucypher.policy.conditions.base import (
AccessControlCondition,
)
from nucypher.policy.conditions.exceptions import InvalidCondition
from nucypher.policy.conditions.lingo import (
ConditionType,
ConditionVariable,
IfThenElseCondition,
OrCompoundCondition,
SequentialAccessControlCondition,
)
from nucypher.policy.conditions.utils import ConditionProviderManager
@pytest.fixture(scope="function")
def mock_conditions(mocker):
cond_1 = mocker.Mock(spec=AccessControlCondition)
cond_1.verify.return_value = (True, 1)
cond_1.to_dict.return_value = {"value": 1}
cond_2 = mocker.Mock(spec=AccessControlCondition)
cond_2.verify.return_value = (True, 2)
cond_2.to_dict.return_value = {"value": 2}
cond_3 = mocker.Mock(spec=AccessControlCondition)
cond_3.verify.return_value = (True, 3)
cond_3.to_dict.return_value = {"value": 3}
return cond_1, cond_2, cond_3
def test_invalid_if_then_else_condition(rpc_condition, time_condition):
# invalid condition type
with pytest.raises(InvalidCondition, match=ConditionType.IF_THEN_ELSE.value):
_ = IfThenElseCondition(
condition_type=ConditionType.TIME.value,
if_condition=rpc_condition,
then_condition=time_condition,
else_condition=False,
)
def test_nested_sequential_condition_too_many_nested_levels(
rpc_condition, time_condition
):
# causes too many nested multi-conditions when used within a if-then-else condition
problematic_nested_condition = SequentialAccessControlCondition(
condition_variables=[
ConditionVariable("var1", time_condition),
ConditionVariable(
"seq_1",
IfThenElseCondition(
if_condition=rpc_condition,
then_condition=time_condition,
else_condition=rpc_condition,
),
),
]
)
# issue with "if"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=problematic_nested_condition,
then_condition=rpc_condition,
else_condition=True,
)
# issue with "then"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=rpc_condition,
then_condition=problematic_nested_condition,
else_condition=True,
)
# issue with "else"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=rpc_condition,
then_condition=time_condition,
else_condition=problematic_nested_condition,
)
def test_nested_compound_condition_too_many_nested_levels(
rpc_condition, time_condition
):
# causes too many nested multi-conditions when used within a if-then-else condition
problematic_nested_condition = OrCompoundCondition(
operands=[
rpc_condition,
IfThenElseCondition(
if_condition=time_condition,
then_condition=rpc_condition,
else_condition=time_condition,
),
]
)
# issue with "if"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=problematic_nested_condition,
then_condition=rpc_condition,
else_condition=True,
)
# issue with "then"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=rpc_condition,
then_condition=problematic_nested_condition,
else_condition=True,
)
# issue with "else"
with pytest.raises(
InvalidCondition, match="nested levels of multi-conditions are allowed"
):
_ = IfThenElseCondition(
if_condition=rpc_condition,
then_condition=time_condition,
else_condition=problematic_nested_condition,
)
def test_nested_multi_condition_allowed_levels(rpc_condition, time_condition):
# does not raise
_ = IfThenElseCondition(
if_condition=OrCompoundCondition(
operands=[
rpc_condition,
time_condition,
]
),
then_condition=IfThenElseCondition(
if_condition=rpc_condition,
then_condition=time_condition,
else_condition=rpc_condition,
),
else_condition=SequentialAccessControlCondition(
condition_variables=[
ConditionVariable("var1", time_condition),
ConditionVariable("var2", rpc_condition),
]
),
)
@pytest.mark.usefixtures("mock_skip_schema_validation")
def test_if_then_else_condition(mock_conditions):
cond_1, cond_2, cond_3 = mock_conditions
cond_1.verify.return_value = (True, 1)
cond_2.verify.return_value = (True, 2)
cond_3.verify.return_value = (False, 3)
if_then_else_condition = IfThenElseCondition(
if_condition=cond_1,
then_condition=cond_2,
else_condition=cond_3,
)
# then execution happens
result, value = if_then_else_condition.verify()
assert result is True
assert value == [1, 2]
# else execution happens
cond_1.verify.return_value = (False, 1)
result, value = if_then_else_condition.verify()
assert result is False
assert value == [1, 3]
# flip else condition to be sure
cond_3.verify.return_value = (True, 3)
result, value = if_then_else_condition.verify()
assert result is True
assert value == [1, 3]
@pytest.mark.usefixtures("mock_skip_schema_validation")
def test_if_then_else_condition_else_condition_is_boolean(mock_conditions):
cond_1, cond_2, _ = mock_conditions
cond_1.verify.return_value = (False, 1)
cond_2.verify.return_value = (True, 2)
if_then_else_condition = IfThenElseCondition(
if_condition=cond_1,
then_condition=cond_2,
else_condition=False,
)
# else execution happens - False
cond_1.verify.return_value = (False, 1)
result, value = if_then_else_condition.verify()
assert result is False
assert value == [1, False]
if_then_else_condition = IfThenElseCondition(
if_condition=cond_1,
then_condition=cond_2,
else_condition=True,
)
# else execution happens - True
result, value = if_then_else_condition.verify()
assert result is True
assert value == [1, True]
@pytest.mark.usefixtures("mock_skip_schema_validation")
def test_nested_multi_conditions(mock_conditions):
cond_1, cond_2, cond_3 = mock_conditions
# set up to execute "then" condition
cond_1.verify.return_value = (False, 1)
cond_2.verify.return_value = (True, 2)
cond_3.verify.return_value = (True, 3)
if_then_else_condition = IfThenElseCondition(
if_condition=OrCompoundCondition(
operands=[
cond_1,
cond_2,
]
),
then_condition=SequentialAccessControlCondition(
condition_variables=[
ConditionVariable("var1", cond_2),
ConditionVariable("var2", cond_3),
]
),
else_condition=False,
)
result, value = if_then_else_condition.verify(
providers=ConditionProviderManager({})
)
assert result is True
assert value == [[1, 2], [2, 3]] # [[or result], [seq result]]
# set up to execute else condition
cond_1.verify.return_value = (False, 1)
cond_2.verify.return_value = (False, 2)
cond_3.verify.return_value = (True, 3)
if_then_else_condition = IfThenElseCondition(
if_condition=OrCompoundCondition(
operands=[
cond_1,
cond_2,
]
),
then_condition=SequentialAccessControlCondition(
condition_variables=[
ConditionVariable("var1", cond_2),
ConditionVariable("var2", cond_3),
]
),
else_condition=IfThenElseCondition(
if_condition=cond_3,
then_condition=cond_2,
else_condition=cond_1,
),
)
result, value = if_then_else_condition.verify(
providers=ConditionProviderManager({})
)
assert result is False
assert value == [[1, 2], [3, 2]] # [[or result], [else if condition result]]
@pytest.mark.usefixtures("mock_skip_schema_validation")
def test_if_then_else_condition_call_fails(mock_conditions):
cond_1, cond_2, cond_3 = mock_conditions
if_then_else_condition = IfThenElseCondition(
if_condition=cond_1,
then_condition=cond_2,
else_condition=cond_3,
)
# if call fails
cond_1.verify.side_effect = Web3Exception("cond_1 failed")
with pytest.raises(Web3Exception, match="cond_1 failed"):
_ = if_then_else_condition.verify()
# then call fails
cond_1.verify.side_effect = lambda *args, **kwargs: (True, 1)
cond_2.verify.side_effect = Web3Exception("cond_2 failed")
with pytest.raises(Web3Exception, match="cond_2 failed"):
_ = if_then_else_condition.verify()
# else call fails
cond_1.verify.side_effect = lambda *args, **kwargs: (False, 1)
cond_3.verify.side_effect = Web3Exception("cond_3 failed")
with pytest.raises(Web3Exception, match="cond_3 failed"):
_ = if_then_else_condition.verify()