localstack-core 4.10.1.dev7__py3-none-any.whl → 4.11.2.dev14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

Files changed (152) hide show
  1. localstack/aws/api/acm/__init__.py +122 -122
  2. localstack/aws/api/apigateway/__init__.py +604 -561
  3. localstack/aws/api/cloudcontrol/__init__.py +63 -63
  4. localstack/aws/api/cloudformation/__init__.py +1201 -969
  5. localstack/aws/api/cloudwatch/__init__.py +375 -375
  6. localstack/aws/api/config/__init__.py +784 -786
  7. localstack/aws/api/dynamodb/__init__.py +753 -759
  8. localstack/aws/api/dynamodbstreams/__init__.py +74 -74
  9. localstack/aws/api/ec2/__init__.py +10062 -8826
  10. localstack/aws/api/es/__init__.py +453 -453
  11. localstack/aws/api/events/__init__.py +552 -552
  12. localstack/aws/api/firehose/__init__.py +541 -543
  13. localstack/aws/api/iam/__init__.py +866 -572
  14. localstack/aws/api/kinesis/__init__.py +235 -147
  15. localstack/aws/api/kms/__init__.py +341 -336
  16. localstack/aws/api/lambda_/__init__.py +974 -621
  17. localstack/aws/api/logs/__init__.py +988 -675
  18. localstack/aws/api/opensearch/__init__.py +903 -785
  19. localstack/aws/api/pipes/__init__.py +336 -336
  20. localstack/aws/api/redshift/__init__.py +1257 -1166
  21. localstack/aws/api/resource_groups/__init__.py +175 -175
  22. localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
  23. localstack/aws/api/route53/__init__.py +296 -254
  24. localstack/aws/api/route53resolver/__init__.py +397 -396
  25. localstack/aws/api/s3/__init__.py +1412 -1349
  26. localstack/aws/api/s3control/__init__.py +594 -594
  27. localstack/aws/api/scheduler/__init__.py +118 -118
  28. localstack/aws/api/secretsmanager/__init__.py +221 -216
  29. localstack/aws/api/ses/__init__.py +227 -227
  30. localstack/aws/api/sns/__init__.py +115 -115
  31. localstack/aws/api/sqs/__init__.py +100 -100
  32. localstack/aws/api/ssm/__init__.py +1977 -1971
  33. localstack/aws/api/stepfunctions/__init__.py +375 -333
  34. localstack/aws/api/sts/__init__.py +142 -66
  35. localstack/aws/api/support/__init__.py +112 -112
  36. localstack/aws/api/swf/__init__.py +378 -386
  37. localstack/aws/api/transcribe/__init__.py +425 -425
  38. localstack/aws/handlers/logging.py +8 -4
  39. localstack/aws/handlers/service.py +22 -3
  40. localstack/aws/protocol/parser.py +1 -1
  41. localstack/aws/protocol/serializer.py +1 -1
  42. localstack/aws/scaffold.py +15 -17
  43. localstack/cli/localstack.py +6 -1
  44. localstack/deprecations.py +0 -6
  45. localstack/dev/kubernetes/__main__.py +38 -3
  46. localstack/services/acm/provider.py +4 -0
  47. localstack/services/apigateway/helpers.py +5 -9
  48. localstack/services/apigateway/legacy/provider.py +60 -24
  49. localstack/services/apigateway/patches.py +0 -9
  50. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  51. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
  52. localstack/services/cloudformation/provider.py +2 -2
  53. localstack/services/cloudformation/v2/provider.py +6 -6
  54. localstack/services/cloudwatch/provider.py +10 -3
  55. localstack/services/cloudwatch/provider_v2.py +6 -3
  56. localstack/services/configservice/provider.py +5 -1
  57. localstack/services/dynamodb/provider.py +1 -0
  58. localstack/services/dynamodb/v2/provider.py +1 -0
  59. localstack/services/dynamodbstreams/provider.py +6 -0
  60. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  61. localstack/services/ec2/provider.py +6 -0
  62. localstack/services/es/provider.py +6 -0
  63. localstack/services/events/provider.py +4 -0
  64. localstack/services/events/v1/provider.py +9 -0
  65. localstack/services/firehose/provider.py +5 -0
  66. localstack/services/iam/provider.py +4 -0
  67. localstack/services/kinesis/packages.py +1 -1
  68. localstack/services/kms/models.py +44 -24
  69. localstack/services/kms/provider.py +97 -16
  70. localstack/services/lambda_/api_utils.py +40 -21
  71. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  72. localstack/services/lambda_/invocation/assignment.py +4 -1
  73. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  74. localstack/services/lambda_/invocation/lambda_models.py +27 -2
  75. localstack/services/lambda_/invocation/lambda_service.py +51 -3
  76. localstack/services/lambda_/invocation/models.py +9 -1
  77. localstack/services/lambda_/invocation/version_manager.py +18 -3
  78. localstack/services/lambda_/packages.py +1 -1
  79. localstack/services/lambda_/provider.py +240 -96
  80. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  81. localstack/services/lambda_/runtimes.py +10 -3
  82. localstack/services/logs/provider.py +45 -19
  83. localstack/services/opensearch/provider.py +53 -3
  84. localstack/services/resource_groups/provider.py +5 -1
  85. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  86. localstack/services/s3/provider.py +29 -16
  87. localstack/services/s3/utils.py +35 -14
  88. localstack/services/s3control/provider.py +101 -2
  89. localstack/services/s3control/validation.py +50 -0
  90. localstack/services/sns/constants.py +3 -1
  91. localstack/services/sns/publisher.py +15 -6
  92. localstack/services/sns/v2/models.py +30 -1
  93. localstack/services/sns/v2/provider.py +794 -31
  94. localstack/services/sns/v2/utils.py +20 -0
  95. localstack/services/sqs/models.py +37 -10
  96. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  101. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  102. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  103. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  107. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  108. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  109. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  110. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  111. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  112. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  113. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  114. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  115. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  116. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  117. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
  118. localstack/services/stepfunctions/backend/execution.py +6 -6
  119. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  120. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  121. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  122. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  123. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  124. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  125. localstack/services/stepfunctions/provider.py +78 -27
  126. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  127. localstack/testing/pytest/fixtures.py +28 -0
  128. localstack/testing/snapshots/transformer_utility.py +7 -0
  129. localstack/testing/testselection/matching.py +0 -1
  130. localstack/utils/analytics/publisher.py +37 -155
  131. localstack/utils/analytics/service_request_aggregator.py +6 -4
  132. localstack/utils/aws/arns.py +7 -0
  133. localstack/utils/aws/client_types.py +0 -8
  134. localstack/utils/batching.py +258 -0
  135. localstack/utils/catalog/catalog_loader.py +111 -3
  136. localstack/utils/collections.py +23 -11
  137. localstack/utils/crypto.py +109 -0
  138. localstack/version.py +2 -2
  139. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
  140. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
  141. localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
  142. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  143. localstack/utils/batch_policy.py +0 -124
  144. localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
  145. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  146. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
  147. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
  148. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
  149. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
  150. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
  151. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
  152. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,19 @@
