Skip to content

Commit 05b8f16

Browse files
committed
feat: add secretsmanager contract tests
1 parent 77199b8 commit 05b8f16

File tree

3 files changed

+126
-5
lines changed

3 files changed

+126
-5
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def do_GET(self):
4545
self._handle_kinesis_request()
4646
if self.in_path("bedrock"):
4747
self._handle_bedrock_request()
48+
if self.in_path("secretsmanager"):
49+
self._handle_secretsmanager_request()
4850

4951
self._end_request(self.main_status)
5052

@@ -301,6 +303,32 @@ def _handle_bedrock_request(self) -> None:
301303
else:
302304
set_main_status(404)
303305

306+
def _handle_secretsmanager_request(self) -> None:
307+
secretsmanager_client = boto3.client("secretsmanager", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
308+
if self.in_path(_ERROR):
309+
set_main_status(400)
310+
try:
311+
error_client = boto3.client("secretsmanager", endpoint_url=_ERROR_ENDPOINT, region_name=_AWS_REGION)
312+
error_client.describe_secret(
313+
SecretId="arn:aws:secretsmanager:us-west-2:000000000000:secret:unExistSecret"
314+
)
315+
except Exception as exception:
316+
print("Expected exception occurred", exception)
317+
elif self.in_path(_FAULT):
318+
set_main_status(500)
319+
try:
320+
fault_client = boto3.client("secretsmanager", endpoint_url=_FAULT_ENDPOINT, region_name=_AWS_REGION, config=_NO_RETRY_CONFIG)
321+
fault_client.get_secret_value(
322+
SecretId="arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret"
323+
)
324+
except Exception as exception:
325+
print("Expected exception occurred", exception)
326+
elif self.in_path("describesecret/my-secret"):
327+
set_main_status(200)
328+
secretsmanager_client.describe_secret(SecretId="testSecret")
329+
else:
330+
set_main_status(404)
331+
304332
def _end_request(self, status_code: int):
305333
self.send_response_only(status_code)
306334
self.end_headers()
@@ -345,6 +373,16 @@ def prepare_aws_server() -> None:
345373
# Set up Kinesis so tests can access a stream.
346374
kinesis_client: BaseClient = boto3.client("kinesis", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
347375
kinesis_client.create_stream(StreamName="test_stream", ShardCount=1)
376+
377+
# Set up Secrets Manager so tests can access a secret.
378+
secretsmanager_client: BaseClient = boto3.client("secretsmanager", endpoint_url=_AWS_SDK_ENDPOINT, region_name=_AWS_REGION)
379+
secretsmanager_response = secretsmanager_client.list_secrets()
380+
secret = next((s for s in secretsmanager_response["SecretList"] if s["Name"] == "testSecret"), None)
381+
if not secret:
382+
secretsmanager_client.create_secret(
383+
Name="testSecret", SecretString="secretValue", Description="This is a test secret"
384+
)
385+
348386
except Exception as exception:
349387
print("Unexpected exception occurred", exception)
350388

contract-tests/tests/test/amazon/base/contract_test_base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3+
import re
34
import time
45
from logging import INFO, Logger, getLogger
56
from typing import Dict, List
@@ -171,6 +172,12 @@ def _assert_int_attribute(self, attributes_dict: Dict[str, AnyValue], key: str,
171172
self.assertIsNotNone(actual_value)
172173
self.assertEqual(expected_value, actual_value.int_value)
173174

175+
def _assert_match_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, pattern: str) -> None:
176+
self.assertIn(key, attributes_dict)
177+
actual_value: AnyValue = attributes_dict[key]
178+
self.assertIsNotNone(actual_value)
179+
self.assertRegex(actual_value.string_value, pattern)
180+
174181
def check_sum(self, metric_name: str, actual_sum: float, expected_sum: float) -> None:
175182
if metric_name is LATENCY_METRIC:
176183
self.assertTrue(0 < actual_sum < expected_sum)
@@ -221,3 +228,10 @@ def _assert_metric_attributes(
221228
self, resource_scope_metrics: List[ResourceScopeMetric], metric_name: str, expected_sum: int, **kwargs
222229
):
223230
self.fail("Tests must implement this function")
231+
232+
def _is_valid_regex(self, pattern: str) -> bool:
233+
try:
234+
re.compile(pattern)
235+
return True
236+
except re.error:
237+
return False

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

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
_AWS_BEDROCK_KNOWLEDGE_BASE_ID: str = "aws.bedrock.knowledge_base.id"
3636
_AWS_BEDROCK_DATA_SOURCE_ID: str = "aws.bedrock.data_source.id"
3737
_GEN_AI_REQUEST_MODEL: str = "gen_ai.request.model"
38+
_AWS_SECRET_ARN: str = "aws.secretsmanager.secret.arn"
3839

3940

4041
# pylint: disable=too-many-public-methods
@@ -74,7 +75,7 @@ def set_up_dependency_container(cls):
7475
cls._local_stack: LocalStackContainer = (
7576
LocalStackContainer(image="localstack/localstack:3.5.0")
7677
.with_name("localstack")
77-
.with_services("s3", "sqs", "dynamodb", "kinesis")
78+
.with_services("s3", "sqs", "dynamodb", "kinesis", "secretsmanager")
7879
.with_env("DEFAULT_REGION", "us-west-2")
7980
.with_kwargs(network=NETWORK_NAME, networking_config=local_stack_networking_config)
8081
)
@@ -532,6 +533,63 @@ def test_bedrock_agent_get_data_source(self):
532533
span_name="Bedrock Agent.GetDataSource",
533534
)
534535

536+
def test_secretsmanager_describe_secret(self):
537+
self.do_test_requests(
538+
"secretsmanager/describesecret/my-secret",
539+
"GET",
540+
200,
541+
0,
542+
0,
543+
rpc_service="Secrets Manager",
544+
remote_service="AWS::SecretsManager",
545+
remote_operation="DescribeSecret",
546+
remote_resource_type="AWS::SecretsManager::Secret",
547+
remote_resource_identifier=r"testSecret-[a-zA-Z0-9]{6}$",
548+
cloudformation_primary_identifier=r"arn:aws:secretsmanager:us-west-2:000000000000:secret:testSecret-[a-zA-Z0-9]{6}$",
549+
response_specific_attributes={
550+
_AWS_SECRET_ARN: r"arn:aws:secretsmanager:us-west-2:000000000000:secret:testSecret-[a-zA-Z0-9]{6}$",
551+
},
552+
span_name="Secrets Manager.DescribeSecret",
553+
)
554+
555+
def test_secretsmanager_error(self):
556+
self.do_test_requests(
557+
"secretsmanager/error",
558+
"GET",
559+
400,
560+
1,
561+
0,
562+
rpc_service="Secrets Manager",
563+
remote_service="AWS::SecretsManager",
564+
remote_operation="DescribeSecret",
565+
remote_resource_type="AWS::SecretsManager::Secret",
566+
remote_resource_identifier="unExistSecret",
567+
cloudformation_primary_identifier="arn:aws:secretsmanager:us-west-2:000000000000:secret:unExistSecret",
568+
request_specific_attributes={
569+
_AWS_SECRET_ARN: "arn:aws:secretsmanager:us-west-2:000000000000:secret:unExistSecret",
570+
},
571+
span_name="Secrets Manager.DescribeSecret",
572+
)
573+
574+
def test_secretsmanager_fault(self):
575+
self.do_test_requests(
576+
"secretsmanager/fault",
577+
"GET",
578+
500,
579+
0,
580+
1,
581+
rpc_service="Secrets Manager",
582+
remote_service="AWS::SecretsManager",
583+
remote_operation="GetSecretValue",
584+
remote_resource_type="AWS::SecretsManager::Secret",
585+
remote_resource_identifier="nonexistent-secret",
586+
cloudformation_primary_identifier="arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret",
587+
request_specific_attributes={
588+
_AWS_SECRET_ARN: "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret",
589+
},
590+
span_name="Secrets Manager.GetSecretValue",
591+
)
592+
535593
@override
536594
def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs) -> None:
537595
target_spans: List[Span] = []
@@ -571,9 +629,15 @@ def _assert_aws_attributes(
571629
if remote_resource_type != "None":
572630
self._assert_str_attribute(attributes_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type)
573631
if remote_resource_identifier != "None":
574-
self._assert_str_attribute(attributes_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
632+
if self._is_valid_regex(remote_resource_identifier):
633+
self._assert_match_attribute(attributes_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
634+
else:
635+
self._assert_str_attribute(attributes_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
575636
if cloudformation_primary_identifier != "None":
576-
self._assert_str_attribute(attributes_dict, AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformation_primary_identifier)
637+
if self._is_valid_regex(remote_resource_identifier):
638+
self._assert_match_attribute(attributes_dict, AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformation_primary_identifier)
639+
else:
640+
self._assert_str_attribute(attributes_dict, AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformation_primary_identifier)
577641
# See comment above AWS_LOCAL_OPERATION
578642
self._assert_str_attribute(attributes_dict, AWS_SPAN_KIND, span_kind)
579643

@@ -623,7 +687,9 @@ def _assert_semantic_conventions_attributes(
623687
else:
624688
self._assert_array_value_ddb_table_name(attributes_dict, key, value)
625689
for key, value in response_specific_attributes.items():
626-
if isinstance(value, str):
690+
if self._is_valid_regex(value):
691+
self._assert_match_attribute(attributes_dict, key, value)
692+
elif isinstance(value, str):
627693
self._assert_str_attribute(attributes_dict, key, value)
628694
elif isinstance(value, int):
629695
self._assert_int_attribute(attributes_dict, key, value)
@@ -665,7 +731,10 @@ def _assert_metric_attributes(
665731
if remote_resource_type != "None":
666732
self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type)
667733
if remote_resource_identifier != "None":
668-
self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
734+
if self._is_valid_regex(remote_resource_identifier):
735+
self._assert_match_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
736+
else:
737+
self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier)
669738
self.check_sum(metric_name, dependency_dp.sum, expected_sum)
670739

671740
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(service_dp.attributes)

0 commit comments

Comments
 (0)