From 132cf142f63d15faeb9ebb4679e313d4a1e4030b Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Fri, 31 Oct 2025 14:32:18 -0500 Subject: [PATCH 1/2] Reapply "fix: Use full UUIDs for random identifiers in integration tests (#2043)" (#2044) This reverts commit ac8ff1eb1884c02f491aed48a25c81f87f502f12. --- .../String+Identifier.swift | 32 +++++++++++++ .../CloudFrontKeyValueStoreSigV4ATests.swift | 3 +- ...toAWSCredentialIdentityResolverTests.swift | 5 +- .../UnauthenticatedAPITests.swift | 3 +- .../EventBridgeSigV4ATests.swift | 7 +-- .../GlacierTests.swift | 3 +- .../KinesisTests.swift | 7 +-- .../S3ConcurrentTests.swift | 18 +++---- .../S3ContentMD5HeaderTests.swift | 2 +- .../S3EmptyBody404Tests.swift | 2 +- .../AWSS3IntegrationTests/S3ErrorTests.swift | 9 ++-- .../S3ExpressIntegrationTests.swift | 47 ++++++++++--------- .../S3ExpressXCTestCase.swift | 13 +++-- .../AWSS3IntegrationTests/S3SigV4ATests.swift | 9 ++-- .../AWSS3IntegrationTests/S3XCTestCase.swift | 3 +- .../AWSSQSIntegrationTests/SQSTests.swift | 3 +- ...leAWSCredentialIdentityResolverTests.swift | 8 ++-- ...tyAWSCredentialIdentityResolverTests.swift | 7 +-- 18 files changed, 112 insertions(+), 69 deletions(-) create mode 100644 IntegrationTests/AWSIntegrationTestUtils/String+Identifier.swift diff --git a/IntegrationTests/AWSIntegrationTestUtils/String+Identifier.swift b/IntegrationTests/AWSIntegrationTestUtils/String+Identifier.swift new file mode 100644 index 00000000000..3cab6d01d8f --- /dev/null +++ b/IntegrationTests/AWSIntegrationTestUtils/String+Identifier.swift @@ -0,0 +1,32 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.UUID + +package extension String { + + /// Returns an identifier that contains a UUID and is 63 or less chars in length. + /// + /// Size is selected to be compatible with the requirement that bucket names be 63 characters or less. + /// - Parameter service: The name (or abbreviation) of the service being identified. + /// May contain only A-Z, a-z, 0-9, and dash(-). May not be empty. May not be long enough to result in an + /// identifier 64 chars or longer. Uppercase characters in the service will be downcased in the identifier. + /// - Returns: The identifier. + static func uniqueID(service: String) -> String { + let prefix = "sdkinttest-" + guard !service.isEmpty else { + fatalError("Service cannot be empty") + } + guard service.lowercased().allSatisfy({ "abcdefghijklmnopqrstuvwxyz0123456789-".contains($0) }) else { + fatalError("Service cannot contain characters other than alphanumeric & dash") + } + guard prefix.count + service.count + 36 < 64 else { + fatalError("Service name is too long. Limit is \(63 - prefix.count - 36)") + } + return (prefix + service + UUID().uuidString).lowercased() + } +} diff --git a/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift b/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift index c0cf55356c0..3d42784d79e 100644 --- a/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift +++ b/IntegrationTests/Services/AWSCloudFrontKeyValueStoreIntegrationTests/CloudFrontKeyValueStoreSigV4ATests.swift @@ -11,6 +11,7 @@ import ClientRuntime import AWSClientRuntime import AWSCloudFront import AWSCloudFrontKeyValueStore +import AWSIntegrationTestUtils /// Tests SigV4a signing flow using CloudFrontKeyValueStore. class CloudFrontKeyValueStoreSigV4ATests: XCTestCase { @@ -23,7 +24,7 @@ class CloudFrontKeyValueStoreSigV4ATests: XCTestCase { private let region = "us-east-1" // Temporary name of the KVS to use for the test - private let kvsName = "sigv4a-test-kvs-" + UUID().uuidString.split(separator: "-").first!.lowercased() + private let kvsName = String.uniqueID(service: "sigv4a-kvs") // The Etag to use to call CloudFront::deletKeyValueStore private var cfEtag: String! diff --git a/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/CognitoAWSCredentialIdentityResolverTests.swift b/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/CognitoAWSCredentialIdentityResolverTests.swift index 3b6b66d6619..72ef5dbf697 100644 --- a/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/CognitoAWSCredentialIdentityResolverTests.swift +++ b/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/CognitoAWSCredentialIdentityResolverTests.swift @@ -12,6 +12,7 @@ import AWSSTS import ClientRuntime import SmithyWaitersAPI import XCTest +import AWSIntegrationTestUtils /// Tests CognitoAWSCredentialIdentityResolver using STS::getCallerIdentity. class CognitoAWSCredentialIdentityResolverTests: XCTestCase { @@ -19,7 +20,7 @@ class CognitoAWSCredentialIdentityResolverTests: XCTestCase { private var cognitoIdentityClient: CognitoIdentityClient! private var iamClient: IAMClient! - private let identityPoolName = "aws-cognito-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + private let identityPoolName = String.uniqueID(service: "cognito") private var identityPoolId: String! private var roleName: String! @@ -38,7 +39,7 @@ class CognitoAWSCredentialIdentityResolverTests: XCTestCase { ) ).identityPoolId // Create an IAM role for unauthenticated users - roleName = "CognitoUnauth_\(identityPoolName)" + roleName = String.uniqueID(service: "cog-role") let trustPolicy = """ { "Version": "2012-10-17", diff --git a/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/UnauthenticatedAPITests.swift b/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/UnauthenticatedAPITests.swift index 5b08113d537..60c7d468c38 100644 --- a/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/UnauthenticatedAPITests.swift +++ b/IntegrationTests/Services/AWSCognitoIdentityIntegrationTests/UnauthenticatedAPITests.swift @@ -12,6 +12,7 @@ import AWSClientRuntime import ClientRuntime import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse +import AWSIntegrationTestUtils /// Tests unauthenciated API using AWSCognitoIdentity::getId class UnauthenticatedAPITests: XCTestCase { @@ -22,7 +23,7 @@ class UnauthenticatedAPITests: XCTestCase { private var accountID: String! private var identityPoolID: String! - private let identityPoolName = "idpool" + UUID().uuidString.split(separator: "-").first!.lowercased() + private let identityPoolName = String.uniqueID(service: "idpool") override func setUp() async throws { // STS client for getting the account ID, an input parameter for the unauthenticated API, getId(). diff --git a/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift b/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift index 8be2e5d80ef..762ee077f43 100644 --- a/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift +++ b/IntegrationTests/Services/AWSEventBridgeIntegrationTests/EventBridgeSigV4ATests.swift @@ -11,6 +11,7 @@ import AWSEventBridge import ClientRuntime import AWSClientRuntime import AWSRoute53 +import AWSIntegrationTestUtils /// Tests SigV4a signing flow using EventBridge's global endpoint. class EventBridgeSigV4ATests: XCTestCase { @@ -28,8 +29,8 @@ class EventBridgeSigV4ATests: XCTestCase { private let secondaryRegion = "us-east-1" // Name for the EventBridge global endpoint - private let endpointName = "sigv4a-test-global-endpoint-\(UUID().uuidString.split(separator: "-").first!.lowercased())" - private let eventBusName = "sigv4a-integ-test-eventbus-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + private let endpointName = String.uniqueID(service: "eb-globalendpt") + private let eventBusName = String.uniqueID(service: "eb-eventbus") private var endpointId: String! private var healthCheckId: String! @@ -58,7 +59,7 @@ class EventBridgeSigV4ATests: XCTestCase { type: .https ) let createHealthCheckInput = CreateHealthCheckInput( - callerReference: UUID().uuidString.split(separator: "-").first!.lowercased(), + callerReference: String.uniqueID(service: "r53-healthchk"), healthCheckConfig: healthCheckConfig ) let healthCheck = try await route53Client.createHealthCheck(input: createHealthCheckInput) diff --git a/IntegrationTests/Services/AWSGlacierIntegrationTests/GlacierTests.swift b/IntegrationTests/Services/AWSGlacierIntegrationTests/GlacierTests.swift index a15aaa8b0b8..e7d8e23a011 100644 --- a/IntegrationTests/Services/AWSGlacierIntegrationTests/GlacierTests.swift +++ b/IntegrationTests/Services/AWSGlacierIntegrationTests/GlacierTests.swift @@ -11,6 +11,7 @@ import AWSGlacier import AWSSTS import enum Smithy.ByteStream import SmithyWaitersAPI +import AWSIntegrationTestUtils /// Tests that Glacier operations run successfully class GlacierTests: XCTestCase { @@ -18,7 +19,7 @@ class GlacierTests: XCTestCase { var stsClient: STSClient! var accountId: String! var archiveId: String! - let vaultName = UUID().uuidString.split(separator: "-").first!.lowercased() + "integ-test-vault" + let vaultName = String.uniqueID(service: "s3-glacier") override func setUp() async throws { stsClient = try STSClient(region: "us-east-1") diff --git a/IntegrationTests/Services/AWSKinesisIntegrationTests/KinesisTests.swift b/IntegrationTests/Services/AWSKinesisIntegrationTests/KinesisTests.swift index ccb9f863313..cdfd555f8f0 100644 --- a/IntegrationTests/Services/AWSKinesisIntegrationTests/KinesisTests.swift +++ b/IntegrationTests/Services/AWSKinesisIntegrationTests/KinesisTests.swift @@ -10,6 +10,7 @@ import AWSKinesis import ClientRuntime import AWSClientRuntime import SmithyWaitersAPI +import AWSIntegrationTestUtils class KinesisTests: XCTestCase { @@ -23,7 +24,7 @@ class KinesisTests: XCTestCase { // Client must have AWS credentials set that allow access to the Kinesis service. // Resources will be cleaned up before the test concludes, pass or fail, unless the test crashes. - let streamName = UUID().uuidString + let streamName = String.uniqueID(service: "kinesis") let client = try KinesisClient(region: "us-west-2") do { @@ -36,7 +37,7 @@ class KinesisTests: XCTestCase { let streamARN = stream.streamDescription?.streamARN // Make a set of 10 records, add them to the stream - var recordStrings = (1...10).map { _ in UUID().uuidString } + var recordStrings = (1...10).map { _ in String.uniqueID(service: "kinesis") } for record in recordStrings { let putRecordInput = PutRecordInput(data: record.data(using: .utf8), explicitHashKey: nil, partitionKey: "Test", sequenceNumberForOrdering: nil, streamName: streamName) let _ = try await client.putRecord(input: putRecordInput) @@ -48,7 +49,7 @@ class KinesisTests: XCTestCase { let shard = shardList.shards?.first! // Create a consumer for the shard - let consumerName = UUID().uuidString + let consumerName = String.uniqueID(service: "kinesis") let consumerInput = RegisterStreamConsumerInput(consumerName: consumerName, streamARN: stream.streamDescription?.streamARN) let consumer = try await client.registerStreamConsumer(input: consumerInput) let consumerARN = consumer.consumer?.consumerARN diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3ConcurrentTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3ConcurrentTests.swift index f9f5af54ad5..36d88f87aef 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3ConcurrentTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3ConcurrentTests.swift @@ -41,18 +41,18 @@ final class S3ConcurrentTests: S3XCTestCase, @unchecked Sendable { // Puts data to S3, gets the uploaded file, asserts retrieved data == original data, deletes S3 object @Sendable private func getObject(data: Data) async throws { - let objectKey = UUID().uuidString.split(separator: "-").first!.lowercased() - let putObjectInput = PutObjectInput(body: .data(data), bucket: bucketName, key: objectKey) + let objectKey = String.uniqueID(service: "s3") + let putObjectInput = PutObjectInput(body: .data(data), bucket: bucketName, key: objectKey) - _ = try await client.putObject(input: putObjectInput) + _ = try await client.putObject(input: putObjectInput) - let retrievedData = try await client.getObject(input: GetObjectInput( - bucket: bucketName, key: objectKey - )).body?.readData() + let retrievedData = try await client.getObject(input: GetObjectInput( + bucket: bucketName, key: objectKey + )).body?.readData() - XCTAssertEqual(data, retrievedData) + XCTAssertEqual(data, retrievedData) - let deleteObjectInput = DeleteObjectInput(bucket: bucketName, key: objectKey) - _ = try await client.deleteObject(input: deleteObjectInput) + let deleteObjectInput = DeleteObjectInput(bucket: bucketName, key: objectKey) + _ = try await client.deleteObject(input: deleteObjectInput) } } diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3ContentMD5HeaderTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3ContentMD5HeaderTests.swift index 4deb92597d3..f79d81a1eb3 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3ContentMD5HeaderTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3ContentMD5HeaderTests.swift @@ -17,7 +17,7 @@ final class S3ContentMD5HeaderTests: S3XCTestCase { try await super.setUp() // Generate 3 UUIDs to use as object keys and save them for _ in 1...3 { - objectKeys.append("key-\(UUID().uuidString.split(separator: "-").first!.lowercased())") + objectKeys.append(String.uniqueID(service: "s3")) } } diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3EmptyBody404Tests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3EmptyBody404Tests.swift index 3c32318e201..cc7990d8679 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3EmptyBody404Tests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3EmptyBody404Tests.swift @@ -30,7 +30,7 @@ class S3EmptyBody404Tests: S3XCTestCase { // Perform the S3 HeadObject operation on a nonexistent object. // This will cause the 404 error without a body. - let input = HeadObjectInput(bucket: bucketName, key: UUID().uuidString) + let input = HeadObjectInput(bucket: bucketName, key: String.uniqueID(service: "s3")) _ = try await client.headObject(input: input) // If an error was not thrown by the HeadObject call, fail the test. diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3ErrorTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3ErrorTests.swift index 28f59191a0e..a7c69e64a65 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3ErrorTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3ErrorTests.swift @@ -11,12 +11,13 @@ import XCTest import AWSS3 import AWSClientRuntime import SmithyIdentity +import AWSIntegrationTestUtils class S3ErrorTests: S3XCTestCase { func test_noSuchKey_throwsNoSuchKeyWhenUnknownKeyIsUsed() async throws { do { - let input = GetObjectInput(bucket: bucketName, key: UUID().uuidString) + let input = GetObjectInput(bucket: bucketName, key: String.uniqueID(service: "s3-error")) _ = try await client.getObject(input: input) XCTFail("Request should not have succeeded") } catch let error as NoSuchKey { @@ -42,7 +43,7 @@ class S3ErrorTests: S3XCTestCase { func test_requestID_hasARequestIDAndRequestID2() async throws { do { - let input = GetObjectInput(bucket: bucketName, key: UUID().uuidString) + let input = GetObjectInput(bucket: bucketName, key: String.uniqueID(service: "s3-error")) _ = try await client.getObject(input: input) XCTFail("Request should not have succeeded") } catch let error as NoSuchKey { @@ -57,7 +58,7 @@ class S3ErrorTests: S3XCTestCase { func test_InvalidObjectState_hasReadableProperties() async throws { do { - let key = UUID().uuidString + ".txt" + let key = String.uniqueID(service: "s3-error") + ".txt" let putInput = PutObjectInput(bucket: bucketName, key: key, storageClass: .glacier) _ = try await client.putObject(input: putInput) let getInput = GetObjectInput(bucket: bucketName, key: key) @@ -77,7 +78,7 @@ class S3ErrorTests: S3XCTestCase { let credentials = AWSCredentialIdentity(accessKey: "AKIDEXAMPLE", secret: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY") let awsCredentialIdentityResolver = StaticAWSCredentialIdentityResolver(credentials) let config = try await S3Client.S3ClientConfiguration(awsCredentialIdentityResolver: awsCredentialIdentityResolver, region: region) - let input = GetObjectInput(bucket: bucketName, key: UUID().uuidString) + let input = GetObjectInput(bucket: bucketName, key: String.uniqueID(service: "s3-error")) _ = try await S3Client(config: config).getObject(input: input) XCTFail("Request should not have succeeded") } catch let error as InvalidAccessKeyId { diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressIntegrationTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressIntegrationTests.swift index 3fe8d9e8616..e430fb0551d 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressIntegrationTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressIntegrationTests.swift @@ -10,27 +10,40 @@ import AWSS3 final class S3ExpressIntegrationTests: S3ExpressXCTestCase { + // The number of buckets to create during the test + let n = 5 + + // The names of S3 buckets that were created for this test + var buckets = [String]() + + // The object key & data contents to put in each bucket + let key = "hello-world.txt" + let originalContents = Data("Hello, World!".utf8) + + override func tearDown() async throws { + try await super.tearDown() + + // Delete the object from each bucket + for bucket in buckets { + try await deleteObject(bucket: bucket, key: key) + } + + // Delete each directory bucket + for bucket in buckets { + try await deleteBucket(bucket: bucket) + } + } + // This test: // - Creates multiple S3Express ("directory") buckets // - Puts an object with sample contents to each bucket // - Reads each object & compares its contents to the original data - // - Deletes the object from each bucket - // - Deletes each S3Express bucket func test_s3Express_operationalTest() async throws { - // The number of buckets to create - let n = 5 - - // The object key & data contents to put in each bucket - let key = "text" - let originalContents = Data("Hello, World!".utf8) - // Create the S3Express-enabled directory buckets with random names, // save the names for later use - var buckets = [String]() for _ in 1...n { - let baseName = String(UUID().uuidString.prefix(8)).lowercased() - let newBucket = try await createS3ExpressBucket(baseName: baseName) + let newBucket = try await createS3ExpressBucket() buckets.append(newBucket) } @@ -48,15 +61,5 @@ final class S3ExpressIntegrationTests: S3ExpressXCTestCase { let retrievedContents = try await output.body!.readData()! XCTAssertEqual(retrievedContents, originalContents) } - - // Delete the object from each bucket - for bucket in buckets { - try await deleteObject(bucket: bucket, key: key) - } - - // Delete each directory bucket - for bucket in buckets { - try await deleteBucket(bucket: bucket) - } } } diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressXCTestCase.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressXCTestCase.swift index 8a1ae9a29a5..75dba0f1ad0 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressXCTestCase.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3ExpressXCTestCase.swift @@ -7,6 +7,7 @@ import XCTest import AWSS3 +import AWSIntegrationTestUtils class S3ExpressXCTestCase: XCTestCase { // Region in which to run the test @@ -28,10 +29,8 @@ class S3ExpressXCTestCase: XCTestCase { } @discardableResult - func createS3ExpressBucket( - baseName: String = String(UUID().uuidString.prefix(8)).lowercased() - ) async throws -> String { - let bucket = bucket(baseName: baseName) + func createS3ExpressBucket() async throws -> String { + let bucket = bucket() let input = CreateBucketInput( bucket: bucket, createBucketConfiguration: .init( @@ -53,8 +52,8 @@ class S3ExpressXCTestCase: XCTestCase { _ = try await client.deleteBucket(input: deleteBucketInput) } - // Helper method to create a S3Express-compliant bucket name - func bucket(baseName: String) -> String { - "a\(baseName)--\(azID)--x-s3" + // Helper method to create a random, S3Express-compliant bucket name + func bucket() -> String { + "inttest-\(UUID().uuidString.lowercased())--\(azID)--x-s3" } } diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift index fcbad08e5ba..f3b6265d525 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift @@ -16,6 +16,7 @@ import AWSS3Control import AWSSTS import ClientRuntime import AWSClientRuntime +import AWSIntegrationTestUtils /// Tests SigV4A signing flow using S3's Multi-Region Access Point (MRAP). class S3SigV4ATests: S3XCTestCase { @@ -29,8 +30,8 @@ class S3SigV4ATests: S3XCTestCase { private var mrapArnFormat = "arn:aws:s3::%@:accesspoint/%@" private var mrapArn: String! private var mrapAlias: String! - private let mrapNamePrefix = "aws-sdk-s3-integration-test-" - private let mrapName = "aws-sdk-s3-integration-test-" + UUID().uuidString.split(separator: "-").first!.lowercased() + private let mrapNamePrefix = "sdk-inttest-" + private let mrapName = "sdk-inttest-" + UUID().uuidString.lowercased() // 3-50 chars long private var mrapConfig: S3ControlClientTypes.CreateMultiRegionAccessPointInput! // The S3 control client used to create and delete MRAP @@ -41,7 +42,7 @@ class S3SigV4ATests: S3XCTestCase { private var accountId: String! // Key string used for putting object in tests - private let key = UUID().uuidString.split(separator: "-").first!.lowercased() + private let key = String.uniqueID(service: "s3-sigv4a") private let NSEC_PER_SEC = 1_000_000_000 @@ -182,7 +183,7 @@ class S3SigV4ATests: S3XCTestCase { // Create S3 Multi-Region Access Point (MRAP) let createMRAPInput = CreateMultiRegionAccessPointInput( accountId: accountId, - clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(), + clientToken: UUID().uuidString.lowercased(), details: mrapConfig ) _ = try await s3ControlClient.createMultiRegionAccessPoint(input: createMRAPInput) diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3XCTestCase.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3XCTestCase.swift index 0bec2d64213..1a9e48cc0b7 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3XCTestCase.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3XCTestCase.swift @@ -10,6 +10,7 @@ import FoundationNetworking #endif import XCTest import AWSS3 +import AWSIntegrationTestUtils /// Provides a basic set of functions that can be used to perform S3 integration tests. /// Creates a bucket for testing before every test, then deletes the bucket after the test completes. @@ -37,7 +38,7 @@ class S3XCTestCase: XCTestCase { } override func setUp() async throws { - self.bucketName = "sdk-int-test-s3-\(UUID().uuidString.lowercased())" // 52 char bucket name (max 63) + self.bucketName = String.uniqueID(service: "s3") self.client = try S3Client(region: region) try await createBucket(bucketName: bucketName) try await super.setUp() diff --git a/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift b/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift index d1fbf19438f..c9683683660 100644 --- a/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift +++ b/IntegrationTests/Services/AWSSQSIntegrationTests/SQSTests.swift @@ -9,6 +9,7 @@ import XCTest import AWSSQS import ClientRuntime import AWSClientRuntime +import AWSIntegrationTestUtils /// Tests AWS SQS queue creation and deletion. class SQSTests: XCTestCase { @@ -20,7 +21,7 @@ class SQSTests: XCTestCase { override func setUp() async throws { self.client = try SQSClient(region: "us-west-1") - queueName = "integration-test-queue-\(UUID().uuidString)" + queueName = String.uniqueID(service: "sqs") } override func tearDown() async throws { diff --git a/IntegrationTests/Services/AWSSTSIntegrationTests/STSAssumeRoleAWSCredentialIdentityResolverTests.swift b/IntegrationTests/Services/AWSSTSIntegrationTests/STSAssumeRoleAWSCredentialIdentityResolverTests.swift index 28bb2d3be6c..34578187c59 100644 --- a/IntegrationTests/Services/AWSSTSIntegrationTests/STSAssumeRoleAWSCredentialIdentityResolverTests.swift +++ b/IntegrationTests/Services/AWSSTSIntegrationTests/STSAssumeRoleAWSCredentialIdentityResolverTests.swift @@ -11,12 +11,10 @@ import AWSSTS import AWSIAM import AWSSDKIdentity import ClientRuntime +import AWSIntegrationTestUtils #if canImport(InMemoryExporter) import InMemoryExporter #endif -//#if os(Linux) -//import OpenTelemetryConcurrency -//#endif class STSAssumeRoleAWSCredentialIdentityResolverTests: XCTestCase { private let region = "us-east-1" @@ -29,8 +27,8 @@ class STSAssumeRoleAWSCredentialIdentityResolverTests: XCTestCase { // Used to create temporary role assumed by STS assume role credentials provider. private var iamClient: IAMClient! - private let roleName = "aws-sts-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" - private let roleSessionName = "aws-sts-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + private let roleName = String.uniqueID(service: "sts") + private let roleSessionName = String.uniqueID(service: "sts") private var roleArn: String! // JSON assume role policy diff --git a/IntegrationTests/Services/AWSSTSIntegrationTests/STSWebIdentityAWSCredentialIdentityResolverTests.swift b/IntegrationTests/Services/AWSSTSIntegrationTests/STSWebIdentityAWSCredentialIdentityResolverTests.swift index 7316b476248..cecf6ce3d99 100644 --- a/IntegrationTests/Services/AWSSTSIntegrationTests/STSWebIdentityAWSCredentialIdentityResolverTests.swift +++ b/IntegrationTests/Services/AWSSTSIntegrationTests/STSWebIdentityAWSCredentialIdentityResolverTests.swift @@ -13,6 +13,7 @@ import ClientRuntime import AWSClientRuntime import Foundation import AWSSDKIdentity +import AWSIntegrationTestUtils /// Tests STS web identity credentials provider using STS::getCallerIdentity. class STSWebIdentityAWSCredentialIdentityResolverTests: XCTestCase { @@ -30,7 +31,7 @@ class STSWebIdentityAWSCredentialIdentityResolverTests: XCTestCase { private var cognitoIdentityClient: CognitoIdentityClient! // Regular STS client used to fetch the account ID used in fetching cognito ID. private var stsClient: STSClient! - private let identityPoolName = "aws-sts-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + private let identityPoolName = String.uniqueID(service: "sts-web-id") private var identityPoolId: String! private var oidcToken: String! private var oidcTokenFilePath: String! @@ -39,8 +40,8 @@ class STSWebIdentityAWSCredentialIdentityResolverTests: XCTestCase { // Used to create temporary role assumed by STS web identity credentials provider. private var iamClient: IAMClient! - private let roleName = "aws-sts-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" - private let roleSessionName = "aws-sts-integration-test-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + private let roleName = String.uniqueID(service: "sts-web-id") + private let roleSessionName = String.uniqueID(service: "sts-web-id") private var roleArn: String! // JSON assume role policy From 1bd3cb7d0bae1939f0347f9e85d03db85d06e1bf Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Tue, 4 Nov 2025 17:42:21 -0600 Subject: [PATCH 2/2] More granular error messaging on internal STS client --- .../IdentityProvidingSTSClient.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/IdentityClientProvider/IdentityProvidingSTSClient.swift b/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/IdentityClientProvider/IdentityProvidingSTSClient.swift index 051739cfe8b..b8e9cc189c8 100644 --- a/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/IdentityClientProvider/IdentityProvidingSTSClient.swift +++ b/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/IdentityClientProvider/IdentityProvidingSTSClient.swift @@ -9,6 +9,7 @@ import Foundation import InternalAWSSTS import struct Smithy.Attributes import struct SmithyIdentity.StaticAWSCredentialIdentityResolver +import protocol AWSClientRuntime.AWSServiceError struct IdentityProvidingSTSClient: Swift.Sendable { @@ -75,16 +76,23 @@ struct IdentityProvidingSTSClient: Swift.Sendable { throw IdentityProvidingSTSClientError.expiredTokenException } catch is IDPCommunicationErrorException { throw IdentityProvidingSTSClientError.idpCommunicationErrorException + } catch let error as AWSServiceError { + throw AWSCredentialIdentityResolverError.failedToResolveAWSCredentials( + "STSWebIdentityAWSCredentialIdentityResolver.assumeRoleWithWebIdentity: " + + "Unhandled AWS service error of type: \(error.errorCode)" + ) } catch { throw AWSCredentialIdentityResolverError.failedToResolveAWSCredentials( - "STSWebIdentityAWSCredentialIdentityResolver: " + - "Failed to retrieve credentials from STS with web identity token." + "STSWebIdentityAWSCredentialIdentityResolver.assumeRoleWithWebIdentity: " + + "Unhandled error: \(error)" ) } + + // Safe-unwrap credential fields from the assumeRoleWithWebIdentity response guard let creds = out.credentials, let access = creds.accessKeyId, let secret = creds.secretAccessKey else { throw AWSCredentialIdentityResolverError.failedToResolveAWSCredentials( "STSWebIdentityAWSCredentialIdentityResolver: " + - "Failed to retrieve credentials from STS with web identity token." + "Received credentials did not contain necessary fields" ) } var properties = Attributes()