diff --git a/business_rules/engine.py b/business_rules/engine.py index 7483f9ad..b2cda603 100644 --- a/business_rules/engine.py +++ b/business_rules/engine.py @@ -5,6 +5,7 @@ from .actions import BaseActions from .fields import FIELD_NO_INPUT from .variables import BaseVariables +from .exceptions import MissingVariableException logger = logging.getLogger(__name__) @@ -83,7 +84,13 @@ async def check_condition(condition, defined_variables): name = condition['name'] op = condition['operator'] value = condition['value'] - operator_type = await _get_variable_value(defined_variables, name) + + try: + operator_type = await _get_variable_value(defined_variables, name) + except MissingVariableException: + """If variable value is missing, than corresponding condition is false""" + return False + if 'value_is_variable' in condition and condition['value_is_variable']: variable_name = value temp_value = await _get_variable_value(defined_variables, variable_name) diff --git a/business_rules/exceptions.py b/business_rules/exceptions.py new file mode 100644 index 00000000..fa8f0404 --- /dev/null +++ b/business_rules/exceptions.py @@ -0,0 +1,7 @@ + + +class MissingVariableException(BaseException): + """ + Variable can raise this exception, if variable value is missing. + If exception raised, than corresponding exception is false. + """ diff --git a/dev-requirements.txt b/dev-requirements.txt index 83dac91f..86f414c4 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,3 @@ pytest +pytest-asyncio mock==4.0.2 diff --git a/tests/test_missing.py b/tests/test_missing.py new file mode 100644 index 00000000..7010c7e7 --- /dev/null +++ b/tests/test_missing.py @@ -0,0 +1,88 @@ +from business_rules.variables import BaseVariables, string_rule_variable +from business_rules.actions import BaseActions, rule_action +from business_rules.exceptions import MissingVariableException +from business_rules import run +import pytest + + +class Payment: + def __init__(self, amount, type): + self.amount = amount + self.type = type + + +class Order: + def __init__(self, name, payment): + self.name = name + self.payment = payment + + +class Variables(BaseVariables): + + def __init__(self, order): + self.order = order + + @string_rule_variable() + async def order_payment_type(self): + if self.order.payment is None: + raise MissingVariableException() + + return self.order.payment.type + + +class Actions(BaseActions): + @rule_action() + async def approve(self): + return {'action': 'approve'} + + +@pytest.mark.asyncio +async def test_missing_variable_exception(): + rule = { + 'conditions': { + 'all': [ + { + 'name': 'order_payment_type', + 'operator': 'not_equal_to', + 'value': 'paypal' + } + ] + }, + 'actions': [ + { + 'name': 'approve' + } + ] + } + order = Order( + name='order', + payment=Payment(amount=10, type='credit_card') + ) + result = await run( + rule=rule, + defined_variables=Variables(order), + defined_actions=Actions() + ) + assert result == {'action_name': 'approve', 'action_params': {}, 'action_result': {'action': 'approve'}} + + order = Order( + name='order', + payment=Payment(amount=10, type='paypal') + ) + result = await run( + rule=rule, + defined_variables=Variables(order), + defined_actions=Actions() + ) + assert result is None + + order = Order( + name='order', + payment=None + ) + result = await run( + rule=rule, + defined_variables=Variables(order), + defined_actions=Actions() + ) + assert result is None