1
1
  import enum
2
2
  from typing import Final
3
3
 
4
+ from antlr4.tree.Tree import ParseTree
5
+
4
6
  from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser
5
- from localstack.services.stepfunctions.asl.component.common.parargs import Parameters
7
+ from localstack.services.stepfunctions.asl.antlt4utils.antlr4utils import (
8
+ is_production,
9
+ )
10
+ from localstack.services.stepfunctions.asl.component.common.parargs import (
11
+ ArgumentsJSONataTemplateValueObject,
12
+ ArgumentsStringJSONata,
13
+ Parameters,
14
+ )
6
15
  from localstack.services.stepfunctions.asl.component.common.path.input_path import InputPath
16
+ from localstack.services.stepfunctions.asl.component.common.path.items_path import ItemsPath
7
17
  from localstack.services.stepfunctions.asl.component.common.path.result_path import ResultPath
8
18
  from localstack.services.stepfunctions.asl.component.common.query_language import QueryLanguage
9
19
  from localstack.services.stepfunctions.asl.component.common.result_selector import ResultSelector
@@ -11,13 +21,43 @@ from localstack.services.stepfunctions.asl.component.state.state import CommonSt
11
21
  from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import (
12
22
  StateChoice,
13
23
  )
14
- from localstack.services.stepfunctions.asl.component.state.state_execution.execute_state import (
15
- ExecutionState,
24
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.max_concurrency import (
25
+ MaxConcurrency,
26
+ MaxConcurrencyJSONata,
27
+ MaxConcurrencyPath,
28
+ )
29
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import (
30
+ StateMap,
16
31
  )
32
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.tolerated_failure import (
33
+ ToleratedFailureCountInt,
34
+ ToleratedFailureCountPath,
35
+ ToleratedFailureCountStringJSONata,
36
+ ToleratedFailurePercentage,
37
+ ToleratedFailurePercentagePath,
38
+ ToleratedFailurePercentageStringJSONata,
39
+ )
40
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import (
41
+ StateTask,
42
+ )
43
+ from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
17
44
  from localstack.services.stepfunctions.asl.component.state.state_pass.result import Result
45
+ from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
46
+ from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
47
+ StateSucceed,
48
+ )
18
49
  from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
