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

localstack / localstack / a8850804-5a35-4cf0-98c0-1c624f795040

10 Mar 2025 11:34PM UTC coverage: 86.929% (+0.05%) from 86.877%
a8850804-5a35-4cf0-98c0-1c624f795040

push

circleci

web-flow
Update CODEOWNERS (#12359)

Co-authored-by: LocalStack Bot <localstack-bot@users.noreply.github.com>

62138 of 71481 relevant lines covered (86.93%)

0.87 hits per line

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

68.08
/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py
1
import json
1✔
2
import logging
1✔
3
from typing import Final
1✔
4

5
import pytest
1✔
6
from botocore.config import Config
1✔
7
from localstack_snapshot.snapshots.transformer import (
1✔
8
    JsonpathTransformer,
9
    RegexTransformer,
10
)
11

12
from localstack.aws.api.stepfunctions import StateMachineType
1✔
13
from localstack.testing.aws.util import is_aws_cloud
1✔
14
from localstack.testing.pytest.stepfunctions.utils import await_execution_success
1✔
15
from localstack.utils.strings import short_uid
1✔
16

17
LOG = logging.getLogger(__name__)
1✔
18

19

20
@pytest.fixture
1✔
21
def sfn_snapshot(snapshot):
1✔
22
    snapshot.add_transformers_list(snapshot.transform.stepfunctions_api())
1✔
23
    return snapshot
1✔
24

25

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

71

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

139

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

146

147
@pytest.fixture
1✔
148
def create_state_machine_iam_role(cleanups, create_state_machine):
1✔
149
    def _create(target_aws_client):
1✔
150
        iam_client = target_aws_client.iam
1✔
151
        stepfunctions_client = target_aws_client.stepfunctions
1✔
152

153
        role_name = f"test-sfn-role-{short_uid()}"
1✔
154
        policy_name = f"test-sfn-policy-{short_uid()}"
1✔
155
        role = iam_client.create_role(
1✔
156
            RoleName=role_name,
157
            AssumeRolePolicyDocument=json.dumps(
158
                {
159
                    "Version": "2012-10-17",
160
                    "Statement": [
161
                        {
162
                            "Effect": "Allow",
163
                            "Principal": {"Service": ["states.amazonaws.com"]},
164
                            "Action": ["sts:AssumeRole"],
165
                        }
166
                    ],
167
                }
168
            ),
169
        )
170
        cleanups.append(lambda: iam_client.delete_role(RoleName=role_name))
1✔
171
        role_arn = role["Role"]["Arn"]
1✔
172

173
        policy = iam_client.create_policy(
1✔
174
            PolicyName=policy_name,
175
            PolicyDocument=json.dumps(
176
                {
177
                    "Version": "2012-10-17",
178
                    "Statement": [
179
                        {
180
                            "Effect": "Allow",
181
                            "Action": ["*"],
182
                            "Resource": ["*"],
183
                        }
184
                    ],
185
                }
186
            ),
187
        )
188
        cleanups.append(lambda: iam_client.delete_policy(PolicyArn=policy["Policy"]["Arn"]))
1✔
189
        iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy["Policy"]["Arn"])
1✔
190
        cleanups.append(
1✔
191
            lambda: iam_client.detach_role_policy(
192
                RoleName=role_name, PolicyArn=policy["Policy"]["Arn"]
193
            )
194
        )
195

196
        def _wait_sfn_can_assume_role():
1✔
197
            sm_name = f"test-wait-sfn-can-assume-role-{short_uid()}"
×
198
            sm_def = {
×
199
                "Comment": "_wait_sfn_can_assume_role",
200
                "StartAt": "PullAssumeRole",
201
                "States": {
202
                    "PullAssumeRole": {
203
                        "Type": "Task",
204
                        "Parameters": {},
205
                        "Resource": "arn:aws:states:::aws-sdk:s3:listBuckets",
206
                        "Catch": [
207
                            {
208
                                "ErrorEquals": ["States.TaskFailed"],
209
                                "Next": "WaitAndPull",
210
                            }
211
                        ],
212
                        "End": True,
213
                    },
214
                    "WaitAndPull": {"Type": "Wait", "Seconds": 5, "Next": "PullAssumeRole"},
215
                },
216
            }
