Skip to content

Commit

Permalink
Merge pull request #3 from wdolek/feature/introduce-integration-tests
Browse files Browse the repository at this point in the history
Introduce integration tests
  • Loading branch information
wdolek authored Aug 1, 2024
2 parents 2dd8656 + f3502d8 commit ddab4dc
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ jobs:
run: dotnet build --no-restore --configuration Release

- name: Test
run: dotnet test --no-build --configuration Release --verbosity normal
run: dotnet test --no-build --configuration Release --verbosity normal --filter "TestCategory!=Integration"
2 changes: 1 addition & 1 deletion .github/workflows/pack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
run: dotnet build --no-restore --configuration Release

- name: Test
run: dotnet test --no-build --configuration Release --verbosity normal
run: dotnet test --no-build --configuration Release --verbosity normal --filter "TestCategory!=Integration"

- name: Pack `W4k.Extensions.Configuration.Aws.SecretsManager`
run: dotnet pack --no-build --configuration Release -p:Version=${{ github.event.inputs.version }} -o artifacts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## W4k AWS Secrets Manager, Integration tests

In order to run the integration tests, you need to have a valid AWS credentials set up on your machine.
Integration tests require `w4ktest@admin` profile (see [`SecretsManagerTestFixture.cs`](./SecretsManagerTestFixture.cs)), it must be set up in your AWS credentials file (`~/.aws/credentials`).

```ini
[w4ktest]
aws_access_key_id = 00000000000000000000
aws_secret_access_key = 0000000000000000000000000000000000000000
aws_session_token = ...
account = ...
role = ...

[w4ktest@admin]
role_arn = arn:aws:iam::000000000000:role/my-role@admin
source_profile = w4ktest
```

Once credentials are set up, you can run the integration tests as any other tests in the solution.

To run only unit tests (and skip integration tests), filter out tests by category:

```bash
dotnet test --filter "TestCategory!=Integration"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using Microsoft.Extensions.Configuration;

namespace W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests;

[Category("Integration")]
public class SecretsManagerConfigSourceShould
{
[Test]
public void ThrowWhenSecretNotFound()
{
// act & assert
Assert.Throws<SecretNotFoundException>(
() =>
{
new ConfigurationBuilder().AddSecretsManager(
"w4k/awssm/unknown-secret-mandatory",
SecretsManagerTestFixture.SecretsManagerClient)
.Build();
});
}

[Test]
public void NotThrowWhenSecretIsOptional()
{
// act & assert
IConfiguration config = null!;
Assert.DoesNotThrow(
() =>
{
config = new ConfigurationBuilder().AddSecretsManager(
"w4k/awssm/unknown-secret-optional",
SecretsManagerTestFixture.SecretsManagerClient,
c => c.IsOptional = true)
.Build();
});

Assert.That(config, Is.Not.Null);
CollectionAssert.IsEmpty(config.GetChildren());
}

[Test]
public void FetchSecrets()
{
// arrange
var expected = new KeyValuePair<string, string>[]
{
new("ClientId", "my_client_id"),
new("ClientSecret", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
};

// act
var config = new ConfigurationBuilder().AddSecretsManager(
SecretsManagerTestFixture.KeyValueSecretName,
SecretsManagerTestFixture.SecretsManagerClient)
.Build();

var secrets = config.AsEnumerable().ToList();

// act
Assert.That(secrets, Has.Count.EqualTo(2));
CollectionAssert.AreEquivalent(expected, secrets);
}

[Test]
public void FetchSecretsWithPrefix()
{
// arrange
var expected = new KeyValuePair<string, string?>[]
{
new("App", null),
new("App:Secrets", null),
new("App:Secrets:ClientId", "my_client_id"),
new("App:Secrets:ClientSecret", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
};

// act
var config = new ConfigurationBuilder().AddSecretsManager(
SecretsManagerTestFixture.KeyValueSecretName,
SecretsManagerTestFixture.SecretsManagerClient,
c => c.ConfigurationKeyPrefix = "App:Secrets")
.Build();

var secrets = config.AsEnumerable().ToList();

// assert
CollectionAssert.AreEquivalent(expected, secrets);
}

[Test]
public void FetchSecretsWithKeyTransformation()
{
// arrange
var expected = new KeyValuePair<string, string>[]
{
new("id", "my_client_id"),
new("secret", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
};

var customKeyTransformer = new TestKeyTransformer(s => s.Replace("Client", "").ToLowerInvariant());

// act
var config = new ConfigurationBuilder().AddSecretsManager(
SecretsManagerTestFixture.KeyValueSecretName,
SecretsManagerTestFixture.SecretsManagerClient,
c => c.KeyTransformers.Add(customKeyTransformer))
.Build();

var secrets = config.AsEnumerable().ToList();

// act
CollectionAssert.AreEquivalent(expected, secrets);
}

[Test]
public void FetchComplexJsonSecret()
{
// arrange
var expected = new KeyValuePair<string, string?>[]
{
new("MyService", null),
new("MyService:Username", "saanvis"),
new("ApiKeys", null),
new("ApiKeys:Citizenship", "rosebud"),
new("ApiKeys:Universe", "42"),
new("PIN", null),
new("PIN:0", "5"),
new("PIN:1", "5"),
new("PIN:2", "5"),
new("PIN:3", "2"),
new("PIN:4", "3"),
new("PIN:5", "6"),
new("PIN:6", "8"),
};

// act
var config = new ConfigurationBuilder().AddSecretsManager(
SecretsManagerTestFixture.ComplexSecretName,
SecretsManagerTestFixture.SecretsManagerClient)
.Build();

var secrets = config.AsEnumerable().ToList();

// assert
CollectionAssert.AreEquivalent(expected, secrets);
}

[Test]
public void FetchMultipleSecrets()
{
// act
var config = new ConfigurationBuilder().AddSecretsManager(
[SecretsManagerTestFixture.KeyValueSecretName, SecretsManagerTestFixture.ComplexSecretName],
SecretsManagerTestFixture.SecretsManagerClient)
.Build();

var secrets = config.AsEnumerable().ToList();

// assert
Assert.That(secrets, Has.Count.EqualTo(15));
}

private class TestKeyTransformer : IConfigurationKeyTransformer
{
private readonly Func<string,string> _transform;

public TestKeyTransformer(Func<string, string> transform)
{
_transform = transform;
}

public string Transform(string key) => _transform(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;

namespace W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests;

[SetUpFixture]
public class SecretsManagerTestFixture
{
private const string AwsProfileName = "w4ktest@admin";

public static IAmazonSecretsManager SecretsManagerClient { get; private set; } = null!;

public static string KeyValueSecretName { get; private set; } = "";
public static string ComplexSecretName { get; private set; } = "";

[OneTimeSetUp]
public void OneTimeSetUp()
{
var storeChain = new CredentialProfileStoreChain();
if (!storeChain.TryGetAWSCredentials(AwsProfileName, out var credentials))
{
throw new InvalidOperationException($"""Unable to get AWS credentials using "{AwsProfileName}" profile.""");
}

var guid = Guid.NewGuid().ToString("N")[^8..];
KeyValueSecretName = $"{TestSecrets.KeyValueSecretName}/{guid}";
ComplexSecretName = $"{TestSecrets.ComplexSecretName}/{guid}";

var client = new AmazonSecretsManagerClient(credentials, RegionEndpoint.EUWest1);
CreateSecret(client, KeyValueSecretName, TestSecrets.KeyValueJson).GetAwaiter().GetResult();
CreateSecret(client, ComplexSecretName, TestSecrets.ComplexJson).GetAwaiter().GetResult();

SecretsManagerClient = client;
}

[OneTimeTearDown]
public void OneTimeTearDown()
{
var client = SecretsManagerClient;
DeleteSecret(client, KeyValueSecretName).GetAwaiter().GetResult();
DeleteSecret(client, ComplexSecretName).GetAwaiter().GetResult();

client.Dispose();
SecretsManagerClient = null!;
}

private static Task CreateSecret(IAmazonSecretsManager client, string secretName, string secretValue) =>
client.CreateSecretAsync(
new CreateSecretRequest
{
Name = secretName,
SecretString = secretValue,
Description = "W4k.Extensions.Configuration.Aws.SecretsManager integration secret"
});

private static Task DeleteSecret(IAmazonSecretsManager client, string secretName) =>
client.DeleteSecretAsync(
new DeleteSecretRequest
{
SecretId = secretName,
ForceDeleteWithoutRecovery = true,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests;

public static class TestSecrets
{
public const string KeyValueSecretName = "w4k/awssm/key-value-secret";
public const string KeyValueJson = """
{
"ClientId": "my_client_id",
"ClientSecret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
""";

public const string ComplexSecretName = "w4k/awssm/complex-secret";
public const string ComplexJson = """
{
"MyService__Username": "saanvis",
"ApiKeys": {
"Citizenship": "rosebud",
"Universe": "42"
},
"PIN": [ 5, 5, 5, 2, 3, 6, 8 ]
}
""";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.2"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="NUnit" Version="3.14.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\W4k.Extensions.Configuration.Aws.SecretsManager\W4k.Extensions.Configuration.Aws.SecretsManager.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ public void ReturnKeyValuePairsWhenTokenizingObject()
"Dentonite Toothpaste"
],
"hasLicenseToKill": true,
"married": null
"married": null,
"permissions": {
"kill": true
}
}
""";

Expand All @@ -54,7 +57,7 @@ public void ReturnKeyValuePairsWhenTokenizingObject()
.ToList();

// assert
Assert.That(result, Has.Count.EqualTo(7));
Assert.That(result, Has.Count.EqualTo(8));
Assert.Multiple(() =>
{
Assert.That(result[0].Key, Is.EqualTo("MI6:name"));
Expand All @@ -77,6 +80,9 @@ public void ReturnKeyValuePairsWhenTokenizingObject()
Assert.That(result[6].Key, Is.EqualTo("MI6:married"));
Assert.That(result[6].Value, Is.Null);
Assert.That(result[7].Key, Is.EqualTo("MI6:permissions:kill"));
Assert.That(result[7].Value, Is.EqualTo("True"));
});
}
}
6 changes: 6 additions & 0 deletions W4k.Extensions.Configuration.Aws.SecretsManager.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests", "W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests\W4k.Extensions.Configuration.Aws.SecretsManager.IntegrationTests.csproj", "{DBC85EDF-3FA2-436F-BBC4-C63F0081A373}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -25,5 +27,9 @@ Global
{C54F45CB-CEA0-40A8-8A1C-1A24DA0DBE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C54F45CB-CEA0-40A8-8A1C-1A24DA0DBE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C54F45CB-CEA0-40A8-8A1C-1A24DA0DBE7F}.Release|Any CPU.Build.0 = Release|Any CPU
{DBC85EDF-3FA2-436F-BBC4-C63F0081A373}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBC85EDF-3FA2-436F-BBC4-C63F0081A373}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBC85EDF-3FA2-436F-BBC4-C63F0081A373}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBC85EDF-3FA2-436F-BBC4-C63F0081A373}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.SecretsManager" Version="[3.3.0,)"/>
<PackageReference Include="AWSSDK.SecretsManager" Version="[3.7.0,)"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down

0 comments on commit ddab4dc

Please sign in to comment.