• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

localstack / localstack / 22709357475

05 Mar 2026 08:35AM UTC coverage: 59.732% (-27.2%) from 86.974%
22709357475

Pull #13880

github

web-flow
Merge 28fcab93c into 710618057
Pull Request #13880: Firehose: Replace TaggingService

12 of 12 new or added lines in 2 files covered. (100.0%)

20464 existing lines in 510 files now uncovered.

45290 of 75822 relevant lines covered (59.73%)

0.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.92
/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py
1
import json
1✔
2
import logging
1✔
3
import os
1✔
4
import shutil
1✔
5
import tempfile
1✔
6
from typing import Final
1✔
7

8
import pytest
1✔
9
from botocore.config import Config
1✔
10
from localstack_snapshot.snapshots.transformer import (
1✔
11
    JsonpathTransformer,
12
    RegexTransformer,
13
)
14

15
from localstack.aws.api.stepfunctions import StateMachineType
1✔
16
from localstack.testing.aws.util import is_aws_cloud
1✔
17
from localstack.testing.pytest.stepfunctions.utils import await_execution_success
1✔
18
from localstack.utils.strings import short_uid
1✔
19

20
LOG = logging.getLogger(__name__)
1✔
21

22

23
@pytest.fixture
1✔
24
def sfn_snapshot(snapshot):
1✔
UNCOV
25
    snapshot.add_transformers_list(snapshot.transform.stepfunctions_api())
×
UNCOV
26
    return snapshot
×
27

28

29
@pytest.fixture
1✔
30
def sfn_batch_snapshot(sfn_snapshot):
1✔
31
    sfn_snapshot.add_transformer(
×
32
        JsonpathTransformer(jsonpath="$..JobDefinition", replacement="job-definition")
33
    )
34
    sfn_snapshot.add_transformer(JsonpathTransformer(jsonpath="$..JobName", replacement="job-name"))
×
35
    sfn_snapshot.add_transformer(
×
36
        JsonpathTransformer(jsonpath="$..JobQueue", replacement="job-queue")
37
    )
38
    sfn_snapshot.add_transformer(JsonpathTransformer(jsonpath="$..roleArn", replacement="role-arn"))
×
39
    sfn_snapshot.add_transformer(
×
40
        JsonpathTransformer(
41
            jsonpath="$..x-amz-apigw-id", replacement="x-amz-apigw-id", replace_reference=False
42
        )
43
    )
44
    sfn_snapshot.add_transformer(
×
45
        JsonpathTransformer(
46
            jsonpath="$..X-Amzn-Trace-Id", replacement="X-Amzn-Trace-Id", replace_reference=False
47
        )
48
    )
49
    sfn_snapshot.add_transformer(JsonpathTransformer(jsonpath="$..TaskArn", replacement="task-arn"))
×
50
    sfn_snapshot.add_transformer(
×
51
        JsonpathTransformer(jsonpath="$..ExecutionRoleArn", replacement="execution-role-arn")
52
    )
53
    sfn_snapshot.add_transformer(
×
54
        JsonpathTransformer(jsonpath="$..LogStreamName", replacement="log-stream-name")
55
    )
56
    sfn_snapshot.add_transformer(
×
57
        JsonpathTransformer(jsonpath="$..StartedAt", replacement="time", replace_reference=False)
58
    )
59
    sfn_snapshot.add_transformer(
×
60
        JsonpathTransformer(jsonpath="$..StoppedAt", replacement="time", replace_reference=False)
61
    )
62
    sfn_snapshot.add_transformer(
×
63
        JsonpathTransformer(jsonpath="$..CreatedAt", replacement="time", replace_reference=False)
64
    )
65
    sfn_snapshot.add_transformer(
×
66
        JsonpathTransformer(
67
            jsonpath="$..PrivateIpv4Address",
68
            replacement="private-ipv4-address",
69
            replace_reference=False,
70
        )
71
    )
72
    return sfn_snapshot
×
73

74

75
@pytest.fixture
1✔
76
def sfn_ecs_snapshot(sfn_snapshot):
1✔
77
    sfn_snapshot.add_transformer(JsonpathTransformer(jsonpath="$..TaskArn", replacement="task_arn"))
×
78
    sfn_snapshot.add_transformer(
×
79
        JsonpathTransformer(jsonpath="$..ContainerArn", replacement="container_arn")
80
    )
81
    sfn_snapshot.add_transformer(
×
82
        JsonpathTransformer(jsonpath="$..PrivateIpv4Address", replacement="private_ipv4_address")
83
    )