19
50
  TestStateProgram,
20
51
  )
52
+ from localstack.services.stepfunctions.asl.component.test_state.state.common import (
53
+ MockedCommonState,
54
+ )
55
+ from localstack.services.stepfunctions.asl.component.test_state.state.map import (
56
+ MockedStateMap,
57
+ )
58
+ from localstack.services.stepfunctions.asl.component.test_state.state.task import (
59
+ MockedStateTask,
60
+ )
21
61
  from localstack.services.stepfunctions.asl.component.test_state.state.test_state_state_props import (
22
62
  TestStateStateProps,
23
63
  )
@@ -30,63 +70,96 @@ class InspectionDataKey(enum.Enum):
30
70
  INPUT = "input"
31
71
  AFTER_INPUT_PATH = "afterInputPath"
32
72
  AFTER_PARAMETERS = "afterParameters"
73
+ AFTER_ARGUMENTS = "afterArguments"
33
74
  RESULT = "result"
34
75
  AFTER_RESULT_SELECTOR = "afterResultSelector"
35
76
  AFTER_RESULT_PATH = "afterResultPath"
77
+ AFTER_ITEMS_PATH = "afterItemsPath"
36
78
  REQUEST = "request"
37
79
  RESPONSE = "response"
38
80
 
39
-
40
- def _decorated_updated_choice_inspection_data(method):
41
- def wrapper(env: TestStateEnvironment, *args, **kwargs):
42
- method(env, *args, **kwargs)
43
- env.set_choice_selected(env.next_state_name)
44
-
45
- return wrapper
81
+ MAX_CONCURRENCY = "maxConcurrency"
82
+ TOLERATED_FAILURE_COUNT = "toleratedFailureCount"
83
+ TOLERATED_FAILURE_PERCENTAGE = "toleratedFailurePercentage"
46
84
 
47
85
 
48
86
  def _decorated_updates_inspection_data(method, inspection_data_key: InspectionDataKey):
49
87
  def wrapper(env: TestStateEnvironment, *args, **kwargs):
50
88
  method(env, *args, **kwargs)
51
- result = to_json_str(env.stack[-1])
89
+ result = env.stack[-1]
90
+ if not isinstance(result, (int, float)):
91
+ result = to_json_str(result)
52
92
  # We know that the enum value used here corresponds to a supported inspection data field by design.
53
93
  env.inspection_data[inspection_data_key.value] = result # noqa
54
94
 
55
95
  return wrapper
56
96
 
57
97
 
58
- def _decorate_state_field(state_field: CommonStateField) -> None:
59
- if isinstance(state_field, ExecutionState):
60
- state_field._eval_execution = _decorated_updates_inspection_data(
61
- # As part of the decoration process, we intentionally access this protected member
62
- # to facilitate the decorator's functionality.
63
- method=state_field._eval_execution, # noqa
64
- inspection_data_key=InspectionDataKey.RESULT,
65
- )
66
- elif isinstance(state_field, StateChoice):
67
- state_field._eval_body = _decorated_updated_choice_inspection_data(
68
- # As part of the decoration process, we intentionally access this protected member
69
- # to facilitate the decorator's functionality.
70
- method=state_field._eval_body # noqa
71
- )
98
+ def _decorate_state_field(state_field: CommonStateField, is_single_state: bool = False) -> None:
99
+ if isinstance(state_field, StateMap):
100
+ MockedStateMap.wrap(state_field, is_single_state)
101
+ elif isinstance(state_field, StateTask):
102
+ MockedStateTask.wrap(state_field, is_single_state)
103
+ elif isinstance(state_field, (StateChoice, StatePass, StateFail, StateSucceed)):
104
+ MockedCommonState.wrap(state_field, is_single_state)
105
+
106
+
107
+ def find_state(state_name: str, states: dict[str, CommonStateField]) -> CommonStateField | None:
108
+ if state_name in states:
109
+ return states[state_name]
110
+
111
+ for state in states.values():
112
+ if isinstance(state, StateMap):
113
+ found_state = find_state(state_name, state.iteration_component._states.states)
114
+ if found_state:
115
+ return found_state
72
116
 