217
            creation_resp = create_state_machine(
×
218
                target_aws_client, name=sm_name, definition=json.dumps(sm_def), roleArn=role_arn
219
            )
220
            state_machine_arn = creation_resp["stateMachineArn"]
×
221

222
            exec_resp = stepfunctions_client.start_execution(
×
223
                stateMachineArn=state_machine_arn, input="{}"
224
            )
225
            execution_arn = exec_resp["executionArn"]
×
226

227
            await_execution_success(
×
228
                stepfunctions_client=stepfunctions_client, execution_arn=execution_arn
229
            )
230

231
            stepfunctions_client.delete_state_machine(stateMachineArn=state_machine_arn)
×
232

233
        if is_aws_cloud():
1✔
234
            _wait_sfn_can_assume_role()
×
235

236
        return role_arn
1✔
237

238
    return _create
1✔
239

240

241
@pytest.fixture
1✔
242
def create_state_machine():
1✔
243
    created_state_machine_references = list()
1✔
244

245
    def _create_state_machine(target_aws_client, **kwargs):
1✔
246
        sfn_client = target_aws_client.stepfunctions
1✔
247
        create_output = sfn_client.create_state_machine(**kwargs)
1✔
248
        create_output_arn = create_output["stateMachineArn"]
1✔
249
        created_state_machine_references.append(
1✔
250
            (create_output_arn, kwargs.get("type", StateMachineType.STANDARD), sfn_client)
251
        )
252
        return create_output
1✔
253

254
    yield _create_state_machine
1✔
255

256
    # Delete all state machine, attempting to stop all running executions of STANDARD state machines,
257
    # as other types, such as EXPRESS, cannot be manually stopped.
258
    for arn, typ, client in created_state_machine_references:
1✔
259
        try:
1✔
260
            if typ == StateMachineType.STANDARD:
1✔
261
                executions = client.list_executions(stateMachineArn=arn)
1✔
262
                for execution in executions["executions"]:
1✔
263
                    client.stop_execution(executionArn=execution["executionArn"])
1✔
264
            client.delete_state_machine(stateMachineArn=arn)
1✔
265
        except Exception as ex:
1✔
266
            LOG.debug("Unable to delete state machine '%s' during cleanup: %s", arn, ex)
1✔
267

268

269
@pytest.fixture
1✔
270
def create_state_machine_alias():
1✔
271
    state_machine_alias_arn_and_client = list()
1✔
272

273
    def _create_state_machine_alias(target_aws_client, **kwargs):
1✔
274
        step_functions_client = target_aws_client.stepfunctions
1✔
275
        create_state_machine_response = step_functions_client.create_state_machine_alias(**kwargs)
1✔
276
        state_machine_alias_arn_and_client.append(
1✔
277
            (create_state_machine_response["stateMachineAliasArn"], step_functions_client)
278
        )
279
        return create_state_machine_response
1✔
280

281
    yield _create_state_machine_alias
1✔
282

283
    for state_machine_alias_arn, sfn_client in state_machine_alias_arn_and_client:
1✔
284
        try:
1✔
285
            sfn_client.delete_state_machine_alias(stateMachineAliasArn=state_machine_alias_arn)
1✔
286
        except Exception as ex:
×
287
            LOG.debug(
×
288
                "Unable to delete the state machine alias '%s' during cleanup due '%s'",
289
                state_machine_alias_arn,
290
                ex,
291
            )
292

293

294
@pytest.fixture
1✔
295
def create_activity(aws_client):
1✔
296
    activities_arns: Final[list[str]] = list()
1✔
297

298
    def _create_activity(**kwargs):
1✔
299
        create_output = aws_client.stepfunctions.create_activity(**kwargs)
1✔
300
        create_output_arn = create_output["activityArn"]
1✔
301
        activities_arns.append(create_output_arn)