84
    sfn_snapshot.add_transformer(
×
85
        JsonpathTransformer(jsonpath="$..RuntimeId", replacement="runtime_id")
86
    )
87
    sfn_snapshot.add_transformer(
×
88
        JsonpathTransformer(jsonpath="$..ImageDigest", replacement="image_digest")
89
    )
90
    sfn_snapshot.add_transformer(
×
91
        JsonpathTransformer(
92
            jsonpath="$..PullStartedAt", replacement="time", replace_reference=False
93
        )
94
    )
95
    sfn_snapshot.add_transformer(
×
96
        JsonpathTransformer(
97
            jsonpath="$..PullStoppedAt", replacement="time", replace_reference=False
98
        )
99
    )
100
    sfn_snapshot.add_transformer(
×
101
        JsonpathTransformer(jsonpath="$..StartedAt", replacement="time", replace_reference=False)
102
    )
103
    sfn_snapshot.add_transformer(
×
104
        JsonpathTransformer(jsonpath="$..StoppedAt", replacement="time", replace_reference=False)
105
    )
106
    sfn_snapshot.add_transformer(
×
107
        JsonpathTransformer(jsonpath="$..StoppingAt", replacement="time", replace_reference=False)
108
    )
109
    sfn_snapshot.add_transformer(
×
110
        JsonpathTransformer(jsonpath="$..CreatedAt", replacement="time", replace_reference=False)
111
    )
112
    sfn_snapshot.add_transformer(
×
113
        JsonpathTransformer(
114
            jsonpath="$..ExecutionStoppedAt", replacement="time", replace_reference=False
115
        )
116
    )
117
    sfn_snapshot.add_transformer(
×
118
        JsonpathTransformer(
119
            jsonpath="$..ConnectivityAt", replacement="time", replace_reference=False
120
        )
121
    )
122
    sfn_snapshot.add_transformer(
×
123
        JsonpathTransformer(
124
            jsonpath="$..PullStartedAt", replacement="time", replace_reference=False
125
        )
126
    )
127
    sfn_snapshot.add_transformer(
×
128
        JsonpathTransformer(
129
            jsonpath="$..PullStoppedAt", replacement="time", replace_reference=False
130
        )
131
    )
132
    sfn_snapshot.add_transformer(RegexTransformer("subnet-[0-9a-zA-Z]+", "subnet_value"))
×
133
    sfn_snapshot.add_transformer(RegexTransformer("sg-[0-9a-zA-Z]+", "sg_value"))
×
134
    sfn_snapshot.add_transformer(RegexTransformer("eni-[0-9a-zA-Z]+", "eni_value"))
×
135
    sfn_snapshot.add_transformer(RegexTransformer("ip-[0-9-]+", "ip_value"))
×
136
    sfn_snapshot.add_transformer(
×
137
        RegexTransformer(":".join(["[0-9a-z][0-9a-z]?[0-9a-z]?"] * 4), "ip_value")
138
    )
139
    sfn_snapshot.add_transformer(RegexTransformer(":".join(["[0-9a-z][0-9a-z]+"] * 6), "mac_value"))
×
140
    return sfn_snapshot
×
141

142

143
@pytest.fixture
1✔
144
def aws_client_no_sync_prefix(aws_client_factory):
1✔
145
    # For StartSyncExecution and TestState calls, boto will prepend "sync-" to the endpoint string.
146
    # As we operate on localhost, this function creates a new stepfunctions client with that functionality disabled.
UNCOV
147
    return aws_client_factory(config=Config(inject_host_prefix=is_aws_cloud()))
×
148

149

150
@pytest.fixture
1✔
151
def mock_config_file():
1✔
UNCOV
152
    tmp_dir = tempfile.mkdtemp()
×
UNCOV
153
    file_path = os.path.join(tmp_dir, "MockConfigFile.json")
×
154

UNCOV
155
    def write_json_to_mock_file(mock_config):
×
UNCOV
156
        with open(file_path, "w") as df:
×
UNCOV
157
            json.dump(mock_config, df)  # noqa
×
UNCOV
158
            df.flush()
×
UNCOV
159
        return file_path
×
160

UNCOV
161
    try:
×
UNCOV
162
        yield write_json_to_mock_file
×
163
    finally:
UNCOV
164
        try:
×
UNCOV
165
            os.remove(file_path)
×
UNCOV
166
        except Exception as ex:
×
UNCOV
167
            LOG.error("Error removing temporary MockConfigFile.json: %s", ex)
×
168
        finally:
UNCOV
169
            shutil.rmtree(
×
170
                tmp_dir,
171
                ignore_errors=True,
172
                onerror=lambda _, path, exc_info: LOG.error(
173
                    "Error removing temporary MockConfigFile.json: %s, %s", path, exc_info
174
                ),
175
            )
176

177

178
@pytest.fixture
1✔
179
def create_state_machine_iam_role(cleanups, create_state_machine):
1✔
UNCOV
180
    def _create(target_aws_client):
×
UNCOV
181
        iam_client = target_aws_client.iam
×
UNCOV
182
        stepfunctions_client = target_aws_client.stepfunctions
×
183

UNCOV
184
        role_name = f"test-sfn-role-{short_uid()}"
×
UNCOV
185
        policy_name = f"test-sfn-policy-{short_uid()}"
×
UNCOV
186
        role = iam_client.create_role(
×
187
            RoleName=role_name,
188
            AssumeRolePolicyDocument=json.dumps(
189
                {
190
                    "Version": "2012-10-17",
191
                    "Statement": [
192
                        {
193
                            "Effect": "Allow",
194
                            "Principal": {"Service": ["states.amazonaws.com"]},
195
                            "Action": ["sts:AssumeRole"],
196
                        }
197
                    ],
198
                }
199
            ),
200
        )
UNCOV
201
        cleanups.append(lambda: iam_client.delete_role(RoleName=role_name))
×
UNCOV
202
        role_arn = role["Role"]["Arn"]
×
203

UNCOV
204
        policy = iam_client.create_policy(
×
205
            PolicyName=policy_name,
206
            PolicyDocument=json.dumps(
207
                {
208
                    "Version": "2012-10-17",
209
                    "Statement": [
210
                        {
211
                            "Effect": "Allow",
212
                            "Action": ["*"],
213
                            "Resource": ["*"],
214
                        }
215
                    ],
216
                }
217
            ),
218
        )
UNCOV
219
        cleanups.append(lambda: iam_client.delete_policy(PolicyArn=policy["Policy"]["Arn"]))
×
UNCOV
220
        iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy["Policy"]["Arn"])
×
UNCOV
221
        cleanups.append(
×
222
            lambda: iam_client.detach_role_policy(
223
                RoleName=role_name, PolicyArn=policy["Policy"]["Arn"]
224
            )
225
        )
226

UNCOV
227
        def _wait_sfn_can_assume_role():
×
228
            sm_name = f"test-wait-sfn-can-assume-role-{short_uid()}"
×
229
            sm_def = {
×
230
                "Comment": "_wait_sfn_can_assume_role",
231
                "StartAt": "PullAssumeRole",
232
                "States": {
233
                    "PullAssumeRole": {
234
                        "Type": "Task",
235
                        "Parameters": {},
236
                        "Resource": "arn:aws:states:::aws-sdk:s3:listBuckets",
237
                        "Catch": [
238
                            {
239
                                "ErrorEquals": ["States.TaskFailed"],
240
                                "Next": "WaitAndPull",
241
                            }
242
                        ],
243
                        "End": True,
244
                    },
245
                    "WaitAndPull": {"Type": "Wait", "Seconds": 5, "Next": "PullAssumeRole"},
246
                },
247
            }
248
            creation_resp = create_state_machine(
×
249
                target_aws_client, name=sm_name, definition=json.dumps(sm_def), roleArn=role_arn
250
            )
251
            state_machine_arn = creation_resp["stateMachineArn"]
×
252

253
            exec_resp = stepfunctions_client.start_execution(
×
254
                stateMachineArn=state_machine_arn, input="{}"
255
            )
256
            execution_arn = exec_resp["executionArn"]
×
257

258
            await_execution_success(
×
259
                stepfunctions_client=stepfunctions_client, execution_arn=execution_arn
260
            )
261

262
            stepfunctions_client.delete_state_machine(stateMachineArn=state_machine_arn)
×
263

UNCOV
264
        if is_aws_cloud():
×
265
            _wait_sfn_can_assume_role()
×
266

UNCOV
267
        return role_arn
×
268

UNCOV
269
    return _create
×
270

271

272
@pytest.fixture
1✔
273
def create_state_machine():
1✔
UNCOV
274
    created_state_machine_references = []
×
275

UNCOV
276
    def _create_state_machine(target_aws_client, **kwargs):
×
UNCOV
277
        sfn_client = target_aws_client.stepfunctions
×
UNCOV
278
        create_output = sfn_client.create_state_machine(**kwargs)
×
UNCOV
279
        create_output_arn = create_output["stateMachineArn"]
×
UNCOV
280
        created_state_machine_references.append(
×
281
            (create_output_arn, kwargs.get("type", StateMachineType.STANDARD), sfn_client)
282
        )
