Skip to content

Commit c303bf3

Browse files
committed
feat: add stepfunctions contract tests
1 parent 05b8f16 commit c303bf3

File tree

2 files changed

+177
-1
lines changed

2 files changed

+177
-1
lines changed

contract-tests/images/applications/botocore/botocore_server.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import requests
1313
from botocore.client import BaseClient
1414
from botocore.config import Config
15+
from botocore.exceptions import ClientError
1516
from typing_extensions import Tuple, override
1617

1718
_PORT: int = 8080
@@ -47,6 +48,8 @@ def do_GET(self):
4748
self._handle_bedrock_request()
4849
if self.in_path("secretsmanager"):
4950
self._handle_secretsmanager_request()
51+
if self.in_path("stepfunctions"):
52+
self._handle_stepfunctions_request()
5053

5154
self._end_request(self.main_status)
5255

@@ -329,6 +332,37 @@ def _handle_secretsmanager_request(self) -> None:
329332
else:
330333
set_main_status(404)
331334

335+
def _handle_stepfunctions_request(self) -> None:
336+
sfn_client = boto3.client("stepfunctions", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
337+
if self.in_path(_ERROR):
338+
set_main_status(400)
339+
try:
340+
error_client = boto3.client("stepfunctions", endpoint_url=_ERROR_ENDPOINT, region_name=_AWS_REGION)
341+
error_client.describe_state_machine(stateMachineArn="arn:aws:states:us-west-2:000000000000:stateMachine:unExistStateMachine")
342+
except Exception as exception:
343+
print("Expected exception occurred", exception)
344+
elif self.in_path(_FAULT):
345+
set_main_status(500)
346+
try:
347+
fault_client = boto3.client("stepfunctions", endpoint_url=_FAULT_ENDPOINT, region_name=_AWS_REGION)
348+
fault_client.meta.events.register(
349+
"before-call.stepfunctions.ListStateMachineVersions",
350+
lambda **kwargs: inject_500_error("ListStateMachineVersions", **kwargs),
351+
)
352+
fault_client.list_state_machine_versions(
353+
stateMachineArn="arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine",
354+
)
355+
except Exception as exception:
356+
print("Expected exception occurred", exception)
357+
elif self.in_path("describestatemachine/my-state-machine"):
358+
set_main_status(200)
359+
sfn_client.describe_state_machine(stateMachineArn="arn:aws:states:us-west-2:000000000000:stateMachine:testStateMachine")
360+
elif self.in_path("describeactivity/my-activity"):
361+
set_main_status(200)
362+
sfn_client.describe_activity(activityArn="arn:aws:states:us-west-2:000000000000:activity:testActivity")
363+
else:
364+
set_main_status(404)
365+
332366
def _end_request(self, status_code: int):
333367
self.send_response_only(status_code)
334368
self.end_headers()
@@ -383,6 +417,60 @@ def prepare_aws_server() -> None:
383417
Name="testSecret", SecretString="secretValue", Description="This is a test secret"
384418
)
385419