73
117
 
74
118
  class TestStatePreprocessor(Preprocessor):
75
- STATE_NAME: Final[str] = "TestState"
119
+ STATE_NAME: Final[str] = "StateName"
120
+ _state_name_stack: list[str] = []
121
+
122
+ def to_test_state_program(
123
+ self, tree: ParseTree, state_name: str | None = None
124
+ ) -> TestStateProgram:
125
+ if is_production(tree, ASLParser.RULE_state_machine):
126
+ # full definition passed in
127
+ program = self.visitState_machine(ctx=tree)
128
+ state_field = find_state(state_name, program.states.states)
129
+ _decorate_state_field(state_field, False)
130
+ return TestStateProgram(state_field)
131
+
132
+ if is_production(tree, ASLParser.RULE_state_decl_body):
133
+ # single state case
134
+ state_props = self.visitState_decl_body(ctx=tree)
135
+ state_field = self._common_state_field_of(state_props=state_props)
136
+ _decorate_state_field(state_field, True)
137
+ return TestStateProgram(state_field)
76
138
 
77
- def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> TestStateProgram:
139
+ return super().visit(tree)
140
+
141
+ def visitState_decl(self, ctx: ASLParser.State_declContext) -> CommonStateField:
142
+ # if we are parsing a full state machine, we need to record the state_name prior to stepping
143
+ # into the state body definition.
144
+ state_name = self._inner_string_of(parser_rule_context=ctx.string_literal())
145
+ self._state_name_stack.append(state_name)
146
+ state_props: TestStateStateProps = self.visit(ctx.state_decl_body())
147
+ state_field = self._common_state_field_of(state_props=state_props)
148
+ return state_field
149
+
150
+ def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> TestStateStateProps:
78
151
  self._open_query_language_scope(ctx)
79
152
  state_props = TestStateStateProps()
80
- state_props.name = self.STATE_NAME
153
+ state_props.name = (
154
+ self._state_name_stack.pop(-1) if self._state_name_stack else self.STATE_NAME
155
+ )
81
156
  for child in ctx.children:
82
157
  cmp = self.visit(child)
83
158
  state_props.add(cmp)
84
- state_field = self._common_state_field_of(state_props=state_props)
85
159
  if state_props.get(QueryLanguage) is None:
86
160
  state_props.add(self._get_current_query_language())
87
- _decorate_state_field(state_field)
88
161
  self._close_query_language_scope()
89
- return TestStateProgram(state_field)
162
+ return state_props
90
163
 
91
164
  def visitInput_path_decl(self, ctx: ASLParser.Input_path_declContext) -> InputPath:
92
165
  input_path: InputPath = super().visitInput_path_decl(ctx=ctx)
@@ -129,3 +202,121 @@ class TestStatePreprocessor(Preprocessor):
129
202
  inspection_data_key=InspectionDataKey.RESULT, # noqa
130
203
  )
131
204
  return result