UNCOV
283
        return create_output
×
284

UNCOV
285
    yield _create_state_machine
×
286

287
    # Delete all state machine, attempting to stop all running executions of STANDARD state machines,
288
    # as other types, such as EXPRESS, cannot be manually stopped.
UNCOV
289
    for arn, typ, client in created_state_machine_references:
×
UNCOV
290
        try:
×
UNCOV
291
            if typ == StateMachineType.STANDARD:
×
UNCOV
292
                executions = client.list_executions(stateMachineArn=arn)
×
UNCOV
293
                for execution in executions["executions"]:
×
UNCOV
294
                    client.stop_execution(executionArn=execution["executionArn"])
×
UNCOV
295
            client.delete_state_machine(stateMachineArn=arn)
×
UNCOV
296
        except Exception as ex:
×
UNCOV
297
            LOG.debug("Unable to delete state machine '%s' during cleanup: %s", arn, ex)
×
298

299

300
@pytest.fixture
1✔
301
def create_state_machine_alias():
1✔
UNCOV
302
    state_machine_alias_arn_and_client = []
×
303

UNCOV
304
    def _create_state_machine_alias(target_aws_client, **kwargs):
×
UNCOV
305
        step_functions_client = target_aws_client.stepfunctions
×
UNCOV
306
        create_state_machine_response = step_functions_client.create_state_machine_alias(**kwargs)
×
UNCOV
307
        state_machine_alias_arn_and_client.append(
×
308
            (create_state_machine_response["stateMachineAliasArn"], step_functions_client)
309
        )
UNCOV
310
        return create_state_machine_response
×
311

UNCOV
312
    yield _create_state_machine_alias
×
313

UNCOV
314
    for state_machine_alias_arn, sfn_client in state_machine_alias_arn_and_client:
×
UNCOV
315
        try:
×
UNCOV
316
            sfn_client.delete_state_machine_alias(stateMachineAliasArn=state_machine_alias_arn)
×
317
        except Exception as ex:
×
318
            LOG.debug(
×
319
                "Unable to delete the state machine alias '%s' during cleanup due '%s'",
320
                state_machine_alias_arn,
321
                ex,
322
            )
323

324

325
@pytest.fixture
1✔
326
def create_activity(aws_client):
1✔
UNCOV
327
    activities_arns: Final[list[str]] = []
×
328

UNCOV
329
    def _create_activity(**kwargs):
×
UNCOV
330
        create_output = aws_client.stepfunctions.create_activity(**kwargs)
×
UNCOV
331
        create_output_arn = create_output["activityArn"]
×
UNCOV
332
        activities_arns.append(create_output_arn)
×
UNCOV
333
        return create_output
×
334

UNCOV
335
    yield _create_activity
×
336

UNCOV
337
    for activity_arn in activities_arns:
×
UNCOV
338
        try:
×
UNCOV
339
            aws_client.stepfunctions.delete_activity(activityArn=activity_arn)
×
340
        except Exception:
×
341
            LOG.debug("Unable to delete Activity '%s' during cleanup.", activity_arn)
×
342

343

344
@pytest.fixture
1✔
345
def sqs_send_task_success_state_machine(
1✔
346
    aws_client, create_state_machine, create_state_machine_iam_role
347
):
UNCOV
348
    def _create_state_machine(sqs_queue_url):
×
UNCOV
349
        snf_role_arn = create_state_machine_iam_role(aws_client)
×
UNCOV
350
        sm_name: str = f"sqs_send_task_success_state_machine_{short_uid()}"
×
351

