import Foundation import Testing @testable import CodexBarCore private final class CapturedEnvironment: @unchecked Sendable { private let lock = NSLock() private var stored: [String: String] = [:] func record(_ environment: [String: String]) { self.lock.withLock { self.stored = environment } } var value: [String: String] { self.lock.withLock { self.stored } } } @Suite(.serialized) struct BedrockCredentialResolverTests { private static let credentialsJSON = #""" {"Version":1,"AccessKeyId":"AKIAPROFILE","SecretAccessKey":"profile-secret","SessionToken":"profile-token"} """# /// Fake AWS CLI runner: returns exported credentials and a profile region. private func profileProvider(region: String = "ap-southeast-2") -> BedrockProfileCredentialProvider { BedrockProfileCredentialProvider(awsBinaryPath: "/usr/bin/aws") { arguments, _ in if arguments.contains("export-credentials") { return SubprocessResult(stdout: Self.credentialsJSON, stderr: "") } if arguments.contains("get") { return SubprocessResult(stdout: region + "\n", stderr: "") } return SubprocessResult(stdout: "", stderr: "") } } @Test func `keys mode resolves static credentials and region`() async throws { let env = [ BedrockSettingsReader.accessKeyIDKey: "AKIAKEYS", BedrockSettingsReader.secretAccessKeyKey: "keys-secret", BedrockSettingsReader.regionKeys[0]: "us-west-2", ] let resolved = try await BedrockCredentialResolver.resolve(environment: env) #expect(resolved.credentials.accessKeyID == "AKIAKEYS") #expect(resolved.credentials.secretAccessKey == "keys-secret") #expect(resolved.region == "us-west-2") } @Test func `keys mode without credentials throws missingCredentials`() async { await #expect(throws: BedrockUsageError.missingCredentials) { try await BedrockCredentialResolver.resolve(environment: [:]) } } @Test func `profile mode resolves credentials via the AWS CLI`() async throws { let env = [ BedrockSettingsReader.authModeKey: "profile", BedrockSettingsReader.profileKey: "work", ] let resolved = try await BedrockCredentialResolver.resolve( environment: env, resolveAWSBinary: { _ in "/usr/bin/aws" }, makeProvider: { _ in self.profileProvider() }) #expect(resolved.credentials.accessKeyID == "AKIAPROFILE") #expect(resolved.credentials.sessionToken == "profile-token") // No explicit region in env, so it is derived from the profile. #expect(resolved.region == "ap-southeast-2") } @Test func `profile mode prefers explicit region over the profile region`() async throws { let env = [ BedrockSettingsReader.authModeKey: "profile", BedrockSettingsReader.profileKey: "work", BedrockSettingsReader.regionKeys[0]: "eu-central-1", ] let resolved = try await BedrockCredentialResolver.resolve( environment: env, resolveAWSBinary: { _ in "/usr/bin/aws" }, makeProvider: { _ in self.profileProvider() }) #expect(resolved.region == "eu-central-1") } @Test func `profile mode without a profile name throws missingCredentials`() async { let env = [BedrockSettingsReader.authModeKey: "profile"] await #expect(throws: BedrockUsageError.missingCredentials) { try await BedrockCredentialResolver.resolve( environment: env, resolveAWSBinary: { _ in "/usr/bin/aws" }, makeProvider: { _ in self.profileProvider() }) } } @Test func `profile mode preserves source credentials but removes AWS_PROFILE for AWS CLI`() async throws { let captured = CapturedEnvironment() let env = [ BedrockSettingsReader.authModeKey: "profile", BedrockSettingsReader.profileKey: "work", BedrockSettingsReader.accessKeyIDKey: "AKIAINHERITED", BedrockSettingsReader.secretAccessKeyKey: "inherited-secret", BedrockSettingsReader.sessionTokenKey: "inherited-token", ] _ = try await BedrockCredentialResolver.resolve( environment: env, resolveAWSBinary: { _ in "/usr/bin/aws" }, makeProvider: { _ in BedrockProfileCredentialProvider(awsBinaryPath: "/usr/bin/aws") { arguments, environment in captured.record(environment) if arguments.contains("export-credentials") { return SubprocessResult(stdout: Self.credentialsJSON, stderr: "") } return SubprocessResult(stdout: "us-east-1\n", stderr: "") } }) let seen = captured.value #expect(seen[BedrockSettingsReader.accessKeyIDKey] == "AKIAINHERITED") #expect(seen[BedrockSettingsReader.secretAccessKeyKey] == "inherited-secret") #expect(seen[BedrockSettingsReader.sessionTokenKey] == "inherited-token") #expect(seen[BedrockSettingsReader.profileKey] == nil) } @Test func `profile mode without the AWS CLI throws awsCLINotFound`() async { let env = [ BedrockSettingsReader.authModeKey: "profile", BedrockSettingsReader.profileKey: "work", ] await #expect(throws: BedrockUsageError.awsCLINotFound) { try await BedrockCredentialResolver.resolve( environment: env, resolveAWSBinary: { _ in nil }, makeProvider: { _ in self.profileProvider() }) } } }