localstack-core 4.10.1.dev42__py3-none-any.whl → 4.12.1.dev18__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 (158) hide show
  1. localstack/aws/api/apigateway/__init__.py +42 -0
  2. localstack/aws/api/cloudformation/__init__.py +161 -0
  3. localstack/aws/api/ec2/__init__.py +1178 -12
  4. localstack/aws/api/iam/__init__.py +228 -0
  5. localstack/aws/api/kms/__init__.py +1 -0
  6. localstack/aws/api/lambda_/__init__.py +1034 -66
  7. localstack/aws/api/logs/__init__.py +500 -0
  8. localstack/aws/api/opensearch/__init__.py +100 -0
  9. localstack/aws/api/redshift/__init__.py +69 -0
  10. localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
  11. localstack/aws/api/route53/__init__.py +45 -0
  12. localstack/aws/api/route53resolver/__init__.py +1 -0
  13. localstack/aws/api/s3/__init__.py +64 -0
  14. localstack/aws/api/s3control/__init__.py +19 -0
  15. localstack/aws/api/secretsmanager/__init__.py +37 -23
  16. localstack/aws/api/stepfunctions/__init__.py +52 -10
  17. localstack/aws/api/sts/__init__.py +52 -0
  18. localstack/aws/connect.py +35 -15
  19. localstack/aws/handlers/logging.py +8 -4
  20. localstack/aws/handlers/service.py +11 -2
  21. localstack/aws/protocol/serializer.py +1 -1
  22. localstack/config.py +8 -0
  23. localstack/constants.py +3 -0
  24. localstack/deprecations.py +0 -6
  25. localstack/dev/kubernetes/__main__.py +39 -14
  26. localstack/runtime/analytics.py +11 -0
  27. localstack/services/acm/provider.py +17 -1
  28. localstack/services/apigateway/legacy/provider.py +28 -15
  29. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  30. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  31. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
  32. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  33. localstack/services/cloudformation/provider.py +26 -1
  34. localstack/services/cloudformation/provider_utils.py +20 -0
  35. localstack/services/cloudformation/resource_provider.py +5 -4
  36. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  37. localstack/services/cloudformation/v2/provider.py +41 -0
  38. localstack/services/cloudwatch/provider.py +10 -3
  39. localstack/services/cloudwatch/provider_v2.py +6 -3
  40. localstack/services/configservice/provider.py +5 -1
  41. localstack/services/dynamodb/provider.py +1 -0
  42. localstack/services/dynamodb/v2/provider.py +1 -0
  43. localstack/services/dynamodbstreams/provider.py +6 -0
  44. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  45. localstack/services/ec2/provider.py +6 -0
  46. localstack/services/es/provider.py +6 -0
  47. localstack/services/events/provider.py +4 -0
  48. localstack/services/events/v1/provider.py +9 -0
  49. localstack/services/firehose/provider.py +5 -0
  50. localstack/services/iam/provider.py +4 -0
  51. localstack/services/kinesis/packages.py +1 -1
  52. localstack/services/kms/models.py +16 -22
  53. localstack/services/kms/provider.py +4 -0
  54. localstack/services/lambda_/analytics.py +11 -2
  55. localstack/services/lambda_/api_utils.py +37 -20
  56. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  57. localstack/services/lambda_/invocation/assignment.py +4 -1
  58. localstack/services/lambda_/invocation/event_manager.py +15 -11
  59. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  60. localstack/services/lambda_/invocation/lambda_models.py +31 -2
  61. localstack/services/lambda_/invocation/lambda_service.py +62 -3
  62. localstack/services/lambda_/invocation/models.py +9 -1
  63. localstack/services/lambda_/invocation/version_manager.py +18 -3
  64. localstack/services/lambda_/provider.py +307 -106
  65. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  66. localstack/services/lambda_/runtimes.py +3 -1
  67. localstack/services/logs/provider.py +9 -0
  68. localstack/services/opensearch/packages.py +34 -20
  69. localstack/services/opensearch/provider.py +53 -3
  70. localstack/services/resource_groups/provider.py +5 -1
  71. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  72. localstack/services/route53/provider.py +7 -0
  73. localstack/services/route53resolver/provider.py +5 -0
  74. localstack/services/s3/constants.py +5 -0
  75. localstack/services/s3/exceptions.py +9 -0
  76. localstack/services/s3/models.py +9 -1
  77. localstack/services/s3/provider.py +51 -43
  78. localstack/services/s3/utils.py +81 -15
  79. localstack/services/s3control/provider.py +107 -2
  80. localstack/services/s3control/validation.py +50 -0
  81. localstack/services/scheduler/provider.py +4 -2
  82. localstack/services/secretsmanager/provider.py +4 -0
  83. localstack/services/ses/provider.py +4 -0
  84. localstack/services/sns/constants.py +16 -1
  85. localstack/services/sns/provider.py +5 -0
  86. localstack/services/sns/publisher.py +15 -6
  87. localstack/services/sns/v2/models.py +9 -0
  88. localstack/services/sns/v2/provider.py +750 -19
  89. localstack/services/sns/v2/utils.py +12 -0
  90. localstack/services/sqs/constants.py +6 -0
  91. localstack/services/sqs/provider.py +9 -1
  92. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  93. localstack/services/ssm/provider.py +6 -0
  94. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  95. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  96. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  101. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  102. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  103. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  107. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  108. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  109. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  110. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  111. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  112. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  113. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  114. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  115. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
  116. localstack/services/stepfunctions/backend/execution.py +10 -11
  117. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  118. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  119. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  120. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  121. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  122. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  123. localstack/services/stepfunctions/provider.py +83 -25
  124. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  125. localstack/services/sts/provider.py +7 -0
  126. localstack/services/support/provider.py +5 -1
  127. localstack/services/swf/provider.py +5 -1
  128. localstack/services/transcribe/provider.py +7 -0
  129. localstack/testing/aws/lambda_utils.py +1 -1
  130. localstack/testing/aws/util.py +2 -1
  131. localstack/testing/config.py +1 -0
  132. localstack/testing/pytest/fixtures.py +28 -0
  133. localstack/testing/snapshots/transformer_utility.py +5 -0
  134. localstack/utils/analytics/publisher.py +37 -155
  135. localstack/utils/analytics/service_request_aggregator.py +6 -4
  136. localstack/utils/aws/arns.py +7 -0
  137. localstack/utils/aws/client_types.py +2 -4
  138. localstack/utils/batching.py +258 -0
  139. localstack/utils/bootstrap.py +2 -2
  140. localstack/utils/catalog/catalog.py +3 -2
  141. localstack/utils/collections.py +23 -11
  142. localstack/utils/container_utils/container_client.py +22 -13
  143. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  144. localstack/version.py +2 -2
  145. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
  146. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
  147. localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
  148. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  149. localstack/utils/batch_policy.py +0 -124
  150. localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
  151. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  152. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
  153. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
  154. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
  155. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
  156. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
  157. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
  158. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,50 @@
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
+ InvalidDefinition,
16
+ MockInput,
17
+ MockResponseValidationMode,
18
+ StateName,
19
+ TestStateInput,
20
+ ValidationException,
21
+ )
3
22
  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,