1✔
302
        return create_output
1✔
303

304
    yield _create_activity
1✔
305

306
    for activity_arn in activities_arns:
1✔
307
        try:
1✔
308
            aws_client.stepfunctions.delete_activity(activityArn=activity_arn)
1✔
309
        except Exception:
×
310
            LOG.debug("Unable to delete Activity '%s' during cleanup.", activity_arn)
×
311

312

313
@pytest.fixture
1✔
314
def sqs_send_task_success_state_machine(
1✔
315
    aws_client, create_state_machine, create_state_machine_iam_role
316
):
317
    def _create_state_machine(sqs_queue_url):
1✔
318
        snf_role_arn = create_state_machine_iam_role(aws_client)
1✔
319
        sm_name: str = f"sqs_send_task_success_state_machine_{short_uid()}"
1✔
320

321
        template = {
1✔
322
            "Comment": "sqs_success_on_task_token",
323
            "StartAt": "Iterate",
324
            "States": {
325
                "Iterate": {
326
                    "Type": "Pass",
327
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
328
                    "ResultPath": "$.Iterator",
329
                    "Next": "IterateStep",
330
                },
331
                "IterateStep": {
332
                    "Type": "Choice",
333
                    "Choices": [
334
                        {
335
                            "Variable": "$.Iterator.Count",
336
                            "NumericLessThanEquals": 0,
337
                            "Next": "NoMoreCycles",
338
                        }
339
                    ],
340
                    "Default": "WaitAndReceive",
341
                },
342
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
343
                "Receive": {
344
                    "Type": "Task",
345
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
346
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
347
                    "ResultPath": "$.SQSOutput",
348
                    "Next": "CheckMessages",
349
                },
350
                "CheckMessages": {
351
                    "Type": "Choice",
352
                    "Choices": [
353
                        {
354
                            "Variable": "$.SQSOutput.Messages",
355
                            "IsPresent": True,
356
                            "Next": "SendSuccesses",
357
                        }
358
                    ],
359
                    "Default": "Iterate",
360
                },
361
                "SendSuccesses": {
362
                    "Type": "Map",
363
                    "InputPath": "$.SQSOutput.Messages",
364
                    "ItemProcessor": {
365
                        "ProcessorConfig": {"Mode": "INLINE"},
366
                        "StartAt": "ParseBody",
367
                        "States": {
368
                            "ParseBody": {
369
                                "Type": "Pass",
370
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
371
                                "Next": "Send",
372
                            },
373
                            "Send": {
374
                                "Type": "Task",
375
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskSuccess",
376
                                "Parameters": {
377
                                    "Output.$": "States.JsonToString($.Body.Message)",
378
                                    "TaskToken.$": "$.Body.TaskToken",
379
                                },
380
                                "End": True,
381
                            },
382
                        },
383
                    },
384
                    "ResultPath": None,
385
                    "Next": "Iterate",
386
                },
387
                "NoMoreCycles": {"Type": "Pass", "End": True},
388
            },
389
        }
390

391
        creation_resp = create_state_machine(
1✔
392
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
393
        )
394
        state_machine_arn = creation_resp["stateMachineArn"]
1✔
395

396
        aws_client.stepfunctions.start_execution(
1✔
397
            stateMachineArn=state_machine_arn,
398
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
399
        )
400

401
    return _create_state_machine
1✔
402

403

404
@pytest.fixture
1✔
405
def sqs_send_task_failure_state_machine(
1✔
406
    aws_client, create_state_machine, create_state_machine_iam_role
407
):
408
    def _create_state_machine(sqs_queue_url):
1✔
409
        snf_role_arn = create_state_machine_iam_role(aws_client)
1✔
410
        sm_name: str = f"sqs_send_task_failure_state_machine_{short_uid()}"
1✔
411

