Skip to content

Commit

Permalink
Refactored AWSUtilities to allow dependency injection for test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
gcbeattyAWS committed Oct 30, 2024
1 parent 6cde192 commit a8b0275
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 4 deletions.
18 changes: 14 additions & 4 deletions src/AWS.Deploy.CLI/AWSUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,36 @@ public class AWSUtilities : IAWSUtilities
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly IServiceProvider _serviceProvider;
private readonly Func<ICredentialProfileStoreChain> _credentialChainFactory;
private readonly Func<ISharedCredentialsFile> _sharedCredentialsFileFactory;
private readonly IFallbackCredentialsFactory _fallbackCredentialsFactory;


public AWSUtilities(
IServiceProvider serviceProvider,
IToolInteractiveService toolInteractiveService,
IConsoleUtilities consoleUtilities,
IDirectoryManager directoryManager,
IOptionSettingHandler optionSettingHandler)
IOptionSettingHandler optionSettingHandler,
Func<ICredentialProfileStoreChain>? credentialChainFactory = null,
Func<ISharedCredentialsFile>? sharedCredentialsFileFactory = null,
IFallbackCredentialsFactory? fallbackCredentialsFactory = null)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
_consoleUtilities = consoleUtilities;
_directoryManager = directoryManager;
_optionSettingHandler = optionSettingHandler;
_credentialChainFactory = credentialChainFactory ?? (() =>new CredentialProfileStoreChainWrapper());
_sharedCredentialsFileFactory = sharedCredentialsFileFactory ?? (() => new SharedCredentialsFileWrapper());
_fallbackCredentialsFactory = fallbackCredentialsFactory ?? new FallbackCredentialsFactoryWrapper();
}

public async Task<Tuple<AWSCredentials, string?>> ResolveAWSCredentials(string? profileName)
{
async Task<Tuple<AWSCredentials, string?>> Resolve()
{
var chain = new CredentialProfileStoreChain();
var chain = _credentialChainFactory();

if (!string.IsNullOrEmpty(profileName))
{
Expand All @@ -70,7 +80,7 @@ public AWSUtilities(

try
{
var fallbackCredentials = FallbackCredentialsFactory.GetCredentials();
var fallbackCredentials = _fallbackCredentialsFactory.GetCredentials();

if (await CanLoadCredentials(fallbackCredentials))
{
Expand All @@ -85,7 +95,7 @@ public AWSUtilities(
_toolInteractiveService.WriteDebugLine(ex.PrettyPrint());
}

var sharedCredentials = new SharedCredentialsFile();
var sharedCredentials = _sharedCredentialsFileFactory();
if (sharedCredentials.ListProfileNames().Count == 0)
{
throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToResolveAWSCredentials, "Unable to resolve AWS credentials to access AWS.");
Expand Down
20 changes: 20 additions & 0 deletions src/AWS.Deploy.CLI/CredentialProfileStoreChainWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime;
using System.Collections.Generic;

namespace AWS.Deploy.CLI
{
public class CredentialProfileStoreChainWrapper : ICredentialProfileStoreChain
{
private readonly CredentialProfileStoreChain _chain = new CredentialProfileStoreChain();

public bool TryGetAWSCredentials(string profileName, out AWSCredentials credentials)
=> _chain.TryGetAWSCredentials(profileName, out credentials);

public bool TryGetProfile(string profileName, out CredentialProfile profile)
=> _chain.TryGetProfile(profileName, out profile);
}
}
14 changes: 14 additions & 0 deletions src/AWS.Deploy.CLI/FallbackCredentialsFactoryWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime;
using System.Collections.Generic;

namespace AWS.Deploy.CLI
{
public class FallbackCredentialsFactoryWrapper : IFallbackCredentialsFactory
{
public AWSCredentials GetCredentials() => FallbackCredentialsFactory.GetCredentials();
}
}
15 changes: 15 additions & 0 deletions src/AWS.Deploy.CLI/ICredentialProfileStoreChain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime;
using System.Collections.Generic;

namespace AWS.Deploy.CLI
{
public interface ICredentialProfileStoreChain
{
bool TryGetAWSCredentials(string profileName, out AWSCredentials credentials);
bool TryGetProfile(string profileName, out CredentialProfile profile);
}
}
12 changes: 12 additions & 0 deletions src/AWS.Deploy.CLI/IFallbackCredentialsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;

namespace AWS.Deploy.CLI
{
public interface IFallbackCredentialsFactory
{
AWSCredentials GetCredentials();
}
}
12 changes: 12 additions & 0 deletions src/AWS.Deploy.CLI/ISharedCredentialsFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Collections.Generic;

namespace AWS.Deploy.CLI
{
public interface ISharedCredentialsFile
{
List<string> ListProfileNames();
}
}
16 changes: 16 additions & 0 deletions src/AWS.Deploy.CLI/SharedCredentialsFileWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime;
using System.Collections.Generic;

namespace AWS.Deploy.CLI
{
public class SharedCredentialsFileWrapper : ISharedCredentialsFile
{
private readonly SharedCredentialsFile _sharedCredentialsFile = new SharedCredentialsFile();

public List<string> ListProfileNames() => _sharedCredentialsFile.ListProfileNames();
}
}
199 changes: 199 additions & 0 deletions test/AWS.Deploy.CLI.UnitTests/AWSUtilitiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using Amazon.Runtime.CredentialManagement;
using Amazon.Runtime;
using AWS.Deploy.CLI.Common.UnitTests.IO;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
using Moq;
using System;
using Xunit;
using System.Threading.Tasks;
using Amazon;
using System.Linq;
using System.Collections.Generic;

namespace AWS.Deploy.CLI.UnitTests
{
public class AWSUtilitiesTests
{
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly Mock<IToolInteractiveService> _mockToolInteractiveService;
private readonly Mock<IConsoleUtilities> _mockConsoleUtilities;
private readonly Mock<IServiceProvider> _mockServiceProvider;
private readonly Mock<ICredentialProfileStoreChain> _mockCredentialProfileStoreChain;
private readonly Mock<ISharedCredentialsFile> _mockSharedCredentialsFile;
private readonly Mock<IFallbackCredentialsFactory> _mockFallbackCredentialsFactory;

public AWSUtilitiesTests()
{
_directoryManager = new TestDirectoryManager();
_mockToolInteractiveService = new Mock<IToolInteractiveService>();
_mockConsoleUtilities = new Mock<IConsoleUtilities>();
_mockServiceProvider = new Mock<IServiceProvider>();
_optionSettingHandler = new Mock<IOptionSettingHandler>().Object;
_mockCredentialProfileStoreChain = new Mock<ICredentialProfileStoreChain>();
_mockSharedCredentialsFile = new Mock<ISharedCredentialsFile>();
_mockFallbackCredentialsFactory = new Mock<IFallbackCredentialsFactory>();
}

private AWSUtilities CreateAWSUtilities()
{
return new AWSUtilities(
_mockServiceProvider.Object,
_mockToolInteractiveService.Object,
_mockConsoleUtilities.Object,
_directoryManager,
_optionSettingHandler,
() => _mockCredentialProfileStoreChain.Object,
() => _mockSharedCredentialsFile.Object,
_mockFallbackCredentialsFactory.Object
);
}

delegate void CredentialsCallback(string profileName, out AWSCredentials credentials);
delegate bool CredentialsReturns(string profileName, out AWSCredentials credentials);

delegate void ProfileCallback(string profileName, out CredentialProfile profile);
delegate bool ProfileReturns(string profileName, out CredentialProfile profile);

[Fact]
public async Task ResolveAWSCredentials_WithValidProfileName_ReturnsCredentials()
{
// Arrange
var awsUtilities = CreateAWSUtilities();
var profileName = "valid-profile";
var mockCredentials = new Mock<AWSCredentials>();
var mockProfile = new CredentialProfile("valid-profile", new CredentialProfileOptions())
{
Region = RegionEndpoint.USEast1
};

_mockCredentialProfileStoreChain
.Setup(c => c.TryGetAWSCredentials(It.IsAny<string>(), out It.Ref<AWSCredentials>.IsAny))
.Callback(new CredentialsCallback((string name, out AWSCredentials creds) =>
{
creds = mockCredentials.Object;
}))
.Returns(new CredentialsReturns((string name, out AWSCredentials creds) =>
{
creds = mockCredentials.Object;
return true;
}));

_mockCredentialProfileStoreChain
.Setup(c => c.TryGetProfile(It.IsAny<string>(), out It.Ref<CredentialProfile>.IsAny))
.Callback(new ProfileCallback((string name, out CredentialProfile profile) =>
{
profile = mockProfile;
}))
.Returns(new ProfileReturns((string name, out CredentialProfile profile) =>
{
profile = mockProfile;
return true;
}));

// Act
var result = await awsUtilities.ResolveAWSCredentials(profileName);

// Assert
Assert.NotNull(result);
Assert.Equal(mockCredentials.Object, result.Item1);
Assert.Equal("us-east-1", result.Item2);
}

[Fact]
public async Task ResolveAWSCredentials_WithInvalidProfileName_ThrowsException()
{
// Arrange
var awsUtilities = CreateAWSUtilities();
var profileName = "invalid-profile";

_mockCredentialProfileStoreChain
.Setup(c => c.TryGetAWSCredentials(It.IsAny<string>(), out It.Ref<AWSCredentials>.IsAny))
.Returns(false);

// Act & Assert
await Assert.ThrowsAsync<FailedToGetCredentialsForProfile>(() => awsUtilities.ResolveAWSCredentials(profileName));
}

[Fact]
public async Task ResolveAWSCredentials_WithNullProfileName_UsesFallbackCredentials()
{
// Arrange
var awsUtilities = CreateAWSUtilities();
var mockFallbackCredentials = new Mock<AWSCredentials>();

_mockFallbackCredentialsFactory
.Setup(f => f.GetCredentials())
.Returns(mockFallbackCredentials.Object);

// Act
var result = await awsUtilities.ResolveAWSCredentials(null);

// Assert
Assert.NotNull(result);
Assert.Equal(mockFallbackCredentials.Object, result.Item1);
Assert.Null(result.Item2);
}

[Fact]
public async Task ResolveAWSCredentials_WithNoCredentials_PromptsUserToChooseProfile()
{
// Arrange
var awsUtilities = CreateAWSUtilities();
var profileNames = new List<string> { "profile1", "profile2" };
var selectedProfileName = "profile1";
var mockCredentials = new Mock<AWSCredentials>();
var mockProfile = new CredentialProfile(selectedProfileName, new CredentialProfileOptions())
{
Region = RegionEndpoint.USEast2
};

_mockFallbackCredentialsFactory
.Setup(f => f.GetCredentials())
.Throws(new AmazonServiceException("No credentials found"));

_mockSharedCredentialsFile
.Setup(s => s.ListProfileNames())
.Returns(profileNames);

_mockConsoleUtilities
.Setup(c => c.AskUserToChoose(
It.Is<List<string>>(list => list.SequenceEqual(profileNames)),
It.Is<string>(s => s == "Select AWS Credentials Profile"),
It.IsAny<string>(),
It.IsAny<string>()
))
.Returns(selectedProfileName);



_mockCredentialProfileStoreChain
.Setup(c => c.TryGetAWSCredentials(It.IsAny<string>(), out It.Ref<AWSCredentials>.IsAny))
.Returns(new CredentialsReturns((string name, out AWSCredentials creds) =>
{
creds = mockCredentials.Object;
return true;
}));

_mockCredentialProfileStoreChain
.Setup(c => c.TryGetProfile(It.IsAny<string>(), out It.Ref<CredentialProfile>.IsAny))
.Returns(new ProfileReturns((string name, out CredentialProfile profile) =>
{
profile = mockProfile;
return true;
}));

// Act
var result = await awsUtilities.ResolveAWSCredentials(null);

// Assert
Assert.NotNull(result);
Assert.Equal(mockCredentials.Object, result.Item1);
Assert.Equal("us-east-2", result.Item2);
_mockConsoleUtilities.Verify(c => c.AskUserToChoose(It.IsAny<List<string>>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}

}

}

0 comments on commit a8b0275

Please sign in to comment.