23
+ from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
24
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import (
25
+ StateMap,
26
+ )
27
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_parallel.state_parallel import (
28
+ StateParallel,
29
+ )
30
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import (
31
+ StateTaskService,
32
+ )
33
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service_api_gateway import (
34
+ StateTaskServiceApiGateway,
35
+ )
36
+ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import (
37
+ StateTask,
38
+ )
39
+ from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
40
+ from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
41
+ from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
42
+ StateSucceed,
8
43
  )
9
44
  from localstack.services.stepfunctions.asl.component.state.state_type import StateType
45
+ from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
46
+ TestStateProgram,
47
+ )
10
48
  from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
11
49
  TestStateAmazonStateLanguageParser,
12
50
  )
@@ -14,6 +52,11 @@ from localstack.services.stepfunctions.asl.static_analyser.static_analyser impor
14
52
 
15
53
 
16
54
  class TestStateStaticAnalyser(StaticAnalyser):
55
+ state_name: StateName | None
56
+
57
+ def __init__(self, state_name: StateName | None = None):
58
+ self.state_name = state_name
59
+
17
60
  _SUPPORTED_STATE_TYPES: Final[set[StateType]] = {
18
61
  StateType.Task,
19
62
  StateType.Pass,
@@ -21,10 +64,216 @@ class TestStateStaticAnalyser(StaticAnalyser):
21
64
  StateType.Choice,
22
65
  StateType.Succeed,
23
66
  StateType.Fail,
67
+ StateType.Map,
24
68
  }