412
        template = {
1✔
413
            "Comment": "sqs_failure_on_task_token",
414
            "StartAt": "Iterate",
415
            "States": {
416
                "Iterate": {
417
                    "Type": "Pass",
418
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
419
                    "ResultPath": "$.Iterator",
420
                    "Next": "IterateStep",
421
                },
422
                "IterateStep": {
423
                    "Type": "Choice",
424
                    "Choices": [
425
                        {
426
                            "Variable": "$.Iterator.Count",
427
                            "NumericLessThanEquals": 0,
428
                            "Next": "NoMoreCycles",
429
                        }
430
                    ],
431
                    "Default": "WaitAndReceive",
432
                },
433
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
434
                "Receive": {
435
                    "Type": "Task",
436
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
437
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
438
                    "ResultPath": "$.SQSOutput",
439
                    "Next": "CheckMessages",
440
                },
441
                "CheckMessages": {
442
                    "Type": "Choice",
443
                    "Choices": [
444
                        {
445
                            "Variable": "$.SQSOutput.Messages",
446
                            "IsPresent": True,
447
                            "Next": "SendFailure",
448
                        }
449
                    ],
450
                    "Default": "Iterate",
451
                },
452
                "SendFailure": {
453
                    "Type": "Map",
454
                    "InputPath": "$.SQSOutput.Messages",
455
                    "ItemProcessor": {
456
                        "ProcessorConfig": {"Mode": "INLINE"},
457
                        "StartAt": "ParseBody",
458
                        "States": {
459
                            "ParseBody": {
460
                                "Type": "Pass",
461
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
462
                                "Next": "Send",
463
                            },
464
                            "Send": {
465
                                "Type": "Task",
466
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskFailure",
467
                                "Parameters": {
468
                                    "Error": "Failure error",
469
                                    "Cause": "Failure cause",
470
                                    "TaskToken.$": "$.Body.TaskToken",
471
                                },
472
                                "End": True,
473
                            },
474
                        },
475
                    },
476
                    "ResultPath": None,
477
                    "Next": "Iterate",
478
                },
479
                "NoMoreCycles": {"Type": "Pass", "End": True},
480
            },
481
        }
482

483
        creation_resp = create_state_machine(
1✔
484
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
485
        )
486
        state_machine_arn = creation_resp["stateMachineArn"]
1✔
487

488
        aws_client.stepfunctions.start_execution(
1✔
489
            stateMachineArn=state_machine_arn,
490
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
491
        )
492

493
    return _create_state_machine
1✔
494

495

496
@pytest.fixture
1✔
497
def sqs_send_heartbeat_and_task_success_state_machine(
1✔
498
    aws_client, create_state_machine, create_state_machine_iam_role
499
):
500
    def _create_state_machine(sqs_queue_url):
1✔
501
        snf_role_arn = create_state_machine_iam_role(aws_client)
1✔
502
        sm_name: str = f"sqs_send_heartbeat_and_task_success_state_machine_{short_uid()}"
1✔
503