205
+
206
+ def visitMax_concurrency_int(self, ctx: ASLParser.Max_concurrency_intContext) -> MaxConcurrency:
207
+ max_concurrency: MaxConcurrency = super().visitMax_concurrency_int(ctx)
208
+ max_concurrency._eval_body = _decorated_updates_inspection_data(
209
+ method=max_concurrency._eval_body,
210
+ inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
211
+ )
212
+ return max_concurrency
213
+
214
+ def visitMax_concurrency_jsonata(
215
+ self, ctx: ASLParser.Max_concurrency_jsonataContext
216
+ ) -> MaxConcurrencyJSONata:
217
+ max_concurrency_jsonata: MaxConcurrencyJSONata = super().visitMax_concurrency_jsonata(ctx)
218
+ max_concurrency_jsonata._eval_body = _decorated_updates_inspection_data(
219
+ method=max_concurrency_jsonata._eval_body,
220
+ inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
221
+ )
222
+ return max_concurrency_jsonata
223
+
224
+ def visitMax_concurrency_path(
225
+ self, ctx: ASLParser.Max_concurrency_declContext
226
+ ) -> MaxConcurrencyPath:
227
+ max_concurrency_path: MaxConcurrencyPath = super().visitMax_concurrency_path(ctx)
228
+ max_concurrency_path._eval_body = _decorated_updates_inspection_data(
229
+ method=max_concurrency_path._eval_body,
230
+ inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa
231
+ )
232
+ return max_concurrency_path
233
+
234
+ def visitTolerated_failure_count_int(self, ctx) -> ToleratedFailureCountInt:
235
+ tolerated_failure_count: ToleratedFailureCountInt = (
236
+ super().visitTolerated_failure_count_int(ctx)
237
+ )
238
+ tolerated_failure_count._eval_body = _decorated_updates_inspection_data(
239
+ method=tolerated_failure_count._eval_body,
240
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
241
+ )
242
+ return tolerated_failure_count
243
+
244
+ def visitTolerated_failure_count_path(self, ctx) -> ToleratedFailureCountPath:
245
+ tolerated_failure_count_path: ToleratedFailureCountPath = (
246
+ super().visitTolerated_failure_count_path(ctx)
247
+ )
248
+ tolerated_failure_count_path._eval_body = _decorated_updates_inspection_data(
249
+ method=tolerated_failure_count_path._eval_body,
250
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
251
+ )
252
+ return tolerated_failure_count_path
253
+
254
+ def visitTolerated_failure_count_string_jsonata(
255
+ self, ctx
256
+ ) -> ToleratedFailureCountStringJSONata:
257
+ tolerated_failure_count_jsonata: ToleratedFailureCountStringJSONata = (
258
+ super().visitTolerated_failure_count_string_jsonata(ctx)
259
+ )
260
+ tolerated_failure_count_jsonata._eval_body = _decorated_updates_inspection_data(
261
+ method=tolerated_failure_count_jsonata._eval_body,
262
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT,
263
+ )
264
+ return tolerated_failure_count_jsonata
265
+
266
+ def visitTolerated_failure_percentage_number(self, ctx) -> ToleratedFailurePercentage:
267
+ tolerated_failure_percentage: ToleratedFailurePercentage = (
268
+ super().visitTolerated_failure_percentage_number(ctx)
269
+ )
270
+ tolerated_failure_percentage._eval_body = _decorated_updates_inspection_data(
271
+ method=tolerated_failure_percentage._eval_body,
272
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
273
+ )
274
+ return tolerated_failure_percentage
275
+
276
+ def visitTolerated_failure_percentage_path(self, ctx) -> ToleratedFailurePercentagePath:
277
+ tolerated_failure_percentage_path: ToleratedFailurePercentagePath = (
278
+ super().visitTolerated_failure_percentage_path(ctx)
279
+ )
280
+ tolerated_failure_percentage_path._eval_body = _decorated_updates_inspection_data(
281
+ method=tolerated_failure_percentage_path._eval_body,
282
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
283
+ )
284
+ return tolerated_failure_percentage_path
285
+
286
+ def visitTolerated_failure_percentage_string_jsonata(
287
+ self, ctx
288
+ ) -> ToleratedFailurePercentageStringJSONata:
289
+ tolerated_failure_percentage_jsonata: ToleratedFailurePercentageStringJSONata = (
290
+ super().visitTolerated_failure_percentage_string_jsonata(ctx)
291
+ )
292
+ tolerated_failure_percentage_jsonata._eval_body = _decorated_updates_inspection_data(
293
+ method=tolerated_failure_percentage_jsonata._eval_body,
294
+ inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE,
295
+ )
296
+ return tolerated_failure_percentage_jsonata
297
+
298
+ def visitItems_path_decl(self, ctx) -> ItemsPath:
299
+ items_path: ItemsPath = super().visitItems_path_decl(ctx)
300
+ items_path._eval_body = _decorated_updates_inspection_data(
301
+ method=items_path._eval_body,
302
+ inspection_data_key=InspectionDataKey.AFTER_ITEMS_PATH,
303
+ )
304
+ return items_path
305
+
306
+ def visitArguments_string_jsonata(self, ctx):
307
+ arguments: ArgumentsStringJSONata = super().visitArguments_string_jsonata(ctx)
308
+ arguments._eval_body = _decorated_updates_inspection_data(
309
+ method=arguments._eval_body,
310
+ inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS,
311
+ )
312
+ return arguments
313
+
314
+ def visitArguments_jsonata_template_value_object(self, ctx):
315
+ arguments: ArgumentsJSONataTemplateValueObject = (
316
+ super().visitArguments_jsonata_template_value_object(ctx)
317
+ )
318
+ arguments._eval_body = _decorated_updates_inspection_data(
319
+ method=arguments._eval_body,
320
+ inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS,
321
+ )
322
+ return arguments
@@ -1,12 +1,33 @@
1
- from typing import Final
1
+ import json
2
+ from typing import Any, Final
2
3
 
