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.
- localstack/aws/api/apigateway/__init__.py +42 -0
- localstack/aws/api/cloudformation/__init__.py +161 -0
- localstack/aws/api/ec2/__init__.py +1178 -12
- localstack/aws/api/iam/__init__.py +228 -0
- localstack/aws/api/kms/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +1034 -66
- localstack/aws/api/logs/__init__.py +500 -0
- localstack/aws/api/opensearch/__init__.py +100 -0
- localstack/aws/api/redshift/__init__.py +69 -0
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
- localstack/aws/api/route53/__init__.py +45 -0
- localstack/aws/api/route53resolver/__init__.py +1 -0
- localstack/aws/api/s3/__init__.py +64 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +37 -23
- localstack/aws/api/stepfunctions/__init__.py +52 -10
- localstack/aws/api/sts/__init__.py +52 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +11 -2
- localstack/aws/protocol/serializer.py +1 -1
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +17 -1
- localstack/services/apigateway/legacy/provider.py +28 -15
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
- localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
- localstack/services/cloudformation/provider.py +26 -1
- localstack/services/cloudformation/provider_utils.py +20 -0
- localstack/services/cloudformation/resource_provider.py +5 -4
- localstack/services/cloudformation/scaffolding/__main__.py +94 -22
- localstack/services/cloudformation/v2/provider.py +41 -0
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +16 -22
- localstack/services/kms/provider.py +4 -0
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/api_utils.py +37 -20
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +31 -2
- localstack/services/lambda_/invocation/lambda_service.py +62 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/provider.py +307 -106
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +3 -1
- localstack/services/logs/provider.py +9 -0
- localstack/services/opensearch/packages.py +34 -20
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/route53/provider.py +7 -0
- localstack/services/route53resolver/provider.py +5 -0
- localstack/services/s3/constants.py +5 -0
- localstack/services/s3/exceptions.py +9 -0
- localstack/services/s3/models.py +9 -1
- localstack/services/s3/provider.py +51 -43
- localstack/services/s3/utils.py +81 -15
- localstack/services/s3control/provider.py +107 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/scheduler/provider.py +4 -2
- localstack/services/secretsmanager/provider.py +4 -0
- localstack/services/ses/provider.py +4 -0
- localstack/services/sns/constants.py +16 -1
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +9 -0
- localstack/services/sns/v2/provider.py +750 -19
- localstack/services/sns/v2/utils.py +12 -0
- localstack/services/sqs/constants.py +6 -0
- localstack/services/sqs/provider.py +9 -1
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
- localstack/services/ssm/provider.py +6 -0
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
- localstack/services/stepfunctions/backend/execution.py +10 -11
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +83 -25
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/services/sts/provider.py +7 -0
- localstack/services/support/provider.py +5 -1
- localstack/services/swf/provider.py +5 -1
- localstack/services/transcribe/provider.py +7 -0
- localstack/testing/aws/lambda_utils.py +1 -1
- localstack/testing/aws/util.py +2 -1
- localstack/testing/config.py +1 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +5 -0
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +2 -4
- localstack/utils/batching.py +258 -0
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- localstack/utils/collections.py +23 -11
- localstack/utils/container_utils/container_client.py +22 -13
- localstack/utils/container_utils/docker_cmd_client.py +6 -6
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
- localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
-
|
|
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.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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.
|
|
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
|
-
|
|
110
|
+
local_mock_test_case: Final[LocalMockTestCase | None]
|
|
112
111
|
|
|
113
112
|
start_date: Final[Timestamp]
|
|
114
|
-
input_data: Final[
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
)
|