25
69
 
26
- def analyse(self, definition) -> None:
27
- _, parser_rule_context = TestStateAmazonStateLanguageParser.parse(definition)
70
+ @staticmethod
71
+ def is_state_in_definition(definition: Definition, state_name: StateName) -> bool:
72
+ test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
73
+ if not isinstance(test_program, TestStateProgram):
74
+ raise ValueError("expected parsed EvalComponent to be of type TestStateProgram")
75
+
76
+ return test_program.test_state is not None
77
+
78
+ @staticmethod
79
+ def validate_role_arn_required(
80
+ mock_input: MockInput, definition: Definition, state_name: StateName
81
+ ) -> None:
82
+ test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
83
+ test_state = test_program.test_state
84
+ if isinstance(test_state, StateTask) and mock_input is None:
85
+ raise ValidationException("RoleArn must be specified when testing a Task state")
86
+
87
+ @staticmethod
88
+ def validate_mock(test_state_input: TestStateInput) -> None:
89
+ test_program, _ = TestStateAmazonStateLanguageParser.parse(
90
+ test_state_input.get("definition"), test_state_input.get("stateName")
91
+ )
92
+ test_state = test_program.test_state
93
+ mock_input = test_state_input.get("mock")
94
+
95
+ TestStateStaticAnalyser.validate_test_state_allows_mocking(
96
+ mock_input=mock_input, test_state=test_state
97
+ )
98
+
99
+ if mock_input is None:
100
+ return
101
+
102
+ if test_state_input.get("revealSecrets"):
103
+ raise ValidationException(
104
+ "TestState does not support RevealSecrets when a mock is specified."
105
+ )
106
+
107
+ if {"result", "errorOutput"} <= mock_input.keys():
108
+ raise ValidationException(
109
+ "A test mock should have only one of the following fields: [result, errorOutput]."
110
+ )
111
+
112
+ mock_result_raw = mock_input.get("result")
113
+ if mock_result_raw is None:
114
+ return
115
+ try:
116
+ mock_result = json.loads(mock_result_raw)
117
+ except json.JSONDecodeError:
118
+ raise ValidationException("Mocked result must be valid JSON")
119
+
120
+ if isinstance(test_state, StateMap):
121
+ TestStateStaticAnalyser.validate_mock_result_matches_map_definition(
122
+ mock_result=mock_result, test_state=test_state
123
+ )
124
+
125
+ if isinstance(test_state, StateTaskService):
126
+ field_validation_mode = mock_input.get(
127
+ "fieldValidationMode", MockResponseValidationMode.STRICT
128
+ )
129
+ TestStateStaticAnalyser.validate_mock_result_matches_api_shape(
130
+ mock_result=mock_result,
131
+ field_validation_mode=field_validation_mode,
132
+ test_state=test_state,
133
+ )
134
+
135
+ @staticmethod
136
+ def validate_test_state_allows_mocking(
137
+ mock_input: MockInput, test_state: CommonStateField
138
+ ) -> None:
139
+ if mock_input is None and isinstance(test_state, (StateMap, StateParallel)):
140
+ # This is a literal message when a Map or Parallel state is not accompanied by a mock in a test state request.
141
+ # The message is the same for both cases and is not parametrised anyhow.
142
+ raise InvalidDefinition(
143
+ "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]"
144
+ )
145
+
146
+ if mock_input is not None and isinstance(test_state, (StatePass, StateFail, StateSucceed)):
147
+ raise ValidationException(
148
+ f"State type '{test_state.state_type.name}' is not supported when a mock is specified"
149
+ )
150
+
151
+ @staticmethod
152
+ def validate_mock_result_matches_map_definition(mock_result: Any, test_state: StateMap):
153
+ if test_state.result_writer is not None and not isinstance(mock_result, dict):
154
+ raise ValidationException("Mocked result must be a JSON object.")
155
+
156
+ if test_state.result_writer is None and not isinstance(mock_result, list):
157
+ raise ValidationException("Mocked result must be an array.")
158
+
159
+ @staticmethod
160
+ def validate_mock_result_matches_api_shape(
161
+ mock_result: Any,
162
+ field_validation_mode: MockResponseValidationMode,
163
+ test_state: StateTaskService,
164
+ ):
165
+ # apigateway:invoke: has no equivalent in the AWS SDK service integration.
166
+ # Hence, the validation against botocore doesn't apply.
167
+ # See the note in https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
168
+ # TODO do custom validation for apigateway:invoke:
169
+ if isinstance(test_state, StateTaskServiceApiGateway):
170
+ return
171
+
172
+ if field_validation_mode == MockResponseValidationMode.NONE:
173
+ return
174
+
175
+ boto_service_name = test_state._get_boto_service_name()
176
+ service_action_name = test_state._get_boto_service_action()
177
+ output_shape = test_state._get_boto_operation_model(
178
+ boto_service_name=boto_service_name, service_action_name=service_action_name
179
+ ).output_shape
180
+
181
+ # If the operation has no output, there's nothing to validate
182
+ if output_shape is None:
183
+ return
184
+
185
+ def _raise_type_error(expected_type: str, field_name: str) -> None:
186
+ raise ValidationException(
187
+ f"Mock result schema validation error: Field '{field_name}' must be {expected_type}"
188
+ )
189
+
190
+ def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None:
191
+ # Document type accepts any JSON value
192
+ if shape.type_name == "document":
193
+ return
194
+
195
+ if isinstance(shape, StructureShape):
196
+ if not isinstance(value, dict):
197
+ # this is a defensive check, the mock result is loaded from JSON before, so should always be a dict
198
+ raise ValidationException(
199
+ f"Mock result must be a valid JSON object, but got '{type(value)}' instead"
200
+ )
201
+ # Build a mapping from SFN-normalised member keys -> botocore member shapes
202
+ members = shape.members
203
+ sfn_key_to_member_shape: dict[str, Shape] = {
204
+ StateTaskService._to_sfn_cased(member_key): member_shape
205
+ for member_key, member_shape in members.items()
206
+ }
207
+ if field_validation_mode == MockResponseValidationMode.STRICT:
208
+ # Ensure required members are present, using SFN-normalised keys
209
+ for required_key in shape.required_members:
210
+ sfn_required_key = StateTaskService._to_sfn_cased(required_key)
211
+ if sfn_required_key not in value:
212
+ raise ValidationException(
213
+ f"Mock result schema validation error: Required field '{sfn_required_key}' is missing"
214
+ )
215
+ # Validate present fields (match SFN-normalised keys to member shapes)
216
+ for mock_field_name, mock_field_value in value.items():
217
+ member_shape = sfn_key_to_member_shape.get(mock_field_name)
218
+ if member_shape is None:
219
+ # Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility
220
+ continue
221
+ _validate_value(mock_field_value, member_shape, mock_field_name)
222
+ return
223
+
224
+ if isinstance(shape, ListShape):
225
+ if not isinstance(value, list):
226
+ _raise_type_error("an array", field_name)
227
+ member_shape = shape.member
228
+ for list_item in value:
229
+ _validate_value(list_item, member_shape, field_name)
230
+ return
231
+
232
+ if isinstance(shape, MapShape):
233
+ if not isinstance(value, dict):
234
+ _raise_type_error("an object", field_name)
235
+ value_shape = shape.value
236
+ for _, map_item_value in value.items():
237
+ _validate_value(map_item_value, value_shape, field_name)
238
+ return
239
+
240
+ # Primitive shapes and others
241
+ type_name = shape.type_name
242
+ match type_name:
243
+ case "string" | "timestamp":
244
+ if not isinstance(value, str):
245
+ _raise_type_error("a string", field_name)
246
+ # Validate enum if present
247
+ if isinstance(shape, StringShape):
248
+ enum = getattr(shape, "enum", None)
249
+ if enum and value not in enum:
250
+ raise ValidationException(
251
+ f"Mock result schema validation error: Field '{field_name}' is not an expected value"
252
+ )
253
+
254
+ case "integer" | "long":
255
+ if not isinstance(value, int) or isinstance(value, bool):
256
+ _raise_type_error("a number", field_name)
257
+
258
+ case "float" | "double":
259
+ if not (isinstance(value, (int, float)) or isinstance(value, bool)):
260
+ _raise_type_error("a number", field_name)
261
+
262
+ case "boolean":
263
+ if not isinstance(value, bool):
264
+ _raise_type_error("a boolean", field_name)
265
+
266
+ case "blob":
267
+ if not (isinstance(value, (str, bytes))):
268
+ _raise_type_error("a string", field_name)
269
+
270
+ # Perform validation against the output shape
271
+ _validate_value(mock_result, output_shape)
272
+
273
+ def analyse(self, definition: str) -> None:
274
+ _, parser_rule_context = TestStateAmazonStateLanguageParser.parse(
275
+ definition, self.state_name
276
+ )
28
277
  self.visit(parser_rule_context)