UNCOV
352
        template = {
×
353
            "Comment": "sqs_success_on_task_token",
354
            "StartAt": "Iterate",
355
            "States": {
356
                "Iterate": {
357
                    "Type": "Pass",
358
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
359
                    "ResultPath": "$.Iterator",
360
                    "Next": "IterateStep",
361
                },
362
                "IterateStep": {
363
                    "Type": "Choice",
364
                    "Choices": [
365
                        {
366
                            "Variable": "$.Iterator.Count",
367
                            "NumericLessThanEquals": 0,
368
                            "Next": "NoMoreCycles",
369
                        }
370
                    ],
371
                    "Default": "WaitAndReceive",
372
                },
373
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
374
                "Receive": {
375
                    "Type": "Task",
376
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
377
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
378
                    "ResultPath": "$.SQSOutput",
379
                    "Next": "CheckMessages",
380
                },
381
                "CheckMessages": {
382
                    "Type": "Choice",
383
                    "Choices": [
384
                        {
385
                            "Variable": "$.SQSOutput.Messages",
386
                            "IsPresent": True,
387
                            "Next": "SendSuccesses",
388
                        }
389
                    ],
390
                    "Default": "Iterate",
391
                },
392
                "SendSuccesses": {
393
                    "Type": "Map",
394
                    "InputPath": "$.SQSOutput.Messages",
395
                    "ItemProcessor": {
396
                        "ProcessorConfig": {"Mode": "INLINE"},
397
                        "StartAt": "ParseBody",
398
                        "States": {
399
                            "ParseBody": {
400
                                "Type": "Pass",
401
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
402
                                "Next": "Send",
403
                            },
404
                            "Send": {
405
                                "Type": "Task",
406
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskSuccess",
407
                                "Parameters": {
408
                                    "Output.$": "States.JsonToString($.Body.Message)",
409
                                    "TaskToken.$": "$.Body.TaskToken",
410
                                },
411
                                "End": True,
412
                            },
413
                        },
414
                    },
415
                    "ResultPath": None,
416
                    "Next": "Iterate",
417
                },
418
                "NoMoreCycles": {"Type": "Pass", "End": True},
419
            },
420
        }
421

UNCOV
422
        creation_resp = create_state_machine(
×
423
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
424
        )
UNCOV
425
        state_machine_arn = creation_resp["stateMachineArn"]
×
426

UNCOV
427
        aws_client.stepfunctions.start_execution(
×
428
            stateMachineArn=state_machine_arn,
429
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
430
        )
431

UNCOV
432
    return _create_state_machine
×
433

434

435
@pytest.fixture
1✔
436
def sqs_send_task_failure_state_machine(
1✔
437
    aws_client, create_state_machine, create_state_machine_iam_role
438
):
UNCOV
439
    def _create_state_machine(sqs_queue_url):
×
UNCOV
440
        snf_role_arn = create_state_machine_iam_role(aws_client)
×
UNCOV
441
        sm_name: str = f"sqs_send_task_failure_state_machine_{short_uid()}"
×
442

UNCOV
443
        template = {
×
444
            "Comment": "sqs_failure_on_task_token",
445
            "StartAt": "Iterate",
446
            "States": {
447
                "Iterate": {
448
                    "Type": "Pass",
449
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
450
                    "ResultPath": "$.Iterator",
451
                    "Next": "IterateStep",
452
                },
453
                "IterateStep": {
454
                    "Type": "Choice",
455
                    "Choices": [
456
                        {
457
                            "Variable": "$.Iterator.Count",
458
                            "NumericLessThanEquals": 0,
459
                            "Next": "NoMoreCycles",
460
                        }
461
                    ],
462
                    "Default": "WaitAndReceive",
463
                },
464
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
465
                "Receive": {
466
                    "Type": "Task",
467
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
468
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
469
                    "ResultPath": "$.SQSOutput",
470
                    "Next": "CheckMessages",
471
                },
472
                "CheckMessages": {
473
                    "Type": "Choice",
474
                    "Choices": [
475
                        {
476
                            "Variable": "$.SQSOutput.Messages",
477
                            "IsPresent": True,
478
                            "Next": "SendFailure",
479
                        }
480
                    ],
481
                    "Default": "Iterate",
482
                },
483
                "SendFailure": {
484
                    "Type": "Map",
485
                    "InputPath": "$.SQSOutput.Messages",
486
                    "ItemProcessor": {
487
                        "ProcessorConfig": {"Mode": "INLINE"},
488
                        "StartAt": "ParseBody",
489
                        "States": {
490
                            "ParseBody": {
491
                                "Type": "Pass",
492
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
493
                                "Next": "Send",
494
                            },
495
                            "Send": {
496
                                "Type": "Task",
497
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskFailure",
498
                                "Parameters": {
499
                                    "Error": "Failure error",
500
                                    "Cause": "Failure cause",
501
                                    "TaskToken.$": "$.Body.TaskToken",
502
                                },
503
                                "End": True,
504
                            },
505
                        },
506
                    },
507
                    "ResultPath": None,
508
                    "Next": "Iterate",
509
                },
510
                "NoMoreCycles": {"Type": "Pass", "End": True},
511
            },
512
        }
513

UNCOV
514
        creation_resp = create_state_machine(
×
515
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
516
        )
UNCOV
517
        state_machine_arn = creation_resp["stateMachineArn"]
×
518

UNCOV
519
        aws_client.stepfunctions.start_execution(
×
520
            stateMachineArn=state_machine_arn,
521
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
522
        )
523

UNCOV
524
    return _create_state_machine
×
525

526