504
        template = {
1✔
505
            "Comment": "SQS_HEARTBEAT_SUCCESS_ON_TASK_TOKEN",
506
            "StartAt": "Iterate",
507
            "States": {
508
                "Iterate": {
509
                    "Type": "Pass",
510
                    "Parameters": {"Count.$": "States.MathAdd($.Iterator.Count, -1)"},
511
                    "ResultPath": "$.Iterator",
512
                    "Next": "IterateStep",
513
                },
514
                "IterateStep": {
515
                    "Type": "Choice",
516
                    "Choices": [
517
                        {
518
                            "Variable": "$.Iterator.Count",
519
                            "NumericLessThanEquals": 0,
520
                            "Next": "NoMoreCycles",
521
                        }
522
                    ],
523
                    "Default": "WaitAndReceive",
524
                },
525
                "WaitAndReceive": {"Type": "Wait", "Seconds": 1, "Next": "Receive"},
526
                "Receive": {
527
                    "Type": "Task",
528
                    "Parameters": {"QueueUrl.$": "$.QueueUrl"},
529
                    "Resource": "arn:aws:states:::aws-sdk:sqs:receiveMessage",
530
                    "ResultPath": "$.SQSOutput",
531
                    "Next": "CheckMessages",
532
                },
533
                "CheckMessages": {
534
                    "Type": "Choice",
535
                    "Choices": [
536
                        {
537
                            "Variable": "$.SQSOutput.Messages",
538
                            "IsPresent": True,
539
                            "Next": "SendSuccesses",
540
                        }
541
                    ],
542
                    "Default": "Iterate",
543
                },
544
                "SendSuccesses": {
545
                    "Type": "Map",
546
                    "InputPath": "$.SQSOutput.Messages",
547
                    "ItemProcessor": {
548
                        "ProcessorConfig": {"Mode": "INLINE"},
549
                        "StartAt": "ParseBody",
550
                        "States": {
551
                            "ParseBody": {
552
                                "Type": "Pass",
553
                                "Parameters": {"Body.$": "States.StringToJson($.Body)"},
554
                                "Next": "WaitBeforeHeartbeat",
555
                            },
556
                            "WaitBeforeHeartbeat": {
557
                                "Type": "Wait",
558
                                "Seconds": 5,
559
                                "Next": "SendHeartbeat",
560
                            },
561
                            "SendHeartbeat": {
562
                                "Type": "Task",
563
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskHeartbeat",
564
                                "Parameters": {"TaskToken.$": "$.Body.TaskToken"},
565
                                "ResultPath": None,
566
                                "Next": "SendSuccess",
567
                            },
568
                            "SendSuccess": {
569
                                "Type": "Task",
570
                                "Resource": "arn:aws:states:::aws-sdk:sfn:sendTaskSuccess",
571
                                "Parameters": {
572
                                    "Output.$": "States.JsonToString($.Body.Message)",
573
                                    "TaskToken.$": "$.Body.TaskToken",
574
                                },
575
                                "End": True,
576
                            },
577
                        },
578
                    },
579
                    "ResultPath": None,
580
                    "Next": "Iterate",
581
                },
582
                "NoMoreCycles": {"Type": "Pass", "End": True},
583
            },
584
        }
585

586
        creation_resp = create_state_machine(
1✔
587
            aws_client, name=sm_name, definition=json.dumps(template), roleArn=snf_role_arn
588
        )
589
        state_machine_arn = creation_resp["stateMachineArn"]
1✔
590

591
        aws_client.stepfunctions.start_execution(
1✔
592
            stateMachineArn=state_machine_arn,
593
            input=json.dumps({"QueueUrl": sqs_queue_url, "Iterator": {"Count": 300}}),
594
        )
595

596
    return _create_state_machine
1✔
597

598

599
@pytest.fixture
1✔
600
def sfn_activity_consumer(aws_client, create_state_machine, create_state_machine_iam_role):
1✔
601
    def _create_state_machine(template, activity_arn):
1✔
602
        snf_role_arn = create_state_machine_iam_role(aws_client)
1✔
603
        sm_name: str = f"activity_send_task_failure_on_task_{short_uid()}"
1✔
604
        definition = json.dumps(template)
1✔
605

606
        creation_resp = create_state_machine(
1✔
607
            aws_client, name=sm_name, definition=definition, roleArn=snf_role_arn
608
        )
609
        state_machine_arn = creation_resp["stateMachineArn"]
1✔
610

611
        aws_client.stepfunctions.start_execution(
1✔
612
            stateMachineArn=state_machine_arn,
613
            input=json.dumps({"ActivityArn": activity_arn}),
614
        )
615

616
    return _create_state_machine
1✔
617

618

619
@pytest.fixture
1✔
620
def events_to_sqs_queue(events_create_rule, sqs_create_queue, sqs_get_queue_arn, aws_client):
1✔
621
    def _setup(event_pattern):
1✔
622
        queue_name = f"test-queue-{short_uid()}"
1✔
623
        rule_name = f"test-rule-{short_uid()}"
1✔
624
        target_id = f"test-target-{short_uid()}"
1✔
625

626
        rule_arn = events_create_rule(
1✔
627
            Name=rule_name, EventBusName="default", EventPattern=event_pattern
628
        )
629