29
278
 
30
279
  def visitState_type(self, ctx: ASLParser.State_typeContext) -> None:
@@ -32,18 +281,3 @@ class TestStateStaticAnalyser(StaticAnalyser):
32
281
  state_type = StateType(state_type_value)
33
282
  if state_type not in self._SUPPORTED_STATE_TYPES:
34
283
  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
- )
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime
4
- import json
5
4
  import logging
6
- from typing import Final
5
+ from typing import Any, Final
7
6
 
8
7
  from localstack.aws.api.events import PutEventsRequestEntry
9
8
  from localstack.aws.api.stepfunctions import (
@@ -59,7 +58,7 @@ from localstack.services.stepfunctions.backend.state_machine import (
59
58
  StateMachineInstance,
60
59
  StateMachineVersion,
61
60
  )
62
- from localstack.services.stepfunctions.mocking.mock_config import MockTestCase
61
+ from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase
63
62
 
64
63
  LOG = logging.getLogger(__name__)
65
64
 
@@ -108,10 +107,10 @@ class Execution:
108
107
  state_machine_version_arn: Final[Arn | None]
109
108
  state_machine_alias_arn: Final[Arn | None]
110
109
 
111
- mock_test_case: Final[MockTestCase | None]
110
+ local_mock_test_case: Final[LocalMockTestCase | None]
112
111
 
113
112
  start_date: Final[Timestamp]
114
- input_data: Final[json | None]
113
+ input_data: Final[dict[str, Any] | None]
115
114
  input_details: Final[CloudWatchEventsExecutionDataDetails | None]
116
115
  trace_header: Final[TraceHeader | None]
117
116
  _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None]