420+
# Set up Step Functions so tests can access a state machine and activity.
421+
sfn_client: BaseClient = boto3.client("stepfunctions", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
422+
sfn_response = sfn_client.list_state_machines()
423+
state_machine_name = "testStateMachine"
424+
activity_name = "testActivity"
425+
state_machine = next((st for st in sfn_response["stateMachines"] if st["name"] == state_machine_name), None)
426+
if not state_machine:
427+
# create state machine needs an iam role so we create it here
428+
iam_client: BaseClient = boto3.client("iam", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
429+
iam_role_name = "testRole"
430+
iam_role_arn = None
431+
trust_policy = {
432+
"Version": "2012-10-17",
433+
"Statement": [
434+
{
435+
"Effect": "Allow",
436+
"Principal": {
437+
"Service": "states.amazonaws.com"
438+
},
439+
"Action": "sts:AssumeRole"
440+
}
441+
]
442+
}
443+
try:
444+
iam_response = iam_client.create_role(
445+
RoleName=iam_role_name, AssumeRolePolicyDocument=json.dumps(trust_policy)
446+
)
447+
iam_client.attach_role_policy(
448+
RoleName=iam_role_name,
449+
PolicyArn="arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess"
450+
)
451+
print(f"IAM Role '{iam_role_name}' create successfully.")
452+
iam_role_arn = iam_response["Role"]["Arn"]
453+
sfn_defintion = {
454+
"Comment": "A simple sequential workflow",
455+
"StartAt": "FirstState",
456+
"States": {
457+
"FirstState": {
458+
"Type": "Pass",
459+
"Result": "Hello, World!",
460+
"End": True
461+
}
462+
}
463+
}
464+
definition_string = json.dumps(sfn_defintion)
465+
sfn_client.create_state_machine(
466+
name=state_machine_name,
467+
definition=definition_string,
468+
roleArn=iam_role_arn
469+
)
470+
sfn_client.create_activity(name=activity_name)
471+
except Exception as exception:
472+
print("Something went wrong with Step Functions setup", exception)
473+
386474
except Exception as exception:
387475
print("Unexpected exception occurred", exception)
388476

@@ -412,6 +500,16 @@ def inject_200_success(**kwargs):
412500
return http_response, response_body
413501

414502

503+
def inject_500_error(api_name: str, **kwargs):
504+
raise ClientError(
505+
{
506+
"Error": {"Code": "InternalServerError", "Message": "Internal Server Error"},
507+
"ResponseMetadata": {"HTTPStatusCode": 500, "RequestId": "mock-request-id"},
508+
},
509+
api_name,
510+
)
511+
512+
415513
def main() -> None:
416514
prepare_aws_server()
417515
server_address: Tuple[str, int] = ("0.0.0.0", _PORT)

contract-tests/tests/test/amazon/botocore/botocore_test.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
_AWS_BEDROCK_DATA_SOURCE_ID: str = "aws.bedrock.data_source.id"
3737
_GEN_AI_REQUEST_MODEL: str = "gen_ai.request.model"
3838
_AWS_SECRET_ARN: str = "aws.secretsmanager.secret.arn"
39+
_AWS_STATE_MACHINE_ARN: str = "aws.stepfunctions.state_machine.arn"
40+
_AWS_ACTIVITY_ARN: str = "aws.stepfunctions.activity.arn"
3941

4042

4143
# pylint: disable=too-many-public-methods
@@ -75,7 +77,7 @@ def set_up_dependency_container(cls):
7577
cls._local_stack: LocalStackContainer = (
7678
LocalStackContainer(image="localstack/localstack:3.5.0")
7779
.with_name("localstack")
78-
.with_services("s3", "sqs", "dynamodb", "kinesis", "secretsmanager")
80+
.with_services("s3", "sqs", "dynamodb", "kinesis", "secretsmanager", "iam", "stepfunctions")
7981
.with_env("DEFAULT_REGION", "us-west-2")
8082
.with_kwargs(network=NETWORK_NAME, networking_config=local_stack_networking_config)
8183
)
@@ -590,6 +592,82 @@ def test_secretsmanager_fault(self):
590592
span_name="Secrets Manager.GetSecretValue",
591593
)
592594

595+
def test_stepfunctions_describe_state_machine(self):
596+
self.do_test_requests(
597+
"stepfunctions/describestatemachine/my-state-machine",
598+
"GET",
599+
200,
600+
0,
601+
0,
602+
rpc_service="SFN",
603+
remote_service="AWS::StepFunctions",
604+
remote_operation="DescribeStateMachine",
605+
remote_resource_type="AWS::StepFunctions::StateMachine",
606+
remote_resource_identifier="testStateMachine",
607+
cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:testStateMachine",
608+
request_specific_attributes={
609+
_AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:testStateMachine",
610+
},
611+
span_name="SFN.DescribeStateMachine",
612+
)
613+
614+
def test_stepfunctions_describe_activity(self):
615+
self.do_test_requests(
616+
"stepfunctions/describeactivity/my-activity",
617+
"GET",
618+
200,
619+
0,
620+
0,
621+
rpc_service="SFN",
622+
remote_service="AWS::StepFunctions",
623+
remote_operation="DescribeActivity",
624+
remote_resource_type="AWS::StepFunctions::Activity",
625+
remote_resource_identifier="testActivity",
626+
cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:activity:testActivity",
627+
request_specific_attributes={
628+
_AWS_ACTIVITY_ARN: "arn:aws:states:us-west-2:000000000000:activity:testActivity"
629+
},
630+
span_name="SFN.DescribeActivity",
631+
)
632+
633+
def test_stepfunctions_error(self):
634+
self.do_test_requests(
635+
"stepfunctions/error",
636+
"GET",
637+
400,
638+
1,
639+
0,
640+
rpc_service="SFN",
641+
remote_service="AWS::StepFunctions",
642+
remote_operation="DescribeStateMachine",
643+
remote_resource_type="AWS::StepFunctions::StateMachine",
644+
remote_resource_identifier="unExistStateMachine",
645+
cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:unExistStateMachine",
646+
request_specific_attributes={
647+
_AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:unExistStateMachine",
648+
},
649+
span_name="SFN.DescribeStateMachine",
650+
)
651+
652+
def test_stepfunctions_fault(self):
653+
self.do_test_requests(
654+
"stepfunctions/fault",
655+
"GET",
656+
500,
657+
0,
658+
1,
659+
rpc_service="SFN",
660+
remote_service="AWS::StepFunctions",
661+
remote_operation="ListStateMachineVersions",
662+
remote_resource_type="AWS::StepFunctions::StateMachine",
663+
remote_resource_identifier="invalid-state-machine",
664+
cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine",
665+
request_specific_attributes={
666+
_AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine",
667+
},
668+
span_name="SFN.ListStateMachineVersions",
669+
)
670+
593671
@override
594672
def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs) -> None:
595673
target_spans: List[Span] = []

0 commit comments

Comments
 (0)