4
+ # Botocore shape classes to drive validation
5
+ from botocore.model import (
6
+ ListShape,
7
+ MapShape,
8
+ Shape,
9
+ StringShape,
10
+ StructureShape,
11
+ )
12
+
13
+ from localstack.aws.api.stepfunctions import (
14
+ Definition,
15
+ MockInput,
16
+ MockResponseValidationMode,
17
+ StateName,
18
+ ValidationException,
19
+ )
3
20
  from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser
4
- from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import (
5
- ActivityResource,
6
- Resource,
7
- ServiceResource,
21
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import (
22
+ StateTaskService,
23
+ )
24
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service_api_gateway import (
25
+ StateTaskServiceApiGateway,
8
26
  )
9
27
  from localstack.services.stepfunctions.asl.component.state.state_type import StateType
28
+ from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
29
+ TestStateProgram,
30
+ )
10
31
  from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
11
32
  TestStateAmazonStateLanguageParser,
12
33
  )
@@ -14,6 +35,11 @@ from localstack.services.stepfunctions.asl.static_analyser.static_analyser impor
14
35
 
15
36
 
16
37
  class TestStateStaticAnalyser(StaticAnalyser):
38
+ state_name: StateName | None
39
+
40
+ def __init__(self, state_name: StateName | None = None):
41
+ self.state_name = state_name
42
+
17
43
  _SUPPORTED_STATE_TYPES: Final[set[StateType]] = {
18
44
  StateType.Task,
19
45
  StateType.Pass,
@@ -21,10 +47,147 @@ class TestStateStaticAnalyser(StaticAnalyser):
21
47
  StateType.Choice,
22
48
  StateType.Succeed,
23
49
  StateType.Fail,
50
+ StateType.Map,
24
51
  }
25
52
 