630
        queue_url = sqs_create_queue(QueueName=queue_name)
1✔
631
        queue_arn = sqs_get_queue_arn(queue_url)
1✔
632
        queue_policy = {
1✔
633
            "Statement": [
634
                {
635
                    "Sid": "StepFunctionsEventRule",
636
                    "Resource": queue_arn,
637
                    "Action": "sqs:SendMessage",
638
                    "Principal": {"Service": "events.amazonaws.com"},
639
                    "Condition": {"ArnEquals": {"aws:SourceArn": rule_arn}},
640
                    "Effect": "Allow",
641
                }
642
            ]
643
        }
644
        aws_client.sqs.set_queue_attributes(
1✔
645
            QueueUrl=queue_url,
646
            Attributes={"Policy": json.dumps(queue_policy), "ReceiveMessageWaitTimeSeconds": "1"},
647
        )
648

649
        aws_client.events.put_targets(Rule=rule_name, Targets=[{"Id": target_id, "Arn": queue_arn}])
1✔
650

651
        return queue_url
1✔
652

653
    return _setup
1✔
654

655

656
@pytest.fixture
1✔
657
def sfn_events_to_sqs_queue(events_to_sqs_queue):
1✔
658
    def _create(state_machine_arn: str) -> str:
1✔
659
        event_pattern = {
1✔
660
            "source": ["aws.states"],
661
            "detail": {
662
                "stateMachineArn": [state_machine_arn],
663
            },
664
        }
665
        return events_to_sqs_queue(event_pattern=event_pattern)
1✔
666

667
    return _create
1✔
668

669

670
@pytest.fixture
1✔
671
def sfn_glue_create_job(aws_client, create_role, create_policy, wait_and_assume_role):
1✔
672
    job_names = []
×
673

674
    def _execute(**kwargs):
×
675
        job_name = f"glue-job-{short_uid()}"
×
676

677
        assume_role_policy_document = {
×
678
            "Version": "2012-10-17",
679
            "Statement": [
680
                {
681
                    "Effect": "Allow",
682
                    "Principal": {"AWS": "*"},
683
                    "Action": "sts:AssumeRole",
684
                }
685
            ],
686
        }
687
        policy_document = {
×
688
            "Version": "2012-10-17",
689
            "Statement": [
690
                {
691
                    "Effect": "Allow",
692
                    "Action": ["*"],
693
                    "Resource": "*",
694
                },
695
            ],
696
        }
697

698
        role = create_role(AssumeRolePolicyDocument=json.dumps(assume_role_policy_document))
×
699
        role_name = role["Role"]["RoleName"]
×
700
        role_arn = role["Role"]["Arn"]
×
701

702
        policy = create_policy(PolicyDocument=json.dumps(policy_document))
×
703
        policy_arn = policy["Policy"]["Arn"]
×
704

705
        aws_client.iam.attach_role_policy(
×
706
            RoleName=role_name,
707
            PolicyArn=policy_arn,
708
        )
709

710
        wait_and_assume_role(role_arn)
×
711

712
        aws_client.glue.create_job(Name=job_name, Role=role_arn, **kwargs)
×
713

714
        job_names.append(job_name)
×
715
        return job_name
×
716

717
    yield _execute
×
718

719
    for job_name in job_names:
×
720
        try:
×
721
            aws_client.glue.delete_job(JobName=job_name)
×
722
        except Exception as ex:
×
723
            # TODO: the glue provider should not fail on deletion of deleted job, however this is currently the case.
724
            LOG.warning("Could not delete job '%s': %s", job_name, ex)
×
725

726

727
@pytest.fixture
1✔
728
def sfn_create_log_group(aws_client, snapshot):
1✔
729
    log_group_names = []
1✔
730

731
    def _create() -> str:
1✔
732
        log_group_name = f"/aws/vendedlogs/states/sfn-test-group-{short_uid()}"
1✔
733
        snapshot.add_transformer(RegexTransformer(log_group_name, "log_group_name"))
1✔
734
        aws_client.logs.create_log_group(logGroupName=log_group_name)