527
@pytest.fixture
1✔
528
def sqs_send_heartbeat_and_task_success_state_machine(
1✔
529
    aws_client, create_state_machine, create_state_machine_iam_role
530
):
UNCOV
531
    def _create_state_machine(sqs_queue_url):
×
UNCOV
532
        snf_role_arn = create_state_machine_iam_role(aws_client)
×
UNCOV
533
        sm_name: str = f"sqs_send_heartbeat_and_task_success_state_machine_{short_uid()}"
×
534

UNCOV
535
        template = {
×
536
            "Comment": "SQS_HEARTBEAT_SUCCESS_ON_TASK_TOKEN",
537
            "StartAt": "Iterate",
538
            "States": {
539
                "Iterate": {
540
                    "Type": "Pass",
541
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
542
                    "ResultPath": "$.Iterator",
543
                    "Next": "IterateStep",
544
                },
545
                "IterateStep": {
546
                    "Type": "Choice",
547
                    "Choices": [
548
                        {
549
                            "Variable": "$.Iterator.Count",
550
                            "NumericLessThanEquals": 0,
551
                            "Next": "NoMoreCycles",
552
                        }
553
                    ],
554
                    "Default": "WaitAndReceive",
555
                },
556
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
557
                "Receive": {
558
                    "Type": "Task",
559
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
560
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
561
                    "ResultPath": "$.SQSOutput",
562
                    "Next": "CheckMessages",
563
                },
564
                "CheckMessages": {
565
                    "Type": "Choice",
566
                    "Choices": [
567
                        {
568
                            "Variable": "$.SQSOutput.Messages",
569
                            "IsPresent": True,
570
                            "Next": "SendSuccesses",
571
                        }
572
                    ],
573
                    "Default": "Iterate",
574
                },
575
                "SendSuccesses": {
576
                    "Type": "Map",
577
                    "InputPath": "$.SQSOutput.Messages",
578
                    "ItemProcessor": {
579
                        "ProcessorConfig": {"Mode": "INLINE"},
580
                        "StartAt": "ParseBody",
581
                        "States": {
582
                            "ParseBody": {
583
                                "Type": "Pass",
584
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
585
                                "Next": "WaitBeforeHeartbeat",
586
                            },
587
                            "WaitBeforeHeartbeat": {
588
                                "Type": "Wait",
589
                                "Seconds": 5,
590
                                "Next": "SendHeartbeat",
591
                            },
592
                            "SendHeartbeat": {
593
                                "Type": "Task",
594
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskHeartbeat",
595
                                "Parameters": {"TaskToken.$": "$.Body.TaskToken"},
596
                                "ResultPath": None,
597
                                "Next": "SendSuccess",
598
                            },
599
                            "SendSuccess": {
600
                                "Type": "Task",
601
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskSuccess",
602
                                "Parameters": {
603
                                    "Output.$": "States.JsonToString($.Body.Message)",
604
                                    "TaskToken.$": "$.Body.TaskToken",
605
                                },
606
                                "End": True,
607
                            },
608
                        },
609
                    },
610
                    "ResultPath": None,
611
                    "Next": "Iterate",
612
                },
613
                "NoMoreCycles": {"Type": "Pass", "End": True},
614
            },
615
        }
616

UNCOV
617
        creation_resp = create_state_machine(
×
618
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
619
        )
UNCOV
620
        state_machine_arn = creation_resp["stateMachineArn"]
×
621

UNCOV
622
        aws_client.stepfunctions.start_execution(
×
623
            stateMachineArn=state_machine_arn,
624
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
625
        )
626

UNCOV
627
    return _create_state_machine
×
628

629

630
@pytest.fixture
1✔
631
def sfn_activity_consumer(aws_client, create_state_machine, create_state_machine_iam_role):
1✔
UNCOV
632
    def _create_state_machine(template, activity_arn):
×
UNCOV
633
        snf_role_arn = create_state_machine_iam_role(aws_client)
×
UNCOV
634
        sm_name: str = f"activity_send_task_failure_on_task_{short_uid()}"
×
UNCOV
635
        definition = json.dumps(template)
×
636

UNCOV
637
        creation_resp = create_state_machine(
×
638
            aws_client, name=sm_name, definition=definition, roleArn=snf_role_arn
639
        )
UNCOV
640
        state_machine_arn = creation_resp["stateMachineArn"]
×
641

UNCOV
642
        aws_client.stepfunctions.start_execution(
×
643
            stateMachineArn=state_machine_arn,
644
            input=json.dumps({"ActivityArn": activity_arn}),
645
        )
646