@@ -119,7 +118,7 @@ class Execution:
119
118
  exec_status: ExecutionStatus | None
120
119
  stop_date: Timestamp | None
121
120
 
122
- output: json | None
121
+ output: dict[str, Any] | None
123
122
  output_details: CloudWatchEventsExecutionDataDetails | None
124
123
 
125
124
  error: SensitiveError | None
@@ -141,10 +140,10 @@ class Execution:
141
140
  start_date: Timestamp,
142
141
  cloud_watch_logging_session: CloudWatchLoggingSession | None,
143
142
  activity_store: dict[Arn, Activity],
144
- input_data: json | None = None,
143
+ input_data: dict[str, Any] | None = None,
145
144
  trace_header: TraceHeader | None = None,
146
145
  state_machine_alias_arn: Arn | None = None,
147
- mock_test_case: MockTestCase | None = None,
146
+ local_mock_test_case: LocalMockTestCase | None = None,
148
147
  ):
149
148
  self.name = name
150
149
  self.sm_type = sm_type
@@ -173,7 +172,7 @@ class Execution:
173
172
  self.error = None
174
173
  self.cause = None
175
174
  self._activity_store = activity_store
176
- self.mock_test_case = mock_test_case
175
+ self.local_mock_test_case = local_mock_test_case
177
176
 
178
177
  def _get_events_client(self):
179
178
  return connect_to(aws_access_key_id=self.account_id, region_name=self.region_name).events
@@ -304,7 +303,7 @@ class Execution:
304
303
  exec_comm=self._get_start_execution_worker_comm(),