1✔
735
        log_group_names.append(log_group_name)
1✔
736

737
        return log_group_name
1✔
738

739
    yield _create
1✔
740

741
    for log_group_name in log_group_names:
1✔
742
        try:
1✔
743
            aws_client.logs.delete_log_group(logGroupName=log_group_name)
1✔
744
        except Exception:
1✔
745
            LOG.debug("Cannot delete log group %s", log_group_name)
1✔
746

747

748
@pytest.fixture
1✔
749
def create_cross_account_admin_role_and_policy(create_state_machine, create_state_machine_iam_role):
1✔
750
    created = list()
1✔
751

752
    def _create_role_and_policy(trusting_aws_client, trusted_aws_client, trusted_account_id) -> str:
1✔
753
        trusting_iam_client = trusting_aws_client.iam
1✔
754

755
        role_name = f"admin-test-role-cross-account-{short_uid()}"
1✔
756
        policy_name = f"admin-test-policy-cross-account-{short_uid()}"
1✔
757

758
        trust_policy = {
1✔
759
            "Version": "2012-10-17",
760
            "Statement": [
761
                {
762
                    "Effect": "Allow",
763
                    "Principal": {"AWS": f"arn:aws:iam::{trusted_account_id}:root"},
764
                    "Action": "sts:AssumeRole",
765
                }
766
            ],
767
        }
768

769
        create_role_response = trusting_iam_client.create_role(
1✔
770
            RoleName=role_name,
771
            AssumeRolePolicyDocument=json.dumps(trust_policy),
772
        )
773
        role_arn = create_role_response["Role"]["Arn"]
1✔
774

775
        policy_document = {
1✔
776
            "Version": "2012-10-17",
777
            "Statement": [
778
                {
779
                    "Effect": "Allow",
780
                    "Action": "*",
781
                    "Resource": "*",
782
                }
783
            ],
784
        }
785

786
        trusting_iam_client.put_role_policy(
1✔
787
            RoleName=role_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
788
        )
789

790
        def _wait_sfn_can_assume_admin_role():
1✔
791
            trusted_stepfunctions_client = trusted_aws_client.stepfunctions
×
792
            sm_name = f"test-wait-sfn-can-assume-cross-account-admin-role-{short_uid()}"
×
793
            sm_role = create_state_machine_iam_role(trusted_aws_client)
×
794
            sm_def = {
×
795
                "StartAt": "PullAssumeRole",
796
                "States": {
797
                    "PullAssumeRole": {
798
                        "Type": "Task",
799
                        "Parameters": {},
800
                        "Resource": "arn:aws:states:::aws-sdk:s3:listBuckets",
801
                        "Credentials": {"RoleArn": role_arn},
802
                        "Retry": [
803
                            {
804
                                "ErrorEquals": ["States.ALL"],
805
                                "IntervalSeconds": 2,
806
                                "MaxAttempts": 60,
807
                            }
808
                        ],
809
                        "End": True,
810
                    }
811
                },
812
            }
813
            creation_response = create_state_machine(
×
814
                trusted_aws_client, name=sm_name, definition=json.dumps(sm_def), roleArn=sm_role
815
            )
816
            state_machine_arn = creation_response["stateMachineArn"]
×
817

818
            exec_resp = trusted_stepfunctions_client.start_execution(
×
819
                stateMachineArn=state_machine_arn, input="{}"
820
            )
821
            execution_arn = exec_resp["executionArn"]
×
822

823
            await_execution_success(
×
824
                stepfunctions_client=trusted_stepfunctions_client, execution_arn=execution_arn
825
            )
826

827
            trusted_stepfunctions_client.delete_state_machine(stateMachineArn=state_machine_arn)
×
828

829
        if is_aws_cloud():
1✔
830
            _wait_sfn_can_assume_admin_role()
×
831

832
        return role_arn
1✔
833

834
    yield _create_role_and_policy
1✔
835

836
    for aws_client, role_name, policy_name in created:
1✔
837
        aws_client.iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
×
838
        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