26
- def analyse(self, definition) -> None:
27
- _, parser_rule_context = TestStateAmazonStateLanguageParser.parse(definition)
53
+ @staticmethod
54
+ def is_state_in_definition(definition: Definition, state_name: StateName) -> bool:
55
+ test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
56
+ if not isinstance(test_program, TestStateProgram):
57
+ raise ValueError("expected parsed EvalComponent to be of type TestStateProgram")
58
+
59
+ return test_program.test_state is not None
60
+
61
+ @staticmethod
62
+ def validate_mock(mock_input: MockInput, definition: Definition, state_name: StateName) -> None:
63
+ test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
64
+ test_state = test_program.test_state
65
+
66
+ # apigateway:invoke: has no equivalent in the AWS SDK service integration.
67
+ # Hence, the validation against botocore doesn't apply.
68
+ # See the note in https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
69
+ # TODO do custom validation for apigateway:invoke:
70
+ if isinstance(test_state, StateTaskServiceApiGateway):
71
+ return
72
+
73
+ if isinstance(test_state, StateTaskService):
74
+ field_validation_mode = mock_input.get(
75
+ "fieldValidationMode", MockResponseValidationMode.STRICT
76
+ )
77
+ mock_result_raw = mock_input.get("result")
78
+ if mock_result_raw is None:
79
+ return
80
+ try:
81
+ mock_result = json.loads(mock_result_raw)
82
+ except json.JSONDecodeError:
83
+ raise ValidationException("Mocked result must be valid JSON")
84
+ if mock_result is None or field_validation_mode == MockResponseValidationMode.NONE:
85
+ return
86
+
87
+ boto_service_name = test_state._get_boto_service_name()
88
+ service_action_name = test_state._get_boto_service_action()
89
+ output_shape = test_state._get_boto_operation_model(
90
+ boto_service_name=boto_service_name, service_action_name=service_action_name
91
+ ).output_shape
92
+
93
+ # If the operation has no output, there's nothing to validate
94
+ if output_shape is None:
95
+ return
96
+
97
+ def _raise_type_error(expected_type: str, field_name: str) -> None:
98
+ raise ValidationException(
99
+ f"Mock result schema validation error: Field '{field_name}' must be {expected_type}"
100
+ )
101
+
102
+ def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None:
103
+ # Document type accepts any JSON value
104
+ if shape.type_name == "document":
105
+ return
106
+
107
+ if isinstance(shape, StructureShape):
108
+ if not isinstance(value, dict):
109
+ # this is a defensive check, the mock result is loaded from JSON before, so should always be a dict
110
+ raise ValidationException(
111
+ f"Mock result must be a valid JSON object, but got '{type(value)}' instead"
112
+ )
113
+ # Build a mapping from SFN-normalised member keys -> botocore member shapes
114
+ members = shape.members
115
+ sfn_key_to_member_shape: dict[str, Shape] = {
116
+ StateTaskService._to_sfn_cased(member_key): member_shape
117
+ for member_key, member_shape in members.items()
118
+ }
119
+ if field_validation_mode == MockResponseValidationMode.STRICT:
120
+ # Ensure required members are present, using SFN-normalised keys
121
+ for required_key in shape.required_members:
122
+ sfn_required_key = StateTaskService._to_sfn_cased(required_key)
123
+ if sfn_required_key not in value:
124
+ raise ValidationException(
125
+ f"Mock result schema validation error: Required field '{sfn_required_key}' is missing"
126
+ )
127
+ # Validate present fields (match SFN-normalised keys to member shapes)
128
+ for mock_field_name, mock_field_value in value.items():
129
+ member_shape = sfn_key_to_member_shape.get(mock_field_name)
130
+ if member_shape is None:
131
+ # Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility
132
+ continue
133
+ _validate_value(mock_field_value, member_shape, mock_field_name)
134
+ return
135
+
136
+ if isinstance(shape, ListShape):
137
+ if not isinstance(value, list):
138
+ _raise_type_error("an array", field_name)
139
+ member_shape = shape.member
140
+ for list_item in value:
141
+ _validate_value(list_item, member_shape, field_name)
142
+ return
143
+
144
+ if isinstance(shape, MapShape):
145
+ if not isinstance(value, dict):
146
+ _raise_type_error("an object", field_name)
147
+ value_shape = shape.value
148
+ for _, map_item_value in value.items():
149
+ _validate_value(map_item_value, value_shape, field_name)
150
+ return
151
+
152
+ # Primitive shapes and others
153
+ type_name = shape.type_name
154
+ match type_name:
155
+ case "string" | "timestamp":
156
+ if not isinstance(value, str):
157
+ _raise_type_error("a string", field_name)
158
+ # Validate enum if present
159
+ if isinstance(shape, StringShape):
160
+ enum = getattr(shape, "enum", None)
161
+ if enum and value not in enum:
162
+ raise ValidationException(
163
+ f"Mock result schema validation error: Field '{field_name}' is not an expected value"
164
+ )
165
+
166
+ case "integer" | "long":
167
+ if not isinstance(value, int) or isinstance(value, bool):
168
+ _raise_type_error("a number", field_name)
169
+
170
+ case "float" | "double":
171
+ if not (isinstance(value, (int, float)) or isinstance(value, bool)):
172
+ _raise_type_error("a number", field_name)
173
+
174
+ case "boolean":
175
+ if not isinstance(value, bool):
176
+ _raise_type_error("a boolean", field_name)
177
+
178
+ case "blob":
179
+ if not (isinstance(value, (str, bytes))):
180
+ _raise_type_error("a string", field_name)
181
+
182
+ # Perform validation against the output shape
183
+ _validate_value(mock_result, output_shape)
184
+ # Non-service tasks or other cases: nothing to validate
185
+ return
186
+
187
+ def analyse(self, definition: str) -> None:
188
+ _, parser_rule_context = TestStateAmazonStateLanguageParser.parse(
189
+ definition, self.state_name
190
+ )
28
191
  self.visit(parser_rule_context)
29
192
 
30
193
  def visitState_type(self, ctx: ASLParser.State_typeContext) -> None:
@@ -32,18 +195,3 @@ class TestStateStaticAnalyser(StaticAnalyser):
32
195
  state_type = StateType(state_type_value)
33
196
  if state_type not in self._SUPPORTED_STATE_TYPES:
34
197
  raise ValueError(f"Unsupported state type for TestState runs '{state_type}'.")
35
-
36
- def visitResource_decl(self, ctx: ASLParser.Resource_declContext) -> None:
37
- resource_str: str = ctx.string_literal().getText()[1:-1]
38
- resource = Resource.from_resource_arn(resource_str)
39
-
40
- if isinstance(resource, ActivityResource):
41
- raise ValueError(
42
- f"ActivityResources are not supported for TestState runs {resource_str}."
43
- )
44
-
45
- if isinstance(resource, ServiceResource):
46
- if resource.condition is not None:
47
- raise ValueError(
48
- f"Service integration patterns are not supported for TestState runs {resource_str}."
49
- )
@@ -59,7 +59,7 @@ from localstack.services.stepfunctions.backend.state_machine import (
59
59
  StateMachineInstance,
60
60
  StateMachineVersion,
61
61
  )
