Add tests for conditions lingo validation. Use InvalidConditionLingo exception for invalid lingo grammar.

pull/3025/head
derekpierre 2022-11-18 13:42:25 -05:00
parent c50eb1e5c2
commit 2e0a1e5782
5 changed files with 53 additions and 7 deletions

View File

@ -1,4 +1,8 @@
# Lingo Validation Errors (Grammar)
class InvalidConditionLingo(Exception):
"""Invalid lingo grammar."""
class InvalidLogicalOperator(Exception):
"""Invalid definition of logical lingo operator."""

View File

@ -10,6 +10,7 @@ from marshmallow import fields, post_load
from nucypher.policy.conditions.base import ReencryptionCondition
from nucypher.policy.conditions.context import is_context_variable
from nucypher.policy.conditions.exceptions import (
InvalidConditionLingo,
InvalidLogicalOperator,
ReturnValueEvaluationError,
)
@ -180,12 +181,18 @@ class ConditionLingo:
@staticmethod
def _validate_grammar(lingo) -> None:
if len(lingo) % 2 == 0:
raise ValueError('conditions must be odd length, ever other element being an operator')
raise InvalidConditionLingo(
"conditions must be odd length, ever other element being an operator"
)
for index, element in enumerate(lingo):
if (not index % 2) and not (isinstance(element, ReencryptionCondition)):
raise Exception(f'{index} element must be a condition; Got {type(element)}.')
raise InvalidConditionLingo(
f"{index} element must be a condition; Got {type(element)}."
)
elif (index % 2) and (not isinstance(element, Operator)):
raise Exception(f'{index} element must be an operator; Got {type(element)}.')
raise InvalidConditionLingo(
f"{index} element must be an operator; Got {type(element)}."
)
@classmethod
def from_list(cls, payload: LingoList) -> "ConditionLingo":
@ -240,7 +247,9 @@ class ConditionLingo:
elif isinstance(task, Operator):
yield task
else:
raise TypeError(f"Unrecognized type {type(task)} for ConditionLingo")
raise InvalidConditionLingo(
f"Unrecognized type {type(task)} for ConditionLingo"
)
def eval(self, *args, **kwargs) -> bool:
data = self.__process(*args, **kwargs)

View File

@ -11,6 +11,7 @@ from nucypher.policy.conditions.exceptions import (
ConditionEvaluationFailed,
ContextVariableVerificationFailed,
InvalidCondition,
InvalidConditionLingo,
InvalidContextVariableData,
NoConnectionToChain,
RequiredContextVariable,
@ -84,7 +85,9 @@ def resolve_condition_lingo(
elif operator:
return Operator
else:
raise Exception(f"Cannot resolve condition lingo type from data {data}")
raise InvalidConditionLingo(
f"Cannot resolve condition lingo type from data {data}"
)
def deserialize_condition_lingo(
@ -134,6 +137,11 @@ def evaluate_condition_lingo(
f"Unable to evaluate return value: {e}",
HTTPStatus.BAD_REQUEST,
)
except InvalidConditionLingo as e:
error = (
f"Invalid condition grammar: {e}",
HTTPStatus.BAD_REQUEST,
)
except InvalidCondition as e:
error = (
f"Incorrect value provided for condition: {e}",

View File

@ -3,6 +3,7 @@ import pytest
import nucypher
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.policy.conditions.context import USER_ADDRESS_CONTEXT
from nucypher.policy.conditions.exceptions import InvalidConditionLingo
from nucypher.policy.conditions.lingo import ConditionLingo
@ -16,10 +17,10 @@ def lingo():
def test_invalid_condition():
with pytest.raises(Exception):
with pytest.raises(InvalidConditionLingo):
ConditionLingo.from_list([{}])
with pytest.raises(Exception):
with pytest.raises(InvalidConditionLingo):
ConditionLingo.from_list([{"dont_mind_me": "nothing_to_see_here"}])

View File

@ -29,11 +29,13 @@ from nucypher.policy.conditions.utils import (
camel_case_to_snake,
evaluate_condition_lingo,
to_camelcase,
validate_condition_lingo,
)
FAILURE_CASE_EXCEPTION_CODE_MATCHING = [
# (exception, constructor parameters, expected status code)
(ReturnValueEvaluationError, None, HTTPStatus.BAD_REQUEST),
(InvalidConditionLingo, None, HTTPStatus.BAD_REQUEST),
(InvalidCondition, None, HTTPStatus.BAD_REQUEST),
(RequiredContextVariable, None, HTTPStatus.BAD_REQUEST),
(InvalidContextVariableData, None, HTTPStatus.BAD_REQUEST),
@ -124,3 +126,25 @@ def test_camel_case_schema():
reloaded_function = schema.load(output)
assert reloaded_function == {"field_name_with_underscores": f"{value}"}
def test_condition_lingo_validation(compound_lingo):
# valid compound lingo
compound_lingo_list = compound_lingo.to_list()
validate_condition_lingo(compound_lingo_list)
# no issues here
invalid_operator_lingo = [
{"returnValueTest": {"value": 0, "comparator": ">"}, "method": "timelock"},
{"operator": "AND_OPERATOR"}, # replace operator with invalid one
{
"returnValueTest": {"value": 99999999999999999, "comparator": "<"},
"method": "timelock",
},
]
with pytest.raises(InvalidLogicalOperator):
validate_condition_lingo(invalid_operator_lingo)
# invalid condition
with pytest.raises(InvalidConditionLingo):
validate_condition_lingo([{"dont_mind_me": "nothing_to_see_here"}])