diff --git a/.autover/changes/ebd7f49a-72cd-4c5b-83f4-2790a2560e94.json b/.autover/changes/ebd7f49a-72cd-4c5b-83f4-2790a2560e94.json
new file mode 100644
index 000000000..df1f60a88
--- /dev/null
+++ b/.autover/changes/ebd7f49a-72cd-4c5b-83f4-2790a2560e94.json
@@ -0,0 +1,11 @@
+{
+ "Projects": [
+ {
+ "Name": "AWS.Deploy.CLI",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Read region value for non default profiles"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/AWS.Deploy.CLI/AWSCredentialsFactory.cs b/src/AWS.Deploy.CLI/AWSCredentialsFactory.cs
new file mode 100644
index 000000000..d9ac35223
--- /dev/null
+++ b/src/AWS.Deploy.CLI/AWSCredentialsFactory.cs
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ public class AWSCredentialsFactory : IAWSCredentialsFactory
+ {
+ ///
+ public AWSCredentials Create()
+ {
+ return FallbackCredentialsFactory.GetCredentials();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.CLI/AWSUtilities.cs b/src/AWS.Deploy.CLI/AWSUtilities.cs
index 9a3376576..997773e3a 100644
--- a/src/AWS.Deploy.CLI/AWSUtilities.cs
+++ b/src/AWS.Deploy.CLI/AWSUtilities.cs
@@ -6,7 +6,6 @@
using System.Linq;
using System.Threading.Tasks;
using Amazon.Runtime;
-using Amazon.Runtime.CredentialManagement;
using AWS.Deploy.CLI.Utilities;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
@@ -17,7 +16,7 @@ namespace AWS.Deploy.CLI
{
public interface IAWSUtilities
{
- Task ResolveAWSCredentials(string? profileName);
+ Task> ResolveAWSCredentials(string? profileName);
string ResolveAWSRegion(string? region, string? lastRegionUsed = null);
}
@@ -28,27 +27,43 @@ public class AWSUtilities : IAWSUtilities
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly IServiceProvider _serviceProvider;
+ private readonly ICredentialProfileStoreChainFactory _credentialChainFactory;
+ private readonly ISharedCredentialsFileFactory _sharedCredentialsFileFactory;
+ private readonly IAWSCredentialsFactory _awsCredentialsFactory;
public AWSUtilities(
IServiceProvider serviceProvider,
IToolInteractiveService toolInteractiveService,
IConsoleUtilities consoleUtilities,
IDirectoryManager directoryManager,
- IOptionSettingHandler optionSettingHandler)
+ IOptionSettingHandler optionSettingHandler,
+ ICredentialProfileStoreChainFactory credentialChainFactory,
+ ISharedCredentialsFileFactory sharedCredentialsFileFactory,
+ IAWSCredentialsFactory awsCredentialsFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
_consoleUtilities = consoleUtilities;
_directoryManager = directoryManager;
_optionSettingHandler = optionSettingHandler;
+ _credentialChainFactory = credentialChainFactory;
+ _sharedCredentialsFileFactory = sharedCredentialsFileFactory;
+ _awsCredentialsFactory = awsCredentialsFactory;
}
- public async Task ResolveAWSCredentials(string? profileName)
+
+ ///
+ /// At a high level there are 2 possible return values for this function:
+ /// 1. In this case, both the credentials and region were able to be read from the profile.
+ /// 2. : In this case, the region was not able to be read from the profile, so we return null for it. The null case will be handled later on by .
+ ///
+ public async Task> ResolveAWSCredentials(string? profileName)
{
- async Task Resolve()
+ async Task> Resolve()
{
- var chain = new CredentialProfileStoreChain();
+ var chain = _credentialChainFactory.Create();
+ // Use provided profile to read credentials
if (!string.IsNullOrEmpty(profileName))
{
if (chain.TryGetAWSCredentials(profileName, out var profileCredentials) &&
@@ -56,7 +71,10 @@ async Task Resolve()
(profileCredentials is AssumeRoleAWSCredentials || await CanLoadCredentials(profileCredentials)))
{
_toolInteractiveService.WriteLine($"Configuring AWS Credentials from Profile {profileName}.");
- return profileCredentials;
+ chain.TryGetProfile(profileName, out var profile);
+ // Return the credentials since they must be found at this point.
+ // For region, we try to read it from the profile. If it's not found in the profile, then return null and region selection will be handled later on by ResolveAWSRegion.
+ return Tuple.Create(profileCredentials, profile.Region?.SystemName);
}
else
{
@@ -65,14 +83,17 @@ async Task Resolve()
}
}
+ // Use default credentials
try
{
- var fallbackCredentials = FallbackCredentialsFactory.GetCredentials();
+ var fallbackCredentials = _awsCredentialsFactory.Create();
if (await CanLoadCredentials(fallbackCredentials))
{
+ // Always return the credentials since they must be found at this point.
+ // For region, we return null here, since it will read from default region in ResolveAWSRegion
_toolInteractiveService.WriteLine("Configuring AWS Credentials using AWS SDK credential search.");
- return fallbackCredentials;
+ return Tuple.Create(fallbackCredentials, null);
}
}
catch (AmazonServiceException ex)
@@ -82,7 +103,8 @@ async Task Resolve()
_toolInteractiveService.WriteDebugLine(ex.PrettyPrint());
}
- var sharedCredentials = new SharedCredentialsFile();
+ // Use Shared Credentials
+ var sharedCredentials = _sharedCredentialsFileFactory.Create();
if (sharedCredentials.ListProfileNames().Count == 0)
{
throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToResolveAWSCredentials, "Unable to resolve AWS credentials to access AWS.");
@@ -93,21 +115,24 @@ async Task Resolve()
if (chain.TryGetAWSCredentials(selectedProfileName, out var selectedProfileCredentials) &&
(await CanLoadCredentials(selectedProfileCredentials)))
{
- return selectedProfileCredentials;
+ // Return the credentials since they must be found at this point.
+ // For region, we try to read it from the profile. If it's not found in the profile, then return null and region selection will be handled later on by ResolveAWSRegion.
+ chain.TryGetProfile(selectedProfileName, out var profile);
+ return Tuple.Create(selectedProfileCredentials, profile.Region?.SystemName);
}
throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToCreateAWSCredentials, $"Unable to create AWS credentials for profile {selectedProfileName}.");
}
- var credentials = await Resolve();
+ var credentialsAndRegion = await Resolve();
- if (credentials is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
+ if (credentialsAndRegion.Item1 is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
{
var assumeOptions = assumeRoleAWSCredentials.Options;
assumeOptions.MfaTokenCodeCallback = ActivatorUtilities.CreateInstance(_serviceProvider, assumeOptions).Execute;
}
- return credentials;
+ return credentialsAndRegion;
}
private async Task CanLoadCredentials(AWSCredentials credentials)
diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
index 2240d8a1b..5e30341e5 100644
--- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
+++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs
@@ -209,8 +209,8 @@ private Command BuildDeployCommand()
deploymentSettings = await _deploymentSettingsHandler.ReadSettings(applyPath);
}
- var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
- var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion);
+ var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
+ var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion ?? regionFromProfile);
_commandLineWrapper.RegisterAWSContext(awsCredentials, awsRegion);
_awsClientFactory.RegisterAWSContext(awsCredentials, awsRegion);
@@ -318,8 +318,8 @@ private Command BuildDeleteCommand()
_toolInteractiveService.Diagnostics = input.Diagnostics;
_toolInteractiveService.DisableInteractive = input.Silent;
- var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
- var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
+ var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
+ var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);
_awsClientFactory.ConfigureAWSOptions(awsOption =>
{
@@ -401,8 +401,8 @@ private Command BuildListCommand()
{
_toolInteractiveService.Diagnostics = input.Diagnostics;
- var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
- var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
+ var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
+ var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);
_awsClientFactory.ConfigureAWSOptions(awsOptions =>
{
diff --git a/src/AWS.Deploy.CLI/CredentialProfileStoreChainFactory.cs b/src/AWS.Deploy.CLI/CredentialProfileStoreChainFactory.cs
new file mode 100644
index 000000000..4cf75c408
--- /dev/null
+++ b/src/AWS.Deploy.CLI/CredentialProfileStoreChainFactory.cs
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ public class CredentialProfileStoreChainFactory : ICredentialProfileStoreChainFactory
+ {
+ ///
+ public CredentialProfileStoreChain Create()
+ {
+ return new CredentialProfileStoreChain();
+ }
+ }
+}
diff --git a/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs b/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
index fd3f77977..f19a7bea3 100644
--- a/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
+++ b/src/AWS.Deploy.CLI/Extensions/CustomServiceCollectionExtension.cs
@@ -71,6 +71,9 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentVariableManager), typeof(EnvironmentVariableManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeployToolWorkspaceMetadata), typeof(DeployToolWorkspaceMetadata), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeploymentSettingsHandler), typeof(DeploymentSettingsHandler), lifetime));
+ serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICredentialProfileStoreChainFactory), typeof(CredentialProfileStoreChainFactory), lifetime));
+ serviceCollection.TryAdd(new ServiceDescriptor(typeof(ISharedCredentialsFileFactory), typeof(SharedCredentialsFileFactory), lifetime));
+ serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSCredentialsFactory), typeof(AWSCredentialsFactory), lifetime));
var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IPackageJsonGenerator), (serviceProvider) => new PackageJsonGenerator(packageJsonTemplate), lifetime));
diff --git a/src/AWS.Deploy.CLI/IAWSCredentialsFactory.cs b/src/AWS.Deploy.CLI/IAWSCredentialsFactory.cs
new file mode 100644
index 000000000..2d2c9ed4b
--- /dev/null
+++ b/src/AWS.Deploy.CLI/IAWSCredentialsFactory.cs
@@ -0,0 +1,18 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ /// Represents a factory for creating
+ ///
+ public interface IAWSCredentialsFactory
+ {
+ ///
+ /// Creates
+ ///
+ AWSCredentials Create();
+ }
+}
diff --git a/src/AWS.Deploy.CLI/ICredentialProfileStoreChainFactory.cs b/src/AWS.Deploy.CLI/ICredentialProfileStoreChainFactory.cs
new file mode 100644
index 000000000..4cb1c7d6c
--- /dev/null
+++ b/src/AWS.Deploy.CLI/ICredentialProfileStoreChainFactory.cs
@@ -0,0 +1,18 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ /// Represents a factory for creating
+ ///
+ public interface ICredentialProfileStoreChainFactory
+ {
+ ///
+ /// Creates a
+ ///
+ CredentialProfileStoreChain Create();
+ }
+}
diff --git a/src/AWS.Deploy.CLI/ISharedCredentialsFileFactory.cs b/src/AWS.Deploy.CLI/ISharedCredentialsFileFactory.cs
new file mode 100644
index 000000000..b81529079
--- /dev/null
+++ b/src/AWS.Deploy.CLI/ISharedCredentialsFileFactory.cs
@@ -0,0 +1,18 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ /// Represents a factory for creating
+ ///
+ public interface ISharedCredentialsFileFactory
+ {
+ ///
+ /// Creates
+ ///
+ SharedCredentialsFile Create();
+ }
+}
diff --git a/src/AWS.Deploy.CLI/SharedCredentialsFileFactory.cs b/src/AWS.Deploy.CLI/SharedCredentialsFileFactory.cs
new file mode 100644
index 000000000..eaf96f825
--- /dev/null
+++ b/src/AWS.Deploy.CLI/SharedCredentialsFileFactory.cs
@@ -0,0 +1,17 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+
+namespace AWS.Deploy.CLI
+{
+ ///
+ public class SharedCredentialsFileFactory : ISharedCredentialsFileFactory
+ {
+ ///
+ public SharedCredentialsFile Create()
+ {
+ return new SharedCredentialsFile();
+ }
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/AWSCredentialsFactoryTests.cs b/test/AWS.Deploy.CLI.UnitTests/AWSCredentialsFactoryTests.cs
new file mode 100644
index 000000000..a9cf16653
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/AWSCredentialsFactoryTests.cs
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime;
+using Xunit;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class AWSCredentialsFactoryTests
+ {
+ [Fact]
+ public void Create_ReturnsAWSCredentialsInstance()
+ {
+ // Arrange
+ var factory = new AWSCredentialsFactory();
+
+ // Act
+ var result = factory.Create();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsAssignableFrom(result);
+ }
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/AWSUtilitiesTests.cs b/test/AWS.Deploy.CLI.UnitTests/AWSUtilitiesTests.cs
new file mode 100644
index 000000000..ab6160591
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/AWSUtilitiesTests.cs
@@ -0,0 +1,277 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Amazon;
+using Amazon.Runtime;
+using Amazon.Runtime.CredentialManagement;
+using AWS.Deploy.CLI.Common.UnitTests.IO;
+using AWS.Deploy.Common.IO;
+using AWS.Deploy.Common.Recipes;
+using Moq;
+using Xunit;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class AWSUtilitiesTests : IDisposable
+ {
+ private readonly IDirectoryManager _directoryManager;
+ private readonly IOptionSettingHandler _optionSettingHandler;
+ private readonly Mock _mockToolInteractiveService;
+ private readonly Mock _mockConsoleUtilities;
+ private readonly Mock _mockServiceProvider;
+ private readonly Mock _mockCredentialChainFactory;
+ private readonly Mock _mockSharedCredentialsFileFactory;
+ private readonly Mock _mockAWSCredentialsFactory;
+ private CredentialProfileStoreChain _credentialProfileStoreChain;
+
+ private readonly string _tempCredentialsFile;
+ private SharedCredentialsFile _sharedCredentialsFile;
+
+ public AWSUtilitiesTests()
+ {
+ _directoryManager = new TestDirectoryManager();
+ _mockToolInteractiveService = new Mock();
+ _mockConsoleUtilities = new Mock();
+ _mockServiceProvider = new Mock();
+ _optionSettingHandler = new Mock().Object;
+ _mockCredentialChainFactory = new Mock();
+ _mockSharedCredentialsFileFactory = new Mock();
+ _mockAWSCredentialsFactory = new Mock();
+
+ _credentialProfileStoreChain = new CredentialProfileStoreChain();
+
+ _mockCredentialChainFactory
+ .Setup(f => f.Create())
+ .Returns(_credentialProfileStoreChain);
+
+ // Create a temporary credentials file
+ _tempCredentialsFile = Path.GetTempFileName();
+ Environment.SetEnvironmentVariable("AWS_SHARED_CREDENTIALS_FILE", _tempCredentialsFile);
+
+ // Create a real SharedCredentialsFile instance
+ _sharedCredentialsFile = new SharedCredentialsFile(_tempCredentialsFile);
+
+ _mockSharedCredentialsFileFactory
+ .Setup(f => f.Create())
+ .Returns(_sharedCredentialsFile);
+ }
+
+ private AWSUtilities CreateAWSUtilities()
+ {
+ return new AWSUtilities(
+ _mockServiceProvider.Object,
+ _mockToolInteractiveService.Object,
+ _mockConsoleUtilities.Object,
+ _directoryManager,
+ _optionSettingHandler,
+ _mockCredentialChainFactory.Object,
+ _mockSharedCredentialsFileFactory.Object,
+ _mockAWSCredentialsFactory.Object
+ );
+ }
+
+ public void Dispose()
+ {
+ // Clean up the temporary file
+ if (File.Exists(_tempCredentialsFile))
+ {
+ File.Delete(_tempCredentialsFile);
+ }
+ Environment.SetEnvironmentVariable("AWS_SHARED_CREDENTIALS_FILE", null);
+ }
+
+ private void SetupCredentialsFile(params string[] profileNames)
+ {
+ var contents = new StringBuilder();
+ foreach (var profileName in profileNames)
+ {
+ contents.AppendLine($"[{profileName}]");
+ contents.AppendLine("aws_access_key_id = 123");
+ contents.AppendLine("aws_secret_access_key = abc");
+ contents.AppendLine();
+ }
+ File.WriteAllText(_tempCredentialsFile, contents.ToString());
+
+ // Re-create SharedCredentialsFile to pick up the changes
+ _sharedCredentialsFile = new SharedCredentialsFile(_tempCredentialsFile);
+ _mockSharedCredentialsFileFactory
+ .Setup(f => f.Create())
+ .Returns(_sharedCredentialsFile);
+ }
+
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithValidProfileName_ReturnsCredentials()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var profileName = "valid-profile";
+ var options = new CredentialProfileOptions
+ {
+ AccessKey = "abc",
+ SecretKey = "123"
+ };
+ var mockProfile = new CredentialProfile(profileName, options)
+ {
+ Region = RegionEndpoint.USEast1
+ };
+
+ var store = new CredentialProfileStoreChain();
+ store.RegisterProfile(mockProfile);
+ _credentialProfileStoreChain = store;
+
+ _mockCredentialChainFactory
+ .Setup(f => f.Create())
+ .Returns(_credentialProfileStoreChain);
+
+ // Act
+ var result = await awsUtilities.ResolveAWSCredentials(profileName);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Item1);
+ Assert.Equal("us-east-1", result.Item2);
+ }
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithValidProfileNameAndNullRegion_ReturnsCredentialsAndNullRegion()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var profileName = "valid-profile-no-region";
+ var options = new CredentialProfileOptions
+ {
+ AccessKey = "123",
+ SecretKey = "abc"
+ };
+ var mockProfile = new CredentialProfile(profileName, options)
+ {
+ Region = null
+ };
+
+ var store = new CredentialProfileStoreChain();
+ store.RegisterProfile(mockProfile);
+ _credentialProfileStoreChain = store;
+
+ _mockCredentialChainFactory
+ .Setup(f => f.Create())
+ .Returns(_credentialProfileStoreChain);
+
+ // Act
+ var result = await awsUtilities.ResolveAWSCredentials(profileName);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Item1);
+ Assert.Null(result.Item2);
+ }
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithInvalidProfileName_ThrowsException()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var profileName = "invalid-profile";
+
+ var store = new CredentialProfileStoreChain();
+ _credentialProfileStoreChain = store;
+
+ _mockCredentialChainFactory
+ .Setup(f => f.Create())
+ .Returns(_credentialProfileStoreChain);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => awsUtilities.ResolveAWSCredentials(profileName));
+ }
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithNullProfileName_UsesFallbackCredentials()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var mockAWSCredentials = new Mock().Object;
+
+ _mockAWSCredentialsFactory
+ .Setup(f => f.Create())
+ .Returns(mockAWSCredentials);
+
+ // Act
+ var result = await awsUtilities.ResolveAWSCredentials(null);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(mockAWSCredentials, result.Item1);
+ Assert.Null(result.Item2);
+ }
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithNullProfileNameAndFallbackException_PromptsUserToChooseProfile()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var profileNames = new List { "profile1", "profile2" };
+ var selectedProfileName = "profile1";
+
+ SetupCredentialsFile(profileNames.ToArray());
+
+ _mockAWSCredentialsFactory
+ .Setup(f => f.Create())
+ .Throws(new AmazonServiceException("No credentials found"));
+
+ _mockConsoleUtilities
+ .Setup(c => c.AskUserToChoose(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(selectedProfileName);
+
+ var store = new CredentialProfileStoreChain(_tempCredentialsFile);
+ _credentialProfileStoreChain = store;
+
+ _mockCredentialChainFactory
+ .Setup(f => f.Create())
+ .Returns(_credentialProfileStoreChain);
+
+ // Act
+ var result = await awsUtilities.ResolveAWSCredentials(null);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Item1);
+ Assert.Null(result.Item2); // Region will be null as we didn't set it in the file
+ _mockConsoleUtilities.Verify(c => c.AskUserToChoose(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task ResolveAWSCredentials_WithNoProfiles_ThrowsNoAWSCredentialsFoundException()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+
+ SetupCredentialsFile(); // Empty file, no profiles
+
+ _mockAWSCredentialsFactory
+ .Setup(f => f.Create())
+ .Throws(new AmazonServiceException("No credentials found"));
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => awsUtilities.ResolveAWSCredentials(null));
+ }
+
+ [Fact]
+ public void ResolveAWSRegion_WithNonNullRegion_ReturnsRegion()
+ {
+ // Arrange
+ var awsUtilities = CreateAWSUtilities();
+ var region = "us-west-2";
+
+ // Act
+ var result = awsUtilities.ResolveAWSRegion(region);
+
+ // Assert
+ Assert.Equal(region, result);
+ }
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/Commands/CommandFactoryTest.cs b/test/AWS.Deploy.CLI.UnitTests/Commands/CommandFactoryTest.cs
new file mode 100644
index 000000000..35af3992c
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/Commands/CommandFactoryTest.cs
@@ -0,0 +1,576 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Linq;
+using Amazon.Runtime;
+using System.Threading.Tasks;
+using AWS.Deploy.CLI.Commands;
+using AWS.Deploy.CLI.Commands.CommandHandlerInput;
+using AWS.Deploy.CLI.Commands.TypeHints;
+using AWS.Deploy.CLI.Utilities;
+using AWS.Deploy.Common;
+using AWS.Deploy.Common.Data;
+using AWS.Deploy.Common.DeploymentManifest;
+using AWS.Deploy.Common.IO;
+using AWS.Deploy.Common.Recipes;
+using AWS.Deploy.Common.Recipes.Validation;
+using AWS.Deploy.Orchestration;
+using AWS.Deploy.Orchestration.CDK;
+using AWS.Deploy.Orchestration.DisplayedResources;
+using AWS.Deploy.Orchestration.LocalUserSettings;
+using AWS.Deploy.Orchestration.ServiceHandlers;
+using AWS.Deploy.Orchestration.Utilities;
+using Moq;
+using Xunit;
+using Amazon.SecurityToken.Model;
+using System.Collections.Generic;
+using Amazon.Extensions.NETCore.Setup;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class CommandFactoryTests
+ {
+ private readonly Mock _mockServiceProvider;
+ private readonly Mock _mockToolInteractiveService;
+ private readonly Mock _mockOrchestratorInteractiveService;
+ private readonly Mock _mockCdkManager;
+ private readonly Mock _mockSystemCapabilityEvaluator;
+ private readonly Mock _mockCloudApplicationNameGenerator;
+ private readonly Mock _mockAwsUtilities;
+ private readonly Mock _mockAwsClientFactory;
+ private readonly Mock _mockAwsResourceQueryer;
+ private readonly Mock _mockProjectParserUtility;
+ private readonly Mock _mockCommandLineWrapper;
+ private readonly Mock _mockCdkProjectHandler;
+ private readonly Mock _mockDeploymentBundleHandler;
+ private readonly Mock _mockCloudFormationTemplateReader;
+ private readonly Mock _mockDeployedApplicationQueryer;
+ private readonly Mock _mockTypeHintCommandFactory;
+ private readonly Mock _mockDisplayedResourceHandler;
+ private readonly Mock _mockConsoleUtilities;
+ private readonly Mock _mockDirectoryManager;
+ private readonly Mock _mockFileManager;
+ private readonly Mock _mockDeploymentManifestEngine;
+ private readonly Mock _mockLocalUserSettingsEngine;
+ private readonly Mock _mockCdkVersionDetector;
+ private readonly Mock _mockAwsServiceHandler;
+ private readonly Mock _mockOptionSettingHandler;
+ private readonly Mock _mockValidatorFactory;
+ private readonly Mock _mockRecipeHandler;
+ private readonly Mock _mockDeployToolWorkspaceMetadata;
+ private readonly Mock _mockDeploymentSettingsHandler;
+
+ public CommandFactoryTests()
+ {
+ _mockServiceProvider = new Mock();
+ _mockToolInteractiveService = new Mock();
+ _mockOrchestratorInteractiveService = new Mock();
+ _mockCdkManager = new Mock();
+ _mockSystemCapabilityEvaluator = new Mock();
+ _mockCloudApplicationNameGenerator = new Mock();
+ _mockAwsUtilities = new Mock();
+ _mockAwsClientFactory = new Mock();
+ _mockAwsResourceQueryer = new Mock();
+ _mockProjectParserUtility = new Mock();
+ _mockCommandLineWrapper = new Mock();
+ _mockCdkProjectHandler = new Mock();
+ _mockDeploymentBundleHandler = new Mock();
+ _mockCloudFormationTemplateReader = new Mock();
+ _mockDeployedApplicationQueryer = new Mock();
+ _mockTypeHintCommandFactory = new Mock();
+ _mockDisplayedResourceHandler = new Mock();
+ _mockConsoleUtilities = new Mock();
+ _mockDirectoryManager = new Mock();
+ _mockFileManager = new Mock();
+ _mockDeploymentManifestEngine = new Mock();
+ _mockLocalUserSettingsEngine = new Mock();
+ _mockCdkVersionDetector = new Mock();
+ _mockAwsServiceHandler = new Mock();
+ _mockOptionSettingHandler = new Mock();
+ _mockValidatorFactory = new Mock();
+ _mockRecipeHandler = new Mock();
+ _mockDeployToolWorkspaceMetadata = new Mock();
+ _mockDeploymentSettingsHandler = new Mock();
+ }
+
+ private CommandFactory CreateCommandFactory()
+ {
+ return new CommandFactory(
+ _mockServiceProvider.Object,
+ _mockToolInteractiveService.Object,
+ _mockOrchestratorInteractiveService.Object,
+ _mockCdkManager.Object,
+ _mockSystemCapabilityEvaluator.Object,
+ _mockCloudApplicationNameGenerator.Object,
+ _mockAwsUtilities.Object,
+ _mockAwsClientFactory.Object,
+ _mockAwsResourceQueryer.Object,
+ _mockProjectParserUtility.Object,
+ _mockCommandLineWrapper.Object,
+ _mockCdkProjectHandler.Object,
+ _mockDeploymentBundleHandler.Object,
+ _mockCloudFormationTemplateReader.Object,
+ _mockDeployedApplicationQueryer.Object,
+ _mockTypeHintCommandFactory.Object,
+ _mockDisplayedResourceHandler.Object,
+ _mockConsoleUtilities.Object,
+ _mockDirectoryManager.Object,
+ _mockFileManager.Object,
+ _mockDeploymentManifestEngine.Object,
+ _mockLocalUserSettingsEngine.Object,
+ _mockCdkVersionDetector.Object,
+ _mockAwsServiceHandler.Object,
+ _mockOptionSettingHandler.Object,
+ _mockValidatorFactory.Object,
+ _mockRecipeHandler.Object,
+ _mockDeployToolWorkspaceMetadata.Object,
+ _mockDeploymentSettingsHandler.Object
+ );
+ }
+
+ [Fact]
+ public void BuildRootCommand_ReturnsRootCommandWithExpectedSubcommands()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+
+ // Act
+ var rootCommand = commandFactory.BuildRootCommand();
+
+ // Assert
+ Assert.NotNull(rootCommand);
+ Assert.Equal("dotnet-aws", rootCommand.Name);
+ Assert.Contains(rootCommand.Options, o => o.Name == "version");
+ Assert.Contains(rootCommand.Children, c => c.Name == "deploy");
+ Assert.Contains(rootCommand.Children, c => c.Name == "list-deployments");
+ Assert.Contains(rootCommand.Children, c => c.Name == "delete-deployment");
+ Assert.Contains(rootCommand.Children, c => c.Name == "deployment-project");
+ Assert.Contains(rootCommand.Children, c => c.Name == "server-mode");
+ }
+
+ [Fact]
+ public void BuildRootCommand_DeployCommandHasExpectedOptions()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+
+ // Act
+ var rootCommand = commandFactory.BuildRootCommand();
+ var deployCommand = rootCommand.Children.First(c => c.Name == "deploy") as Command;
+
+ // Assert
+ Assert.NotNull(deployCommand);
+ Assert.Contains(deployCommand.Options, o => o.Name == "profile");
+ Assert.Contains(deployCommand.Options, o => o.Name == "region");
+ Assert.Contains(deployCommand.Options, o => o.Name == "project-path");
+ Assert.Contains(deployCommand.Options, o => o.Name == "application-name");
+ Assert.Contains(deployCommand.Options, o => o.Name == "apply");
+ Assert.Contains(deployCommand.Options, o => o.Name == "diagnostics");
+ Assert.Contains(deployCommand.Options, o => o.Name == "silent");
+ Assert.Contains(deployCommand.Options, o => o.Name == "deployment-project");
+ Assert.Contains(deployCommand.Options, o => o.Name == "save-settings");
+ Assert.Contains(deployCommand.Options, o => o.Name == "save-all-settings");
+ }
+
+ // Add more tests for other commands and their options...
+
+ [Fact]
+ public void BuildRootCommand_ServerModeCommandHasExpectedOptions()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+
+ // Act
+ var rootCommand = commandFactory.BuildRootCommand();
+ var serverModeCommand = rootCommand.Children.First(c => c.Name == "server-mode") as Command;
+
+ // Assert
+ Assert.NotNull(serverModeCommand);
+ Assert.Contains(serverModeCommand.Options, o => o.Name == "port");
+ Assert.Contains(serverModeCommand.Options, o => o.Name == "parent-pid");
+ Assert.Contains(serverModeCommand.Options, o => o.Name == "unsecure-mode");
+ Assert.Contains(serverModeCommand.Options, o => o.Name == "diagnostics");
+ }
+
+ [Fact]
+ public async Task DeployCommand_UsesRegionFromCLIWhenProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromCLI = "us-west-2";
+ var regionFromProfile = "us-east-1";
+ var testProjectPath = "/path/to/project";
+
+ // Mock ProjectDefinition
+ var mockProjectDefinition = new ProjectDefinition(
+ new System.Xml.XmlDocument(),
+ testProjectPath,
+ testProjectPath,
+ "123");
+
+ _mockProjectParserUtility
+ .Setup(x => x.Parse(It.IsAny()))
+ .ReturnsAsync(mockProjectDefinition);
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns(regionFromCLI);
+
+ // Create a mock DeploymentSettings
+ var mockDeploymentSettings = new DeploymentSettings
+ {
+ AWSProfile = "deployment-profile",
+ AWSRegion = "deployment-region"
+ };
+
+ _mockDeploymentSettingsHandler
+ .Setup(x => x.ReadSettings(It.IsAny()))
+ .ReturnsAsync(mockDeploymentSettings);
+
+ _mockAwsResourceQueryer
+ .Setup(x => x.GetCallerIdentity(It.IsAny()))
+ .ReturnsAsync(new GetCallerIdentityResponse { Account = "123456789012" });
+
+
+ // Act
+ var result = await InvokeDeployCommandHandler(new DeployCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = regionFromCLI,
+ Apply = "some-settings-file.json",
+ ProjectPath = testProjectPath
+ });
+
+ // Assert
+ _mockProjectParserUtility.Verify(x => x.Parse(testProjectPath), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromCLI, null), Times.Once);
+ }
+
+ [Fact]
+ public async Task DeployCommand_UsesRegionFromProfileWhenCLIRegionNotProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromProfile = "us-east-1";
+ var testProjectPath = "/path/to/project";
+
+ // Mock ProjectDefinition
+ var mockProjectDefinition = new ProjectDefinition(
+ new System.Xml.XmlDocument(),
+ testProjectPath,
+ testProjectPath,
+ "123");
+
+ _mockProjectParserUtility
+ .Setup(x => x.Parse(It.IsAny()))
+ .ReturnsAsync(mockProjectDefinition);
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns((string r, string f) => r);
+
+ // Create a mock DeploymentSettings
+ var mockDeploymentSettings = new DeploymentSettings
+ {
+ AWSProfile = "deployment-profile",
+ AWSRegion = null // Ensure this is null to test our scenario
+ };
+
+ _mockDeploymentSettingsHandler
+ .Setup(x => x.ReadSettings(It.IsAny()))
+ .ReturnsAsync(mockDeploymentSettings);
+
+ _mockAwsResourceQueryer
+ .Setup(x => x.GetCallerIdentity(It.IsAny()))
+ .ReturnsAsync(new GetCallerIdentityResponse { Account = "123456789012" });
+
+ // Act
+ var result = await InvokeDeployCommandHandler(new DeployCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = null, // Not providing a region via CLI
+ Apply = "some-settings-file.json",
+ ProjectPath = testProjectPath
+ });
+
+ // Assert
+ _mockProjectParserUtility.Verify(x => x.Parse(testProjectPath), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromProfile, null), Times.Once);
+
+ // Verify that the region from the profile is used
+ _mockAwsResourceQueryer.Verify(x => x.GetCallerIdentity(regionFromProfile), Times.Once);
+ }
+
+ [Fact]
+ public void BuildRootCommand_DeleteCommandHasExpectedOptions()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+
+ // Act
+ var rootCommand = commandFactory.BuildRootCommand();
+ var deleteCommand = rootCommand.Children.First(c => c.Name == "delete-deployment") as Command;
+
+ // Assert
+ Assert.NotNull(deleteCommand);
+ Assert.Contains(deleteCommand.Options, o => o.Name == "profile");
+ Assert.Contains(deleteCommand.Options, o => o.Name == "region");
+ Assert.Contains(deleteCommand.Options, o => o.Name == "project-path");
+ Assert.Contains(deleteCommand.Options, o => o.Name == "diagnostics");
+ Assert.Contains(deleteCommand.Options, o => o.Name == "silent");
+
+ // Verify that the delete command has a deployment-name argument
+ Assert.Contains(deleteCommand.Arguments, a => a.Name == "deployment-name");
+ }
+
+ [Fact]
+ public void BuildRootCommand_ListCommandHasExpectedOptions()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+
+ // Act
+ var rootCommand = commandFactory.BuildRootCommand();
+ var listCommand = rootCommand.Children.First(c => c.Name == "list-deployments") as Command;
+
+ // Assert
+ Assert.NotNull(listCommand);
+ Assert.Contains(listCommand.Options, o => o.Name == "profile");
+ Assert.Contains(listCommand.Options, o => o.Name == "region");
+ Assert.Contains(listCommand.Options, o => o.Name == "diagnostics");
+ }
+
+ [Fact]
+ public async Task DeleteCommand_UsesRegionFromCLIWhenProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromCLI = "us-west-2";
+ var regionFromProfile = "us-east-1";
+ var deploymentName = "test-deployment";
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns(regionFromCLI);
+
+ _mockAwsClientFactory
+ .Setup(x => x.ConfigureAWSOptions(It.IsAny>()))
+ .Callback>(action =>
+ {
+ var options = new AWSOptions();
+ action(options);
+ Assert.Equal(regionFromCLI, options.Region.SystemName);
+ });
+
+ // Act
+ var result = await InvokeDeleteCommandHandler(new DeleteCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = regionFromCLI,
+ DeploymentName = deploymentName
+ });
+
+ // Assert
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromCLI, null), Times.Once);
+ _mockAwsClientFactory.Verify(x => x.ConfigureAWSOptions(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public async Task DeleteCommand_UsesRegionFromProfileWhenCLIRegionNotProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromProfile = "us-east-1";
+ var deploymentName = "test-deployment";
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns((string r, string f) => f);
+
+ _mockAwsClientFactory
+ .Setup(x => x.ConfigureAWSOptions(It.IsAny>()))
+ .Callback>(action =>
+ {
+ var options = new AWSOptions();
+ action(options);
+ Assert.Equal(regionFromProfile, options.Region.SystemName);
+ });
+
+ // Act
+ var result = await InvokeDeleteCommandHandler(new DeleteCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = null, // Not providing a region via CLI
+ DeploymentName = deploymentName
+ });
+
+ // Assert
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromProfile, null), Times.Once);
+ _mockAwsClientFactory.Verify(x => x.ConfigureAWSOptions(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public async Task ListCommand_UsesRegionFromCLIWhenProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromCLI = "us-west-2";
+ var regionFromProfile = "us-east-1";
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns(regionFromCLI);
+
+ _mockAwsClientFactory
+ .Setup(x => x.ConfigureAWSOptions(It.IsAny>()))
+ .Callback>(action =>
+ {
+ var options = new AWSOptions();
+ action(options);
+ Assert.Equal(regionFromCLI, options.Region.SystemName);
+ });
+
+ // Act
+ var result = await InvokeListCommandHandler(new ListCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = regionFromCLI
+ });
+
+ // Assert
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromCLI, null), Times.Once);
+ _mockAwsClientFactory.Verify(x => x.ConfigureAWSOptions(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public async Task ListCommand_UsesRegionFromProfileWhenCLIRegionNotProvided()
+ {
+ // Arrange
+ var commandFactory = CreateCommandFactory();
+ var mockCredentials = new Mock();
+ var testProfile = "test-profile";
+ var regionFromProfile = "us-east-1";
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSCredentials(It.IsAny()))
+ .Returns(Task.FromResult((Tuple.Create(mockCredentials.Object, regionFromProfile))));
+
+ _mockAwsUtilities
+ .Setup(x => x.ResolveAWSRegion(It.IsAny(), It.IsAny()))
+ .Returns((string r, string f) => f);
+
+ _mockAwsClientFactory
+ .Setup(x => x.ConfigureAWSOptions(It.IsAny>()))
+ .Callback>(action =>
+ {
+ var options = new AWSOptions();
+ action(options);
+ Assert.Equal(regionFromProfile, options.Region.SystemName);
+ });
+
+ // Act
+ var result = await InvokeListCommandHandler(new ListCommandHandlerInput
+ {
+ Profile = testProfile,
+ Region = null // Not providing a region via CLI
+ });
+
+ // Assert
+ _mockAwsUtilities.Verify(x => x.ResolveAWSCredentials(testProfile), Times.Once);
+ _mockAwsUtilities.Verify(x => x.ResolveAWSRegion(regionFromProfile, null), Times.Once);
+ _mockAwsClientFactory.Verify(x => x.ConfigureAWSOptions(It.IsAny>()), Times.Once);
+ }
+
+ private async Task InvokeDeployCommandHandler(DeployCommandHandlerInput input)
+ {
+ var args = new List { "deploy" };
+
+ if (!string.IsNullOrEmpty(input.Profile))
+ args.AddRange(new[] { "--profile", input.Profile });
+
+ if (!string.IsNullOrEmpty(input.Region))
+ args.AddRange(new[] { "--region", input.Region });
+
+ if (!string.IsNullOrEmpty(input.Apply))
+ args.AddRange(new[] { "--apply", input.Apply });
+
+ if (!string.IsNullOrEmpty(input.ProjectPath))
+ args.AddRange(new[] { "--project-path", input.ProjectPath });
+
+ var rootCommand = CreateCommandFactory().BuildRootCommand();
+ return await rootCommand.InvokeAsync(args.ToArray());
+ }
+
+
+ private async Task InvokeDeleteCommandHandler(DeleteCommandHandlerInput input)
+ {
+ var args = new List { "delete-deployment" };
+
+ if (!string.IsNullOrEmpty(input.Profile))
+ args.AddRange(new[] { "--profile", input.Profile });
+
+ if (!string.IsNullOrEmpty(input.Region))
+ args.AddRange(new[] { "--region", input.Region });
+
+ if (!string.IsNullOrEmpty(input.DeploymentName))
+ args.Add(input.DeploymentName);
+
+ var rootCommand = CreateCommandFactory().BuildRootCommand();
+ return await rootCommand.InvokeAsync(args.ToArray());
+ }
+
+ private async Task InvokeListCommandHandler(ListCommandHandlerInput input)
+ {
+ var args = new List { "list-deployments" };
+
+ if (!string.IsNullOrEmpty(input.Profile))
+ args.AddRange(new[] { "--profile", input.Profile });
+
+ if (!string.IsNullOrEmpty(input.Region))
+ args.AddRange(new[] { "--region", input.Region });
+
+ var rootCommand = CreateCommandFactory().BuildRootCommand();
+ return await rootCommand.InvokeAsync(args.ToArray());
+ }
+
+
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/CredentialProfileStoreChainFactoryTests.cs b/test/AWS.Deploy.CLI.UnitTests/CredentialProfileStoreChainFactoryTests.cs
new file mode 100644
index 000000000..8da6f39aa
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/CredentialProfileStoreChainFactoryTests.cs
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+using Xunit;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class CredentialProfileStoreChainFactoryTests
+ {
+ [Fact]
+ public void Create_ReturnsCredentialProfileStoreChainInstance()
+ {
+ // Arrange
+ var factory = new CredentialProfileStoreChainFactory();
+
+ // Act
+ var result = factory.Create();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+ }
+}
diff --git a/test/AWS.Deploy.CLI.UnitTests/SharedCredentialsFileFactoryTests.cs b/test/AWS.Deploy.CLI.UnitTests/SharedCredentialsFileFactoryTests.cs
new file mode 100644
index 000000000..5d8db76a7
--- /dev/null
+++ b/test/AWS.Deploy.CLI.UnitTests/SharedCredentialsFileFactoryTests.cs
@@ -0,0 +1,25 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using Amazon.Runtime.CredentialManagement;
+using Xunit;
+
+namespace AWS.Deploy.CLI.UnitTests
+{
+ public class SharedCredentialsFileFactoryTests
+ {
+ [Fact]
+ public void Create_ReturnsSharedCredentialsFileInstance()
+ {
+ // Arrange
+ var factory = new SharedCredentialsFileFactory();
+
+ // Act
+ var result = factory.Create();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+ }
+}