62
- from localstack.services.stepfunctions.mocking.mock_config import MockTestCase
62
+ from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase
63
63
 
64
64
  LOG = logging.getLogger(__name__)
65
65
 
@@ -108,7 +108,7 @@ class Execution:
108
108
  state_machine_version_arn: Final[Arn | None]
109
109
  state_machine_alias_arn: Final[Arn | None]
110
110
 
111
- mock_test_case: Final[MockTestCase | None]
111
+ local_mock_test_case: Final[LocalMockTestCase | None]
112
112
 
113
113
  start_date: Final[Timestamp]
114
114
  input_data: Final[json | None]
@@ -144,7 +144,7 @@ class Execution:
144
144
  input_data: json | None = None,
145
145
  trace_header: TraceHeader | None = None,
146
146
  state_machine_alias_arn: Arn | None = None,
147
- mock_test_case: MockTestCase | None = None,
147
+ local_mock_test_case: LocalMockTestCase | None = None,
148
148
  ):
149
149
  self.name = name
150
150
  self.sm_type = sm_type
@@ -173,7 +173,7 @@ class Execution:
173
173
  self.error = None
174
174
  self.cause = None
175
175
  self._activity_store = activity_store
176
- self.mock_test_case = mock_test_case
176
+ self.local_mock_test_case = local_mock_test_case
177
177
 
178
178
  def _get_events_client(self):
179
179
  return connect_to(aws_access_key_id=self.account_id, region_name=self.region_name).events
@@ -304,7 +304,7 @@ class Execution:
304
304
  exec_comm=self._get_start_execution_worker_comm(),
305
305
  cloud_watch_logging_session=self._cloud_watch_logging_session,
306
306
  activity_store=self._activity_store,
307
- mock_test_case=self.mock_test_case,
307
+ local_mock_test_case=self.local_mock_test_case,
308
308
  )
309
309
 
310
310
  def start(self) -> None:
@@ -391,7 +391,7 @@ class SyncExecution(Execution):
391
391
  exec_comm=self._get_start_execution_worker_comm(),
392
392
  cloud_watch_logging_session=self._cloud_watch_logging_session,
393
393
  activity_store=self._activity_store,
394
- mock_test_case=self.mock_test_case,
394
+ local_mock_test_case=self.local_mock_test_case,
395
395
  )
396
396
 
397
397
  def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication:
@@ -29,7 +29,7 @@ from localstack.services.stepfunctions.backend.activity import Activity
29
29
  from localstack.services.stepfunctions.backend.execution_worker_comm import (
30
30
  ExecutionWorkerCommunication,
31
31
  )
32
- from localstack.services.stepfunctions.mocking.mock_config import MockTestCase
32
+ from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase
33
33
  from localstack.utils.common import TMP_THREADS
34
34
 
35
35
 
@@ -37,7 +37,7 @@ class ExecutionWorker:
37
37
  _evaluation_details: Final[EvaluationDetails]
38
38
  _execution_communication: Final[ExecutionWorkerCommunication]
39
39
  _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None]
40
- _mock_test_case: Final[MockTestCase | None]
40
+ _local_mock_test_case: Final[LocalMockTestCase | None]
41
41
  _activity_store: dict[Arn, Activity]
42
42
 
43
43
  env: Environment | None
@@ -48,12 +48,12 @@ class ExecutionWorker:
48
48
  exec_comm: ExecutionWorkerCommunication,
49
49
  cloud_watch_logging_session: CloudWatchLoggingSession | None,
50
50
  activity_store: dict[Arn, Activity],
51
- mock_test_case: MockTestCase | None = None,
51
+ local_mock_test_case: LocalMockTestCase | None = None,
52
52
  ):
53
53
  self._evaluation_details = evaluation_details
54
54
  self._execution_communication = exec_comm
55
55
  self._cloud_watch_logging_session = cloud_watch_logging_session
56
- self._mock_test_case = mock_test_case
56
+ self._local_mock_test_case = local_mock_test_case
57
57
  self._activity_store = activity_store
58
58
  self.env = None
59
59
 
@@ -82,7 +82,7 @@ class ExecutionWorker:
82
82
  event_history_context=EventHistoryContext.of_program_start(),
83
83
  cloud_watch_logging_session=self._cloud_watch_logging_session,
84
84
  activity_store=self._activity_store,
85
- mock_test_case=self._mock_test_case,
85
+ local_mock_test_case=self._local_mock_test_case,
86
86
  )
87
87
 
88
88
  def _execution_logic(self):