305
304
  cloud_watch_logging_session=self._cloud_watch_logging_session,
306
305
  activity_store=self._activity_store,
307
- mock_test_case=self.mock_test_case,
306
+ local_mock_test_case=self.local_mock_test_case,
308
307
  )
309
308
 
310
309
  def start(self) -> None:
@@ -391,7 +390,7 @@ class SyncExecution(Execution):
391
390
  exec_comm=self._get_start_execution_worker_comm(),
392
391
  cloud_watch_logging_session=self._cloud_watch_logging_session,
393
392
  activity_store=self._activity_store,
394
- mock_test_case=self.mock_test_case,
393
+ local_mock_test_case=self.local_mock_test_case,
395
394
  )
396
395
 
397
396
  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):
@@ -19,7 +19,9 @@ from localstack.services.stepfunctions.asl.eval.program_state import (
19
19
  ProgramState,
20
20
  )
21
21
  from localstack.services.stepfunctions.asl.eval.test_state.program_state import (
22
+ ProgramCaughtError,
22
23
  ProgramChoiceSelected,
24
+ ProgramRetriable,
23
25
  )
24
26
  from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
25
27
  from localstack.services.stepfunctions.backend.activity import Activity
@@ -31,6 +33,7 @@ from localstack.services.stepfunctions.backend.state_machine import StateMachine
31
33
  from localstack.services.stepfunctions.backend.test_state.execution_worker import (
32
34
  TestStateExecutionWorker,
33
35
  )
36
+ from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
34
37
 
35
38
  LOG = logging.getLogger(__name__)
36
39
 
@@ -38,6 +41,8 @@ LOG = logging.getLogger(__name__)
38
41
  class TestStateExecution(Execution):
39
42
  exec_worker: TestStateExecutionWorker | None
40
43
  next_state: str | None
44
+ state_name: str | None
45
+ mock: TestStateMock | None
41
46
 
42
47
  class TestCaseExecutionWorkerCommunication(BaseExecutionWorkerCommunication):
43
48
  _execution: TestStateExecution
@@ -48,6 +53,16 @@ class TestStateExecution(Execution):
48
53
  self.execution.exec_status = ExecutionStatus.SUCCEEDED
49
54
  self.execution.output = self.execution.exec_worker.env.states.get_input()
50
55
  self.execution.next_state = exit_program_state.next_state_name
56
+ elif isinstance(exit_program_state, ProgramCaughtError):
57
+ self.execution.exec_status = ExecutionStatus.SUCCEEDED
58
+ self.execution.error = exit_program_state.error
59
+ self.execution.cause = exit_program_state.cause
60
+ self.execution.output = self.execution.exec_worker.env.states.get_input()
61
+ self.execution.next_state = exit_program_state.next_state_name
62
+ elif isinstance(exit_program_state, ProgramRetriable):
63
+ self.execution.exec_status = ExecutionStatus.SUCCEEDED
64
+ self.execution.error = exit_program_state.error
65
+ self.execution.cause = exit_program_state.cause
51
66
  else:
52
67
  self._reflect_execution_status()
53
68
 
@@ -61,7 +76,9 @@ class TestStateExecution(Execution):
61
76
  state_machine: StateMachineInstance,
62
77
  start_date: Timestamp,
63
78
  activity_store: dict[Arn, Activity],
79
+ state_name: str | None = None,
64
80
  input_data: dict | None = None,
81
+ mock: TestStateMock | None = None,
65
82
  ):
66
83
  super().__init__(
67
84
  name=name,
@@ -79,6 +96,8 @@ class TestStateExecution(Execution):
79
96
  )
80
97
  self._execution_terminated_event = threading.Event()
81
98
  self.next_state = None
99
+ self.state_name = state_name
100
+ self.mock = mock
82
101
 
83
102
  def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication:
84
103
  return self.TestCaseExecutionWorkerCommunication(self)
@@ -93,6 +112,8 @@ class TestStateExecution(Execution):
93
112
  exec_comm=self._get_start_execution_worker_comm(),
94
113
  cloud_watch_logging_session=self._cloud_watch_logging_session,
95
114
  activity_store=self._activity_store,