UNCOV
647
    return _create_state_machine
×
648

649

650
@pytest.fixture
1✔
651
def events_to_sqs_queue(events_create_rule, sqs_create_queue, sqs_get_queue_arn, aws_client):
1✔
UNCOV
652
    def _setup(event_pattern):
×
UNCOV
653
        queue_name = f"test-queue-{short_uid()}"
×
UNCOV
654
        rule_name = f"test-rule-{short_uid()}"
×
UNCOV
655
        target_id = f"test-target-{short_uid()}"
×
656

UNCOV
657
        rule_arn = events_create_rule(
×
658
            Name=rule_name, EventBusName="default", EventPattern=event_pattern
659
        )
660

UNCOV
661
        queue_url = sqs_create_queue(QueueName=queue_name)
×
UNCOV
662
        queue_arn = sqs_get_queue_arn(queue_url)
×
UNCOV
663
        queue_policy = {
×
664
            "Statement": [
665
                {
666
                    "Sid": "StepFunctionsEventRule",
667
                    "Resource": queue_arn,
668
                    "Action": "sqs:SendMessage",
669
                    "Principal": {"Service": "events.amazonaws.com"},
670
                    "Condition": {"ArnEquals": {"aws:SourceArn": rule_arn}},
671
                    "Effect": "Allow",
672
                }
673
            ]
674
        }
UNCOV
675
        aws_client.sqs.set_queue_attributes(
×
676
            QueueUrl=queue_url,
677
            Attributes={"Policy": json.dumps(queue_policy), "ReceiveMessageWaitTimeSeconds": "1"},
678
        )
679

UNCOV
680
        aws_client.events.put_targets(Rule=rule_name, Targets=[{"Id": target_id, "Arn": queue_arn}])
×
681

UNCOV
682
        return queue_url
×
683

UNCOV
684
    return _setup
×
685

686

687
@pytest.fixture
1✔
688
def sfn_events_to_sqs_queue(events_to_sqs_queue):
1✔
UNCOV
689
    def _create(state_machine_arn: str) -> str:
×
UNCOV
690
        event_pattern = {
×
691
            "source": ["aws.states"],
692
            "detail": {
693
                "stateMachineArn": [state_machine_arn],
694
            },
695
        }
UNCOV
696
        return events_to_sqs_queue(event_pattern=event_pattern)
×
697

UNCOV
698
    return _create
×
699

700

701
@pytest.fixture
1✔
702
def sfn_glue_create_job(aws_client, create_role, create_policy, wait_and_assume_role):
1✔
703
    job_names = []
×
704

705
    def _execute(**kwargs):
×
706
        job_name = f"glue-job-{short_uid()}"
×
707

708
        assume_role_policy_document = {
×
709
            "Version": "2012-10-17",
710
            "Statement": [
711
                {
712
                    "Effect": "Allow",
713
                    "Principal": {"AWS": "*"},
714
                    "Action": "sts:AssumeRole",
715
                }
716
            ],
717
        }
718
        policy_document = {
×
719
            "Version": "2012-10-17",
720
            "Statement": [
721
                {
722
                    "Effect": "Allow",
723
                    "Action": ["*"],
724
                    "Resource": "*",
725
                },
726
            ],
727
        }
728

729
        role = create_role(AssumeRolePolicyDocument=json.dumps(assume_role_policy_document))
×
730
        role_name = role["Role"]["RoleName"]
×
731
        role_arn = role["Role"]["Arn"]
×
732

733
        policy = create_policy(PolicyDocument=json.dumps(policy_document))
×
734
        policy_arn = policy["Policy"]["Arn"]
×
735

736
        aws_client.iam.attach_role_policy(
×
737
            RoleName=role_name,
738
            PolicyArn=policy_arn,
739
        )
740

741
        wait_and_assume_role(role_arn)
×
742

743
        aws_client.glue.create_job(Name=job_name, Role=role_arn, **kwargs)
×
744

745
        job_names.append(job_name)
×
746
        return job_name
×
747

748
    yield _execute
×
749

750
    for job_name in job_names:
×
751
        try:
×
752
            aws_client.glue.delete_job(JobName=job_name)
×
753
        except Exception as ex:
×
754
            # TODO: the glue provider should not fail on deletion of deleted job, however this is currently the case.
755
            LOG.warning("Could not delete job '%s': %s", job_name, ex)
×
756

757

758
@pytest.fixture
1✔
759
def sfn_create_log_group(aws_client, snapshot):
1✔
UNCOV
760
    log_group_names = []
×
761

UNCOV
762
    def _create() -> str:
