diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_func.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_func.py index b8894b99bedf8..f3211e3da9d24 100644 --- a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_func.py +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_func.py @@ -1,98 +1,35 @@ from __future__ import annotations import json -from enum import Enum from typing import Final -from localstack.services.stepfunctions.asl.antlr.runtime.ASLLexer import ASLLexer +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( + ComparisonOperatorType, +) from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_stmt import ( ComparisonStmt, ) -from localstack.services.stepfunctions.asl.component.state.state_wait.variable import NoSuchVariable +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.factory import ( + OperatorFactory, +) +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.operator import ( + Operator, +) from localstack.services.stepfunctions.asl.eval.environment import Environment class ComparisonFunc(ComparisonStmt): - class ComparisonOperator(Enum): - BooleanEqual = ASLLexer.BOOLEANEQUALS - BooleanEqualsPath = ASLLexer.BOOLEANQUALSPATH - IsBoolean = ASLLexer.ISBOOLEAN - IsNull = ASLLexer.ISNULL - IsNumeric = ASLLexer.ISNUMERIC - IsPresent = ASLLexer.ISPRESENT - IsString = ASLLexer.ISSTRING - IsTimestamp = ASLLexer.ISTIMESTAMP - NumericEquals = ASLLexer.NUMERICEQUALS - NumericEqualsPath = ASLLexer.NUMERICEQUALSPATH - NumericGreaterThan = ASLLexer.NUMERICGREATERTHAN - NumericGreaterThanPath = ASLLexer.NUMERICGREATERTHANPATH - NumericGreaterThanEquals = ASLLexer.NUMERICGREATERTHANEQUALS - NumericGreaterThanEqualsPath = ASLLexer.NUMERICGREATERTHANEQUALSPATH - NumericLessThan = ASLLexer.NUMERICLESSTHAN - NumericLessThanPath = ASLLexer.NUMERICLESSTHANPATH - NumericLessThanEquals = ASLLexer.NUMERICLESSTHANEQUALS - NumericLessThanEqualsPath = ASLLexer.NUMERICLESSTHANEQUALSPATH - StringEquals = ASLLexer.STRINGEQUALS - StringEqualsPath = ASLLexer.STRINGEQUALSPATH - StringGreaterThan = ASLLexer.STRINGGREATERTHAN - StringGreaterThanPath = ASLLexer.STRINGGREATERTHANPATH - StringGreaterThanEquals = ASLLexer.STRINGGREATERTHANEQUALS - StringGreaterThanEqualsPath = ASLLexer.STRINGGREATERTHANEQUALSPATH - StringLessThan = ASLLexer.STRINGLESSTHAN - StringLessThanPath = ASLLexer.STRINGLESSTHANPATH - StringLessThanEquals = ASLLexer.STRINGLESSTHANEQUALS - StringLessThanEqualsPath = ASLLexer.STRINGLESSTHANEQUALSPATH - StringMatches = ASLLexer.STRINGMATCHES - TimestampEquals = ASLLexer.TIMESTAMPEQUALS - TimestampEqualsPath = ASLLexer.TIMESTAMPEQUALSPATH - TimestampGreaterThan = ASLLexer.TIMESTAMPGREATERTHAN - TimestampGreaterThanPath = ASLLexer.TIMESTAMPGREATERTHANPATH - TimestampGreaterThanEquals = ASLLexer.TIMESTAMPGREATERTHANEQUALS - TimestampGreaterThanEqualsPath = ASLLexer.TIMESTAMPGREATERTHANEQUALSPATH - TimestampLessThan = ASLLexer.TIMESTAMPLESSTHAN - TimestampLessThanPath = ASLLexer.TIMESTAMPLESSTHANPATH - TimestampLessThanEquals = ASLLexer.TIMESTAMPLESSTHANEQUALS - TimestampLessThanEqualsPath = ASLLexer.TIMESTAMPLESSTHANEQUALSPATH - - def __str__(self): - return f"({self.__class__.__name__}| {self})" - - def __init__(self, operator: ComparisonFunc.ComparisonOperator, value: json): - self.operator: Final[ComparisonFunc.ComparisonOperator] = operator + def __init__(self, operator: ComparisonOperatorType, value: json): + self.operator_type: Final[ComparisonOperatorType] = operator self.value: json = value def _eval_body(self, env: Environment) -> None: value = self.value - match self.operator: - case ComparisonFunc.ComparisonOperator.IsNull: - self._is_null(env, value) - case ComparisonFunc.ComparisonOperator.StringEquals: - self._string_equals(env, value) - case ComparisonFunc.ComparisonOperator.IsPresent: - self._is_present(env) - # TODO: add other operators. - case x: - raise NotImplementedError(f"ComparisonFunc '{x}' is not supported yet.") # noqa - - @staticmethod - def _is_null(env: Environment, value: json) -> None: - if not isinstance(value, bool): - raise RuntimeError(f"Unexpected binding to IsNull: '{value}'.") - val = env.stack.pop() - is_null = val is None and not isinstance( - val, NoSuchVariable - ) # TODO: what if input_state is empty, eg. "" or {}? - res = is_null == value - env.stack.append(res) + operator: Operator = OperatorFactory.get(self.operator_type) + operator.eval(env=env, value=value) @staticmethod def _string_equals(env: Environment, value: json) -> None: val = env.stack.pop() res = str(val) == value env.stack.append(res) - - @staticmethod - def _is_present(env: Environment) -> None: - val = env.stack.pop() - res = not isinstance(val, NoSuchVariable) - env.stack.append(res) diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_operator_type.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_operator_type.py new file mode 100644 index 0000000000000..7e84fd4cc11b5 --- /dev/null +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison_operator_type.py @@ -0,0 +1,48 @@ +from enum import Enum + +from localstack.services.stepfunctions.asl.antlr.runtime.ASLLexer import ASLLexer + + +class ComparisonOperatorType(Enum): + BooleanEquals = ASLLexer.BOOLEANEQUALS + BooleanEqualsPath = ASLLexer.BOOLEANQUALSPATH + IsBoolean = ASLLexer.ISBOOLEAN + IsNull = ASLLexer.ISNULL + IsNumeric = ASLLexer.ISNUMERIC + IsPresent = ASLLexer.ISPRESENT + IsString = ASLLexer.ISSTRING + IsTimestamp = ASLLexer.ISTIMESTAMP + NumericEquals = ASLLexer.NUMERICEQUALS + NumericEqualsPath = ASLLexer.NUMERICEQUALSPATH + NumericGreaterThan = ASLLexer.NUMERICGREATERTHAN + NumericGreaterThanPath = ASLLexer.NUMERICGREATERTHANPATH + NumericGreaterThanEquals = ASLLexer.NUMERICGREATERTHANEQUALS + NumericGreaterThanEqualsPath = ASLLexer.NUMERICGREATERTHANEQUALSPATH + NumericLessThan = ASLLexer.NUMERICLESSTHAN + NumericLessThanPath = ASLLexer.NUMERICLESSTHANPATH + NumericLessThanEquals = ASLLexer.NUMERICLESSTHANEQUALS + NumericLessThanEqualsPath = ASLLexer.NUMERICLESSTHANEQUALSPATH + StringEquals = ASLLexer.STRINGEQUALS + StringEqualsPath = ASLLexer.STRINGEQUALSPATH + StringGreaterThan = ASLLexer.STRINGGREATERTHAN + StringGreaterThanPath = ASLLexer.STRINGGREATERTHANPATH + StringGreaterThanEquals = ASLLexer.STRINGGREATERTHANEQUALS + StringGreaterThanEqualsPath = ASLLexer.STRINGGREATERTHANEQUALSPATH + StringLessThan = ASLLexer.STRINGLESSTHAN + StringLessThanPath = ASLLexer.STRINGLESSTHANPATH + StringLessThanEquals = ASLLexer.STRINGLESSTHANEQUALS + StringLessThanEqualsPath = ASLLexer.STRINGLESSTHANEQUALSPATH + StringMatches = ASLLexer.STRINGMATCHES + TimestampEquals = ASLLexer.TIMESTAMPEQUALS + TimestampEqualsPath = ASLLexer.TIMESTAMPEQUALSPATH + TimestampGreaterThan = ASLLexer.TIMESTAMPGREATERTHAN + TimestampGreaterThanPath = ASLLexer.TIMESTAMPGREATERTHANPATH + TimestampGreaterThanEquals = ASLLexer.TIMESTAMPGREATERTHANEQUALS + TimestampGreaterThanEqualsPath = ASLLexer.TIMESTAMPGREATERTHANEQUALSPATH + TimestampLessThan = ASLLexer.TIMESTAMPLESSTHAN + TimestampLessThanPath = ASLLexer.TIMESTAMPLESSTHANPATH + TimestampLessThanEquals = ASLLexer.TIMESTAMPLESSTHANEQUALS + TimestampLessThanEqualsPath = ASLLexer.TIMESTAMPLESSTHANEQUALSPATH + + # def __str__(self): + # return f"({self.__class__.__name__}| {self})" diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/__init__.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/factory.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/factory.py new file mode 100644 index 0000000000000..bd301e48b84ca --- /dev/null +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/factory.py @@ -0,0 +1,17 @@ +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( + ComparisonOperatorType, +) +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.implementations.boolean_equals import * # noqa +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.implementations.is_operator import * # noqa +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.operator import ( + Operator, +) + + +class OperatorFactory: + @staticmethod + def get(typ: ComparisonOperatorType) -> Operator: + op = Operator.get((str(typ)), raise_if_missing=False) + if op is None: + raise NotImplementedError(f"{typ} is not supported.") + return op diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/__init__.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/boolean_equals.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/boolean_equals.py new file mode 100644 index 0000000000000..0c983fc8b6995 --- /dev/null +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/boolean_equals.py @@ -0,0 +1,43 @@ +from typing import Any + +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( + ComparisonOperatorType, +) +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.operator import ( + Operator, +) +from localstack.services.stepfunctions.asl.eval.environment import Environment +from localstack.services.stepfunctions.asl.utils.json_path import JSONPathUtils + + +class BooleanEquals(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.BooleanEquals) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + res = False + if isinstance(variable, bool): + res = variable is value + env.stack.append(res) + + +class BooleanEqualsPath(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.BooleanEqualsPath) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + comp_value: bool = JSONPathUtils.extract_json(value, env.inp) + if not isinstance(comp_value, bool): + raise TypeError(f"Expected type bool, but got '{comp_value}' from path '{value}'.") + + variable = env.stack.pop() + + res = False + if isinstance(variable, bool): + res = bool(variable) is comp_value + env.stack.append(res) diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py new file mode 100644 index 0000000000000..7d47fdc3ae983 --- /dev/null +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py @@ -0,0 +1,106 @@ +import datetime +import logging +from typing import Any, Final, Optional + +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( + ComparisonOperatorType, +) +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.operator.operator import ( + Operator, +) +from localstack.services.stepfunctions.asl.component.state.state_wait.variable import NoSuchVariable +from localstack.services.stepfunctions.asl.eval.environment import Environment + +LOG = logging.getLogger(__name__) + + +class IsBoolean(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsBoolean) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + res = isinstance(variable, bool) is value + env.stack.append(res) + + +class IsNull(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsNull) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + is_null = variable is None and not isinstance(variable, NoSuchVariable) + res = is_null is value + env.stack.append(res) + + +class IsNumeric(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsNumeric) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + res = (isinstance(variable, (int, float)) and not isinstance(variable, bool)) is value + env.stack.append(res) + + +class IsPresent(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsPresent) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + res = not isinstance(variable, NoSuchVariable) is value + env.stack.append(res) + + +class IsString(Operator): + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsString) + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + res = isinstance(variable, str) is value + env.stack.append(res) + + +class IsTimestamp(Operator): + # Timestamps are strings which MUST conform to the RFC3339 profile of ISO 8601, with the further restrictions that + # an uppercase "T" character MUST be used to separate date and time, and an uppercase "Z" character MUST be + # present in the absence of a numeric time zone offset, for example "2016-03-14T01:59:00Z". + TIMESTAMP_FORMAT: Final[str] = "%Y-%m-%dT%H:%M:%SZ" + + @staticmethod + def impl_name() -> str: + return str(ComparisonOperatorType.IsTimestamp) + + @staticmethod + def string_to_timestamp(string: str) -> Optional[datetime.datetime]: + try: + return datetime.datetime.strptime(string, IsTimestamp.TIMESTAMP_FORMAT) + except Exception: + return None + + @staticmethod + def is_timestamp(inp: Any) -> bool: + return isinstance(inp, str) and IsTimestamp.string_to_timestamp(inp) is not None + + @staticmethod + def eval(env: Environment, value: Any) -> None: + variable = env.stack.pop() + LOG.warning( + f"State Choice's 'IsTimestamp' operator is not fully supported for input '{variable}' and target '{value}'." + ) + res = IsTimestamp.is_timestamp(variable) is value + env.stack.append(res) diff --git a/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/operator.py b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/operator.py new file mode 100644 index 0000000000000..56c4867fdc6dd --- /dev/null +++ b/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/operator.py @@ -0,0 +1,12 @@ +import abc +from typing import Any + +from localstack.services.stepfunctions.asl.eval.environment import Environment +from localstack.utils.objects import SubtypesInstanceManager + + +class Operator(abc.ABC, SubtypesInstanceManager): + @staticmethod + @abc.abstractmethod + def eval(env: Environment, value: Any) -> None: + pass diff --git a/localstack/services/stepfunctions/asl/component/state/state_wait/variable.py b/localstack/services/stepfunctions/asl/component/state/state_wait/variable.py index faf2b99b32b41..8066669915c4b 100644 --- a/localstack/services/stepfunctions/asl/component/state/state_wait/variable.py +++ b/localstack/services/stepfunctions/asl/component/state/state_wait/variable.py @@ -1,11 +1,10 @@ from typing import Final -from jsonpath_ng import parse - from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_stmt import ( ComparisonStmt, ) from localstack.services.stepfunctions.asl.eval.environment import Environment +from localstack.services.stepfunctions.asl.utils.json_path import JSONPathUtils class NoSuchVariable: @@ -18,9 +17,8 @@ def __init__(self, value: str): self.value: Final[str] = value def _eval_body(self, env: Environment) -> None: - variable_expr = parse(self.value) try: - value = variable_expr.find(env.inp) + value = JSONPathUtils.extract_json(self.value, env.inp) except Exception as ex: value = NoSuchVariable(f"{self.value}, {ex}") env.stack.append(value) diff --git a/localstack/services/stepfunctions/asl/parse/preprocessor.py b/localstack/services/stepfunctions/asl/parse/preprocessor.py index b1dd10ddb6114..c92a37f0d0470 100644 --- a/localstack/services/stepfunctions/asl/parse/preprocessor.py +++ b/localstack/services/stepfunctions/asl/parse/preprocessor.py @@ -111,6 +111,9 @@ from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_func import ( ComparisonFunc, ) +from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( + ComparisonOperatorType, +) from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_props import ( ComparisonProps, ) @@ -325,17 +328,15 @@ def visitVariable_decl(self, ctx: ASLParser.Variable_declContext) -> Variable: value: str = self._inner_string_of(parse_tree=ctx.keyword_or_string()) return Variable(value=value) - def visitComparison_op( - self, ctx: ASLParser.Comparison_opContext - ) -> ComparisonFunc.ComparisonOperator: + def visitComparison_op(self, ctx: ASLParser.Comparison_opContext) -> ComparisonOperatorType: try: operator_type: int = ctx.children[0].symbol.type - return ComparisonFunc.ComparisonOperator(operator_type) + return ComparisonOperatorType(operator_type) except Exception: raise ValueError(f"Could not derive ComparisonOperator from context '{ctx.getText()}'.") def visitComparison_func(self, ctx: ASLParser.Comparison_funcContext) -> ComparisonFunc: - comparison_op: ComparisonFunc.ComparisonOperator = self.visit(ctx.comparison_op()) + comparison_op: ComparisonOperatorType = self.visit(ctx.comparison_op()) json_decl = ctx.json_value_decl() json_str: str = json_decl.getText() diff --git a/setup.cfg b/setup.cfg index 47ec3177db401..a07782861276a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,7 @@ runtime = hypercorn==0.14.2 json5==0.9.11 jsonpatch>=1.24,<2.0 + jsonpath-ng>=1.5.3 jsonpath-rw>=1.4.0,<2.0.0 moto-ext[all]==4.1.5.post2 opensearch-py==2.1.1 diff --git a/tests/integration/stepfunctions/templates/choiceoperators/__init__.py b/tests/integration/stepfunctions/templates/choiceoperators/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/integration/stepfunctions/templates/choiceoperators/choice_operators_templates.py b/tests/integration/stepfunctions/templates/choiceoperators/choice_operators_templates.py new file mode 100644 index 0000000000000..0944dc275a89b --- /dev/null +++ b/tests/integration/stepfunctions/templates/choiceoperators/choice_operators_templates.py @@ -0,0 +1,16 @@ +import os +from typing import Final + +from tests.integration.stepfunctions.templates.template_loader import TemplateLoader + +_THIS_FOLDER: Final[str] = os.path.dirname(os.path.realpath(__file__)) + + +class ChoiceOperatorTemplate(TemplateLoader): + COMPARISON_OPERATOR_PLACEHOLDER = "%ComparisonOperatorType%" + VARIABLE_KEY: Final[str] = "Variable" + VALUE_KEY: Final[str] = "Value" + VALUE_PLACEHOLDER = '"$.Value"' + TEST_RESULT_KEY: Final[str] = "TestResult" + + BASE_TEMPLATE: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/template.json5") diff --git a/tests/integration/stepfunctions/templates/choiceoperators/statemachines/template.json5 b/tests/integration/stepfunctions/templates/choiceoperators/statemachines/template.json5 new file mode 100644 index 0000000000000..bfd9aa24e2989 --- /dev/null +++ b/tests/integration/stepfunctions/templates/choiceoperators/statemachines/template.json5 @@ -0,0 +1,31 @@ +{ + "Comment": "BASE_TEMPLATE", + "StartAt": "CheckValues", + "States": { + "CheckValues": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.Variable", + "%ComparisonOperatorType%": "$.Value", + "Next": "ConditionTrue" + }, + ], + "Default": "ConditionFalse", + }, + "ConditionTrue": { + "Type": "Pass", + "Result": { + "TestResult.$": "ConditionTrue" + }, + "End": true, + }, + "ConditionFalse": { + "Type": "Pass", + "Result": { + "TestResult.$": "ConditionFalse" + }, + "End": true, + } + }, +} diff --git a/tests/integration/stepfunctions/v2/choice_operators/__init__.py b/tests/integration/stepfunctions/v2/choice_operators/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.py b/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.py new file mode 100644 index 0000000000000..92ff9b6f9a809 --- /dev/null +++ b/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.py @@ -0,0 +1,52 @@ +import pytest + +from tests.integration.stepfunctions.utils import is_old_provider +from tests.integration.stepfunctions.v2.choice_operators.utils import ( + TYPE_COMPARISONS, + create_and_test_comparison_function, +) + +pytestmark = pytest.mark.skipif( + condition=is_old_provider(), reason="Test suite for v2 provider only." +) + + +# TODO: test for validation errors, and boundary testing. + + +@pytest.mark.skip_snapshot_verify( + paths=["$..loggingConfiguration", "$..tracingConfiguration", "$..previousEventId"] +) +class TestBooleanEquals: + def test_boolean_equals( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "BooleanEquals", + comparisons=TYPE_COMPARISONS, + ) + + def test_boolean_equals_path( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "BooleanEqualsPath", + comparisons=TYPE_COMPARISONS, + add_literal_value=False, + ) diff --git a/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.snapshot.json b/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.snapshot.json new file mode 100644 index 0000000000000..49e4ead6e3bcb --- /dev/null +++ b/tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.snapshot.json @@ -0,0 +1,680 @@ +{ + "tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals": { + "recorded-date": "27-03-2023, 22:08:58", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals_path": { + "recorded-date": "27-03-2023, 22:09:35", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + } + ] + } + } +} diff --git a/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py b/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py new file mode 100644 index 0000000000000..2c12e61d3d8d0 --- /dev/null +++ b/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py @@ -0,0 +1,116 @@ +import pytest + +from tests.integration.stepfunctions.utils import is_old_provider +from tests.integration.stepfunctions.v2.choice_operators.utils import ( + TYPE_COMPARISONS, + create_and_test_comparison_function, +) + +pytestmark = pytest.mark.skipif( + condition=is_old_provider(), reason="Test suite for v2 provider only." +) + + +# TODO: test for validation errors, and boundary testing. + + +@pytest.mark.skip_snapshot_verify( + paths=["$..loggingConfiguration", "$..tracingConfiguration", "$..previousEventId"] +) +class TestIsOperators: + def test_is_boolean( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsBoolean", + comparisons=TYPE_COMPARISONS, + ) + + def test_is_null( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsNull", + comparisons=TYPE_COMPARISONS, + ) + + def test_is_numeric( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsNumeric", + comparisons=TYPE_COMPARISONS, + ) + + def test_is_present( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsPresent", + comparisons=TYPE_COMPARISONS, + ) + + def test_is_string( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsString", + comparisons=TYPE_COMPARISONS, + ) + + @pytest.mark.skip(reason="TODO: investigate IsTimestamp behaviour.") + def test_is_timestamp( + self, + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + ): + create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + "IsTimestamp", + comparisons=TYPE_COMPARISONS, + ) diff --git a/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.snapshot.json b/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.snapshot.json new file mode 100644 index 0000000000000..83144844543da --- /dev/null +++ b/tests/integration/stepfunctions/v2/choice_operators/test_is_operators.snapshot.json @@ -0,0 +1,2018 @@ +{ + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_boolean": { + "recorded-date": "27-03-2023, 22:10:44", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_null": { + "recorded-date": "27-03-2023, 22:11:23", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_numeric": { + "recorded-date": "27-03-2023, 22:12:04", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_present": { + "recorded-date": "27-03-2023, 22:13:16", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_string": { + "recorded-date": "27-03-2023, 22:14:27", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + } + ] + } + }, + "tests/integration/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_timestamp": { + "recorded-date": "03-03-2023, 18:46:14", + "recorded-content": { + "cases": [ + { + "input": { + "Variable": null, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": null, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 0.0, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": 1.1, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": " ", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": " ", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "HelloWorld", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2012-10-09T19:00:55", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": true + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": "date", + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": "2023-02-24", + "Value": false + }, + "output": { + "TestResult.$": "ConditionTrue" + } + }, + { + "input": { + "Variable": [], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": [ + "" + ], + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": {}, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": { + "A": 0 + }, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": true, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": true + }, + "output": { + "TestResult.$": "ConditionFalse" + } + }, + { + "input": { + "Variable": false, + "Value": false + }, + "output": { + "TestResult.$": "ConditionFalse" + } + } + ] + } + } +} diff --git a/tests/integration/stepfunctions/v2/choice_operators/utils.py b/tests/integration/stepfunctions/v2/choice_operators/utils.py new file mode 100644 index 0000000000000..64a8d8b4a16ae --- /dev/null +++ b/tests/integration/stepfunctions/v2/choice_operators/utils.py @@ -0,0 +1,100 @@ +import json +from typing import Any, Final + +from localstack.services.stepfunctions.asl.utils.json_path import JSONPathUtils +from localstack.testing.snapshots.transformer import RegexTransformer +from localstack.utils.strings import short_uid +from tests.integration.stepfunctions.templates.choiceoperators.choice_operators_templates import ( + ChoiceOperatorTemplate as COT, +) +from tests.integration.stepfunctions.utils import await_execution_success + +TYPE_COMPARISONS: Final[list[tuple[Any, bool]]] = [ + (None, True), # 0 + (None, False), # 1 + (0, True), # 2 + (0, False), # 3 + (0.0, True), # 4 + (0.0, False), # 5 + (1, True), # 6 + (1, False), # 7 + (1.1, True), # 8 + (1.1, False), # 9 + ("", True), # 10 + ("", False), # 11 + (" ", True), # 12 + (" ", False), # 13 + ("HelloWorld", True), # 14 + ("HelloWorld", False), # 15 + ("2012-10-09T19:00:55", True), # 16 + ("2012-10-09T19:00:55", False), # 17 + ("2012-10-09T19:00:55Z", True), # 18 + ("2012-10-09T19:00:55Z", False), # 19 + ("2012-10-09T19:00:55+01:00", True), # 20 + ("2012-10-09T19:00:55+01:00", False), # 21 + ("2023-02-24", True), # 22 + ("2023-02-24", False), # 23 + ([], True), # 24 + ([], False), # 25 + ([""], True), # 26 + ([""], False), # 27 + ({}, True), # 28 + ({}, False), # 29 + ({"A": 0}, True), # 30 + ({"A": 0}, False), # 31 + (True, True), # 32 + (False, True), # 33 + (False, True), # 34 + (False, False), # 35 +] + + +def create_and_test_comparison_function( + stepfunctions_client, + create_iam_role_for_sfn, + create_state_machine, + snapshot, + comparison_func_name: str, + comparisons: list[tuple[Any, Any]], + add_literal_value: bool = True, +): + snf_role_arn = create_iam_role_for_sfn() + snapshot.add_transformer(RegexTransformer(snf_role_arn, "snf_role_arn")) + + base_sm_name: str = f"statemachine_{short_uid()}" + + definition = COT.load_sfn_template(COT.BASE_TEMPLATE) + definition_str = json.dumps(definition) + definition_str = definition_str.replace( + COT.COMPARISON_OPERATOR_PLACEHOLDER, comparison_func_name + ) + + input_output_cases: list[dict[str, Any]] = list() + for i, (variable, value) in enumerate(comparisons): + exec_input = json.dumps({COT.VARIABLE_KEY: variable, COT.VALUE_KEY: value}) + + if add_literal_value: + new_definition_str = definition_str.replace(COT.VALUE_PLACEHOLDER, json.dumps(value)) + else: + new_definition_str = definition_str + + creation_resp = create_state_machine( + name=f"{base_sm_name}_{i}", definition=new_definition_str, roleArn=snf_role_arn + ) + state_machine_arn = creation_resp["stateMachineArn"] + + exec_resp = stepfunctions_client.start_execution( + stateMachineArn=state_machine_arn, input=exec_input + ) + execution_arn = exec_resp["executionArn"] + + await_execution_success( + stepfunctions_client=stepfunctions_client, execution_arn=execution_arn + ) + + exec_hist_resp = stepfunctions_client.get_execution_history(executionArn=execution_arn) + output = JSONPathUtils.extract_json( + "$.events[*].executionSucceededEventDetails.output", exec_hist_resp + ) + input_output_cases.append({"input": exec_input, "output": output}) + snapshot.match("cases", input_output_cases)