115
+ state_name=self.state_name,
116
+ mock=self.mock,
96
117
  )
97
118
 
98
119
  def publish_execution_status_change_event(self):
@@ -117,6 +138,21 @@ class TestStateExecution(Execution):
117
138
  test_state_output = TestStateOutput(
118
139
  status=TestExecutionStatus.SUCCEEDED, nextState=self.next_state, output=output_str
119
140
  )
141
+ elif isinstance(exit_program_state, ProgramCaughtError):
142
+ output_str = to_json_str(self.output)
143
+ test_state_output = TestStateOutput(
144
+ status=TestExecutionStatus.CAUGHT_ERROR,
145
+ nextState=self.next_state,
146
+ output=output_str,
147
+ error=exit_program_state.error,
148
+ cause=exit_program_state.cause,
149
+ )
150
+ elif isinstance(exit_program_state, ProgramRetriable):
151
+ test_state_output = TestStateOutput(
152
+ status=TestExecutionStatus.RETRIABLE,
153
+ error=exit_program_state.error,
154
+ cause=exit_program_state.cause,
155
+ )
120
156
  else:
121
157
  # TODO: handle other statuses
122
158
  LOG.warning(
@@ -1,8 +1,13 @@
1
+ from localstack.aws.api.stepfunctions import Arn, StateName
1
2
  from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent
2
3
  from localstack.services.stepfunctions.asl.eval.environment import Environment
4
+ from localstack.services.stepfunctions.asl.eval.evaluation_details import EvaluationDetails
3
5
  from localstack.services.stepfunctions.asl.eval.event.event_manager import (
4
6
  EventHistoryContext,
5
7
  )
8
+ from localstack.services.stepfunctions.asl.eval.event.logging import (
9
+ CloudWatchLoggingSession,
10
+ )
6
11
  from localstack.services.stepfunctions.asl.eval.states import (
7
12
  ContextObjectData,
8
13
  ExecutionData,
@@ -12,15 +17,41 @@ from localstack.services.stepfunctions.asl.eval.test_state.environment import Te
12
17
  from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import (
13
18
  TestStateAmazonStateLanguageParser,
14
19
  )
20
+ from localstack.services.stepfunctions.backend.activity import Activity
15
21
  from localstack.services.stepfunctions.backend.execution_worker import SyncExecutionWorker
22
+ from localstack.services.stepfunctions.backend.execution_worker_comm import (
23
+ ExecutionWorkerCommunication,
24
+ )
25
+ from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock
16
26
 
17
27
 
18
28
  class TestStateExecutionWorker(SyncExecutionWorker):
19
29
  env: TestStateEnvironment | None
30
+ state_name: str | None = None
31
+ mock: TestStateMock | None
32
+
33
+ def __init__(
34
+ self,
35
+ evaluation_details: EvaluationDetails,
36
+ exec_comm: ExecutionWorkerCommunication,
37
+ cloud_watch_logging_session: CloudWatchLoggingSession | None,
38
+ activity_store: dict[Arn, Activity],
39
+ state_name: StateName | None = None,
40
+ mock: TestStateMock | None = None,
41
+ ):
42
+ super().__init__(
43
+ evaluation_details,
44
+ exec_comm,
45
+ cloud_watch_logging_session,
46
+ activity_store,
47
+ local_mock_test_case=None, # local mock is only applicable to SFN Local, but not for TestState
48
+ )
49
+ self.state_name = state_name
50
+ self.mock = mock
20
51
 
21
52
  def _get_evaluation_entrypoint(self) -> EvalComponent:
22
53
  return TestStateAmazonStateLanguageParser.parse(
23
- self._evaluation_details.state_machine_details.definition
54
+ self._evaluation_details.state_machine_details.definition, self.state_name
24
55
  )[0]
25
56
 
26
57
  def _get_evaluation_environment(self) -> Environment:
@@ -43,4 +74,5 @@ class TestStateExecutionWorker(SyncExecutionWorker):
43
74
  event_history_context=EventHistoryContext.of_program_start(),
44
75
  cloud_watch_logging_session=self._cloud_watch_logging_session,
45
76
  activity_store=self._activity_store,
77
+ mock=self.mock,
46
78
  )