×
UNCOV
763
        log_group_name = f"/aws/vendedlogs/states/sfn-test-group-{short_uid()}"
×
UNCOV
764
        snapshot.add_transformer(RegexTransformer(log_group_name, "log_group_name"))
×
UNCOV
765
        aws_client.logs.create_log_group(logGroupName=log_group_name)
×
UNCOV
766
        log_group_names.append(log_group_name)
×
767

UNCOV
768
        return log_group_name
×
769

UNCOV
770
    yield _create
×
771

UNCOV
772
    for log_group_name in log_group_names:
×
UNCOV
773
        try:
×
UNCOV
774
            aws_client.logs.delete_log_group(logGroupName=log_group_name)
×
UNCOV
775
        except Exception:
×
UNCOV
776
            LOG.debug("Cannot delete log group %s", log_group_name)
×
777

778

779
@pytest.fixture
1✔
780
def create_cross_account_admin_role_and_policy(create_state_machine, create_state_machine_iam_role):
1✔
UNCOV
781
    created = []
×
782

UNCOV
783
    def _create_role_and_policy(trusting_aws_client, trusted_aws_client, trusted_account_id) -> str:
×
UNCOV
784
        trusting_iam_client = trusting_aws_client.iam
×
785

UNCOV
786
        role_name = f"admin-test-role-cross-account-{short_uid()}"
×
UNCOV
787
        policy_name = f"admin-test-policy-cross-account-{short_uid()}"
×
788

UNCOV
789
        trust_policy = {
×
790
            "Version": "2012-10-17",
791
            "Statement": [
792
                {
793
                    "Effect": "Allow",
794
                    "Principal": {"AWS": f"arn:aws:iam::{trusted_account_id}:root"},
795
                    "Action": "sts:AssumeRole",
796
                }
797
            ],
798
        }
799

UNCOV
800
        create_role_response = trusting_iam_client.create_role(
×
801
            RoleName=role_name,
802
            AssumeRolePolicyDocument=json.dumps(trust_policy),
803
        )
UNCOV
804
        role_arn = create_role_response["Role"]["Arn"]
×
805

UNCOV
806
        policy_document = {
×
807
            "Version": "2012-10-17",
808
            "Statement": [
809
                {
810
                    "Effect": "Allow",
811
                    "Action": "*",
812
                    "Resource": "*",
813
                }
814
            ],
815
        }
816

UNCOV
817
        trusting_iam_client.put_role_policy(
×
818
            RoleName=role_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
819
        )
820

UNCOV
821
        def _wait_sfn_can_assume_admin_role():
×
822
            trusted_stepfunctions_client = trusted_aws_client.stepfunctions
×
823
            sm_name = f"test-wait-sfn-can-assume-cross-account-admin-role-{short_uid()}"
×
824
            sm_role = create_state_machine_iam_role(trusted_aws_client)
×
825
            sm_def = {
×
826
                "StartAt": "PullAssumeRole",
827
                "States": {
828
                    "PullAssumeRole": {
829
                        "Type": "Task",
830
                        "Parameters": {},
831
                        "Resource": "arn:aws:states:::aws-sdk:s3:listBuckets",
832
                        "Credentials": {"RoleArn": role_arn},
833
                        "Retry": [
834
                            {
835
                                "ErrorEquals": ["States.ALL"],
836
                                "IntervalSeconds": 2,
837
                                "MaxAttempts": 60,
838
                            }
839
                        ],
840
                        "End": True,
841
                    }
842
                },
843
            }
844
            creation_response = create_state_machine(
×
845
                trusted_aws_client, name=sm_name, definition=json.dumps(sm_def), roleArn=sm_role
846
            )
847
            state_machine_arn = creation_response["stateMachineArn"]
×
848

849
            exec_resp = trusted_stepfunctions_client.start_execution(
×
850
                stateMachineArn=state_machine_arn, input="{}"
851
            )
852
            execution_arn = exec_resp["executionArn"]
×
853

854
            await_execution_success(
×
855
                stepfunctions_client=trusted_stepfunctions_client, execution_arn=execution_arn
856
            )
857

858
            trusted_stepfunctions_client.delete_state_machine(stateMachineArn=state_machine_arn)
×
859

UNCOV
860
        if is_aws_cloud():
×
861
            _wait_sfn_can_assume_admin_role()
×
862

UNCOV
863
        return role_arn
×
864

UNCOV
865
    yield _create_role_and_policy
×
866

UNCOV
867
    for aws_client, role_name, policy_name in created:
×
868
        aws_client.iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
×
869
        aws_client.iam.delete_role(RoleName=role_name)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc