feat: implement config builder, source, and extensions
This commit is contained in:
+331
@@ -0,0 +1,331 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
using Moq;
|
||||
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Configuration;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||
|
||||
namespace StevanFreeborn.Extensions.Configuration.Secure.Tests.Unit.Configuration;
|
||||
public class SecureConfigBuilderTests
|
||||
{
|
||||
private readonly SecureConfigBuilder _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldInitializeWithDefaultValues()
|
||||
{
|
||||
_sut.StorageProvider.Should().BeNull();
|
||||
_sut.CryptoProviderFactory.Should().BeNull();
|
||||
_sut.KeyProvider.Should().BeNull();
|
||||
_sut.LoggerFactory.Should().Be(NullLoggerFactory.Instance);
|
||||
_sut.SerializerOptions.PropertyNameCaseInsensitive.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseJsonFileStorage_WithOptions_ItShouldSetStorageProvider()
|
||||
{
|
||||
var options = new JsonStorageOptions
|
||||
{
|
||||
FileName = "test_config.json",
|
||||
DirectoryPath = "/tmp/config"
|
||||
};
|
||||
|
||||
var result = _sut.UseJsonFileStorage(options);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.StorageProvider.Should().NotBeNull();
|
||||
_sut.StorageProvider.Should().BeOfType<JsonFileStorageProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseJsonFileStorage_WithOptions_ItShouldUseProvidedOptions()
|
||||
{
|
||||
var options = new JsonStorageOptions
|
||||
{
|
||||
FileName = "custom.json",
|
||||
DirectoryPath = "/custom/path"
|
||||
};
|
||||
|
||||
_sut.UseJsonFileStorage(options);
|
||||
|
||||
_sut.StorageProvider.Should().BeOfType<JsonFileStorageProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseJsonFileStorage_WithConfigureAction_ItShouldSetStorageProvider()
|
||||
{
|
||||
var result = _sut.UseJsonFileStorage(opt => opt.FileName = "test.json");
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.StorageProvider.Should().NotBeNull();
|
||||
_sut.StorageProvider.Should().BeOfType<JsonFileStorageProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseJsonFileStorage_WithConfigureAction_ItShouldApplyConfiguration()
|
||||
{
|
||||
_sut.UseJsonFileStorage(opt =>
|
||||
{
|
||||
opt.FileName = "configured.json";
|
||||
opt.DirectoryPath = "/configured/path";
|
||||
});
|
||||
|
||||
_sut.StorageProvider.Should().BeOfType<JsonFileStorageProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseJsonFileStorage_WithNullConfigureAction_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => _sut.UseJsonFileStorage((Action<JsonStorageOptions>)null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("configure");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddJsonAotContext_WithContext_ItShouldAddToResolverChain()
|
||||
{
|
||||
var mockContext = new Mock<IJsonTypeInfoResolver>();
|
||||
|
||||
var result = _sut.AddJsonAotContext(mockContext.Object);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.SerializerOptions.TypeInfoResolverChain.Should().Contain(mockContext.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddJsonAotContext_WithNullContext_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => _sut.AddJsonAotContext(null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("context");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddJsonAotContext_WhenCalledMultipleTimes_ItShouldAddAllToChain()
|
||||
{
|
||||
var mockContext1 = new Mock<IJsonTypeInfoResolver>();
|
||||
var mockContext2 = new Mock<IJsonTypeInfoResolver>();
|
||||
|
||||
_sut.AddJsonAotContext(mockContext1.Object);
|
||||
_sut.AddJsonAotContext(mockContext2.Object);
|
||||
|
||||
_sut.SerializerOptions.TypeInfoResolverChain.Should().Contain(mockContext1.Object);
|
||||
_sut.SerializerOptions.TypeInfoResolverChain.Should().Contain(mockContext2.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseCustomStorage_WithProvider_ItShouldSetStorageProvider()
|
||||
{
|
||||
var mockProvider = new Mock<ISecureStorageProvider>();
|
||||
|
||||
var result = _sut.UseCustomStorage(mockProvider.Object);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.StorageProvider.Should().BeSameAs(mockProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseCustomStorage_WithNullProvider_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => _sut.UseCustomStorage(null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("provider");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithBase64EncryptionKey_WithValidKey_ItShouldSetKeyProvider()
|
||||
{
|
||||
var validKey = Convert.ToBase64String(new byte[32]);
|
||||
|
||||
var result = _sut.WithBase64EncryptionKey(validKey);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.KeyProvider.Should().NotBeNull();
|
||||
_sut.KeyProvider.Should().BeOfType<StaticKeyProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithBase64EncryptionKey_WithValidKey_ItShouldReturnCorrectKey()
|
||||
{
|
||||
var keyBytes = new byte[32];
|
||||
RandomNumberGenerator.Fill(keyBytes);
|
||||
|
||||
var validKey = Convert.ToBase64String(keyBytes);
|
||||
|
||||
_sut.WithBase64EncryptionKey(validKey);
|
||||
|
||||
_sut.KeyProvider!.GetKey().Should().Equal(keyBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithMachineIdKey_WhenCalled_ItShouldSetKeyProvider()
|
||||
{
|
||||
var result = _sut.WithMachineIdKey();
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.KeyProvider.Should().NotBeNull();
|
||||
_sut.KeyProvider.Should().BeOfType<MachineIdKeyProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithMachineIdKey_WhenCalled_ItShouldUseLoggerFactory()
|
||||
{
|
||||
var mockLoggerFactory = new Mock<ILoggerFactory>();
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
|
||||
mockLoggerFactory.Setup(f => f.CreateLogger(typeof(MachineIdKeyGenerator).FullName!))
|
||||
.Returns(mockLogger.Object);
|
||||
|
||||
_sut.WithLoggerFactory(mockLoggerFactory.Object);
|
||||
|
||||
_sut.WithMachineIdKey();
|
||||
|
||||
mockLoggerFactory.Verify(f => f.CreateLogger(typeof(MachineIdKeyGenerator).FullName!), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithCustomKeyProvider_WithProvider_ItShouldSetKeyProvider()
|
||||
{
|
||||
var mockProvider = new Mock<IEncryptionKeyProvider>();
|
||||
|
||||
var result = _sut.WithCustomKeyProvider(mockProvider.Object);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.KeyProvider.Should().BeSameAs(mockProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithCustomKeyProvider_WithNullProvider_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => _sut.WithCustomKeyProvider(null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("provider");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithLoggerFactory_WithFactory_ItShouldSetLoggerFactory()
|
||||
{
|
||||
var mockFactory = new Mock<ILoggerFactory>();
|
||||
|
||||
var result = _sut.WithLoggerFactory(mockFactory.Object);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.LoggerFactory.Should().BeSameAs(mockFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithLoggerFactory_WithNullFactory_ItShouldSetNullLoggerFactory()
|
||||
{
|
||||
var mockFactory = new Mock<ILoggerFactory>();
|
||||
_sut.WithLoggerFactory(mockFactory.Object);
|
||||
|
||||
_sut.WithLoggerFactory(null!);
|
||||
|
||||
_sut.LoggerFactory.Should().Be(NullLoggerFactory.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithAesCryptoProvider_WhenCalled_ItShouldSetCryptoProviderFactory()
|
||||
{
|
||||
var mockKeyProvider = new Mock<IEncryptionKeyProvider>();
|
||||
|
||||
var result = _sut.WithAesCryptoProvider();
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.CryptoProviderFactory.Should().NotBeNull();
|
||||
|
||||
var cryptoProvider = _sut.CryptoProviderFactory!(mockKeyProvider.Object);
|
||||
cryptoProvider.Should().BeOfType<AesCryptoProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithCustomCryptoProvider_WithFactory_ItShouldSetCryptoProviderFactory()
|
||||
{
|
||||
var mockCryptoProvider = new Mock<ICryptoProvider>();
|
||||
var mockKeyProvider = new Mock<IEncryptionKeyProvider>();
|
||||
Func<IEncryptionKeyProvider, ICryptoProvider> factory = (kp) => mockCryptoProvider.Object;
|
||||
|
||||
var result = _sut.WithCustomCryptoProvider(factory);
|
||||
|
||||
result.Should().BeSameAs(_sut);
|
||||
_sut.CryptoProviderFactory.Should().NotBeNull();
|
||||
|
||||
var cryptoProvider = _sut.CryptoProviderFactory!(mockKeyProvider.Object);
|
||||
cryptoProvider.Should().BeSameAs(mockCryptoProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithCustomCryptoProvider_WithNullFactory_ItShouldSetNull()
|
||||
{
|
||||
_sut.WithCustomCryptoProvider(null!);
|
||||
|
||||
_sut.CryptoProviderFactory.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MethodChaining_WhenAllMethodsCalled_ItShouldConfigureAllProperties()
|
||||
{
|
||||
var mockStorageProvider = new Mock<ISecureStorageProvider>();
|
||||
var mockKeyProvider = new Mock<IEncryptionKeyProvider>();
|
||||
var mockLoggerFactory = new Mock<ILoggerFactory>();
|
||||
var mockCryptoProvider = new Mock<ICryptoProvider>();
|
||||
|
||||
_sut.UseCustomStorage(mockStorageProvider.Object)
|
||||
.WithCustomKeyProvider(mockKeyProvider.Object)
|
||||
.WithLoggerFactory(mockLoggerFactory.Object)
|
||||
.WithCustomCryptoProvider((kp) => mockCryptoProvider.Object);
|
||||
|
||||
_sut.StorageProvider.Should().BeSameAs(mockStorageProvider.Object);
|
||||
_sut.KeyProvider.Should().BeSameAs(mockKeyProvider.Object);
|
||||
_sut.LoggerFactory.Should().BeSameAs(mockLoggerFactory.Object);
|
||||
_sut.CryptoProviderFactory.Should().NotBeNull();
|
||||
|
||||
var provider = _sut.CryptoProviderFactory!(mockKeyProvider.Object);
|
||||
provider.Should().BeSameAs(mockCryptoProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MethodChaining_WhenOverridingStorage_ItShouldUseLastSetProvider()
|
||||
{
|
||||
var mockProvider1 = new Mock<ISecureStorageProvider>();
|
||||
var mockProvider2 = new Mock<ISecureStorageProvider>();
|
||||
|
||||
_sut.UseCustomStorage(mockProvider1.Object)
|
||||
.UseCustomStorage(mockProvider2.Object);
|
||||
|
||||
_sut.StorageProvider.Should().BeSameAs(mockProvider2.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MethodChaining_WhenOverridingKeyProvider_ItShouldUseLastSetProvider()
|
||||
{
|
||||
var mockProvider1 = new Mock<IEncryptionKeyProvider>();
|
||||
var mockProvider2 = new Mock<IEncryptionKeyProvider>();
|
||||
|
||||
_sut.WithCustomKeyProvider(mockProvider1.Object)
|
||||
.WithCustomKeyProvider(mockProvider2.Object);
|
||||
|
||||
_sut.KeyProvider.Should().BeSameAs(mockProvider2.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MethodChaining_WhenOverridingLoggerFactory_ItShouldUseLastSetFactory()
|
||||
{
|
||||
var mockFactory1 = new Mock<ILoggerFactory>();
|
||||
var mockFactory2 = new Mock<ILoggerFactory>();
|
||||
|
||||
_sut.WithLoggerFactory(mockFactory1.Object)
|
||||
.WithLoggerFactory(mockFactory2.Object);
|
||||
|
||||
_sut.LoggerFactory.Should().BeSameAs(mockFactory2.Object);
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -65,7 +65,7 @@ public class SecureConfigProviderTests
|
||||
{ "BadKey", "encrypted_bad" },
|
||||
};
|
||||
|
||||
_mockLogger.Setup(m => m.IsEnabled(LogLevel.Error)).Returns(true);
|
||||
_mockLogger.Setup(m => m.IsEnabled(LogLevel.Warning)).Returns(true);
|
||||
_mockStorage.Setup(m => m.ReadAllAsync(It.IsAny<CancellationToken>())).ReturnsAsync(storedData);
|
||||
_mockCrypto.Setup(m => m.Decrypt("encrypted_valid")).Returns(@"{ ""Name"": ""test"" }");
|
||||
_mockCrypto.Setup(m => m.Decrypt("encrypted_bad")).Throws(new InvalidOperationException("Decryption failed"));
|
||||
@@ -76,7 +76,7 @@ public class SecureConfigProviderTests
|
||||
val.Should().Be("test");
|
||||
|
||||
_mockLogger.Verify(logger => logger.Log(
|
||||
LogLevel.Error,
|
||||
LogLevel.Warning,
|
||||
It.Is<EventId>(id => id.Id == 3),
|
||||
It.Is<It.IsAnyType>((state, type) => state.ToString()!.Contains("Failed to decrypt value for key")),
|
||||
It.IsAny<Exception>(),
|
||||
@@ -495,14 +495,14 @@ public class SecureConfigProviderTests
|
||||
[Fact]
|
||||
public void Load_WhenCalledWithInvalidJson_ItShouldLogErrorAndContinue()
|
||||
{
|
||||
_mockLogger.Setup(m => m.IsEnabled(LogLevel.Error)).Returns(true);
|
||||
_mockLogger.Setup(m => m.IsEnabled(LogLevel.Warning)).Returns(true);
|
||||
_mockStorage.Setup(m => m.ReadAllAsync(It.IsAny<CancellationToken>())).ReturnsAsync(new Dictionary<string, string> { { "Bad", "enc" } });
|
||||
_mockCrypto.Setup(m => m.Decrypt("enc")).Returns("not valid json{{{");
|
||||
|
||||
_sut.Load();
|
||||
|
||||
_mockLogger.Verify(x => x.Log(
|
||||
LogLevel.Error,
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>((v, _) => v.ToString()!.Contains("Bad")),
|
||||
It.IsAny<Exception>(),
|
||||
@@ -511,4 +511,4 @@ public class SecureConfigProviderTests
|
||||
Times.Once()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Moq;
|
||||
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Configuration;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||
|
||||
namespace StevanFreeborn.Extensions.Configuration.Secure.Tests.Unit.Configuration;
|
||||
|
||||
public class SecureConfigSourceTests
|
||||
{
|
||||
private readonly Mock<ISecureStorageProvider> _mockStorage = new();
|
||||
private readonly Mock<ICryptoProvider> _mockCrypto = new();
|
||||
private readonly Mock<ILoggerFactory> _mockLoggerFactory = new();
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenStorageProviderIsNull_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfigSource(
|
||||
storageProvider: null!,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("storageProvider");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCryptoProviderIsNull_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: null!,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("cryptoProvider");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenLoggerFactoryIsNull_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: null!
|
||||
);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>()
|
||||
.WithParameterName("loggerFactory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenAllDependenciesAreProvided_ItShouldNotThrow()
|
||||
{
|
||||
var act = () => new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WhenCalled_ItShouldReturnSecureConfigProvider()
|
||||
{
|
||||
var sut = new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
var mockBuilder = new Mock<IConfigurationBuilder>();
|
||||
|
||||
var result = sut.Build(mockBuilder.Object);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Should().BeOfType<SecureConfigProvider>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WhenCalled_ItShouldCreateLoggerFromFactory()
|
||||
{
|
||||
var mockLogger = new Mock<ILogger<SecureConfigProvider>>();
|
||||
_mockLoggerFactory
|
||||
.Setup(f => f.CreateLogger(typeof(SecureConfigProvider).FullName!))
|
||||
.Returns(mockLogger.Object);
|
||||
|
||||
var sut = new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
var mockBuilder = new Mock<IConfigurationBuilder>();
|
||||
sut.Build(mockBuilder.Object);
|
||||
|
||||
_mockLoggerFactory.Verify(
|
||||
f => f.CreateLogger(typeof(SecureConfigProvider).FullName!),
|
||||
Times.Once()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WhenCalledMultipleTimes_ItShouldReturnNewProviderInstance()
|
||||
{
|
||||
var sut = new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
var mockBuilder = new Mock<IConfigurationBuilder>();
|
||||
|
||||
var result1 = sut.Build(mockBuilder.Object);
|
||||
var result2 = sut.Build(mockBuilder.Object);
|
||||
|
||||
result1.Should().NotBeSameAs(result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WhenCalledWithNullBuilder_ItShouldStillReturnProvider()
|
||||
{
|
||||
var sut = new SecureConfigSource(
|
||||
storageProvider: _mockStorage.Object,
|
||||
cryptoProvider: _mockCrypto.Object,
|
||||
loggerFactory: _mockLoggerFactory.Object
|
||||
);
|
||||
|
||||
var result = sut.Build(builder: null!);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Should().BeOfType<SecureConfigProvider>();
|
||||
}
|
||||
}
|
||||
+100
-19
@@ -1,28 +1,31 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Moq;
|
||||
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Configuration;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StevanFreeborn.Extensions.Configuration.Secure.Tests.Unit.Configuration;
|
||||
|
||||
public class SecureConfigTests
|
||||
{
|
||||
private readonly Mock<ICryptoProvider> _mockCryptoProvider = new();
|
||||
private readonly Mock<ISecureStorageProvider> _mockStorageProvider = new();
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions = new();
|
||||
private readonly SecureConfig _sut;
|
||||
|
||||
public SecureConfigTests()
|
||||
{
|
||||
_sut = new(_mockStorageProvider.Object, _mockCryptoProvider.Object);
|
||||
_sut = new(_mockStorageProvider.Object, _mockCryptoProvider.Object, _jsonSerializerOptions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithNullStorageProvider_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfig(null!, _mockCryptoProvider.Object);
|
||||
var act = () => new SecureConfig(null!, _mockCryptoProvider.Object, _jsonSerializerOptions);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
@@ -30,7 +33,15 @@ public class SecureConfigTests
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithNullCryptoProvider_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfig(_mockStorageProvider.Object, null!);
|
||||
var act = () => new SecureConfig(_mockStorageProvider.Object, null!, _jsonSerializerOptions);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithNullJsonOptions_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = () => new SecureConfig(_mockStorageProvider.Object, _mockCryptoProvider.Object, null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
@@ -38,22 +49,22 @@ public class SecureConfigTests
|
||||
[Fact]
|
||||
public async Task SetAsync_WhenCalledWithNullKey_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = async () => await _sut.SetAsync(null!, string.Empty);
|
||||
var act = async () => await _sut.SetAsync(null!, string.Empty, SecureConfigTestsJsonContext.Default.String);
|
||||
|
||||
await act.Should().ThrowAsync<ArgumentNullException>();
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task SetAsync_WhenCalledWithNullValue_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = async () => await _sut.SetAsync<string>("Key", null!);
|
||||
var act = async () => await _sut.SetAsync("Key", null!, SecureConfigTestsJsonContext.Default.String);
|
||||
|
||||
await act.Should().ThrowAsync<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetAsync_WhenCalled_ItShouldSerializeGivenValueAndEncryptIt()
|
||||
public async Task SetAsync_WhenCalledWithoutJsonContextSet_ItShouldSerializeGivenValueAndEncryptIt()
|
||||
{
|
||||
var key = "Database";
|
||||
var config = new DummyConfig("localhost", 9999);
|
||||
@@ -62,17 +73,81 @@ public class SecureConfigTests
|
||||
|
||||
_mockCryptoProvider.Setup(m => m.Encrypt(json)).Returns(encryptedString);
|
||||
|
||||
var act = async () => await _sut.SetAsync(key, config);
|
||||
|
||||
await act.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetAsync_WhenCalledWithJsonContextSet_ItShouldSerializeGivenValueAndEncryptIt()
|
||||
{
|
||||
var key = "Database";
|
||||
var config = new DummyConfig("localhost", 9999);
|
||||
var encryptedString = "encryptedString";
|
||||
var json = JsonSerializer.Serialize(config);
|
||||
|
||||
_jsonSerializerOptions.TypeInfoResolverChain.Insert(0, SecureConfigTestsJsonContext.Default);
|
||||
_mockCryptoProvider.Setup(m => m.Encrypt(json)).Returns(encryptedString);
|
||||
|
||||
await _sut.SetAsync(key, config);
|
||||
|
||||
_mockStorageProvider.Verify(m => m.WriteAsync(key, encryptedString), Times.Once());
|
||||
_mockStorageProvider.Verify(m => m.WriteAsync(key, encryptedString, It.IsAny<CancellationToken>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetAsync_WhenCalledWithTypeInfo_ItShouldSerializeGivenValueAndEncryptIt()
|
||||
{
|
||||
var key = "Database";
|
||||
var config = new DummyConfig("localhost", 9999);
|
||||
var encryptedString = "encryptedString";
|
||||
var json = JsonSerializer.Serialize(config);
|
||||
|
||||
_mockCryptoProvider.Setup(m => m.Encrypt(json)).Returns(encryptedString);
|
||||
|
||||
await _sut.SetAsync(key, config, SecureConfigTestsJsonContext.Default.DummyConfig);
|
||||
|
||||
_mockStorageProvider.Verify(m => m.WriteAsync(key, encryptedString, It.IsAny<CancellationToken>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_WhenCalledWithNullKey_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = async () => await _sut.GetAsync<DummyConfig>(null!);
|
||||
var act = async () => await _sut.GetAsync(null!, SecureConfigTestsJsonContext.Default.DummyConfig);
|
||||
|
||||
await act.Should().ThrowAsync<ArgumentNullException>();
|
||||
await act.Should().ThrowAsync<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_WhenKeyExistsAndJsonContextNotSet_ItShouldReadDecryptAndDeserializeTheValue()
|
||||
{
|
||||
var key = "Database";
|
||||
var expectedConfig = new DummyConfig("localhost", 9999);
|
||||
var encryptedString = "encryptedString";
|
||||
var json = JsonSerializer.Serialize(expectedConfig);
|
||||
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key, It.IsAny<CancellationToken>())).ReturnsAsync(encryptedString);
|
||||
_mockCryptoProvider.Setup(m => m.Decrypt(encryptedString)).Returns(json);
|
||||
|
||||
var act = async () => await _sut.GetAsync<DummyConfig>(key);
|
||||
|
||||
await act.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_WhenKeyExistsAndJsonContextIsSet_ItShouldReadDecryptAndDeserializeTheValue()
|
||||
{
|
||||
var key = "Database";
|
||||
var expectedConfig = new DummyConfig("localhost", 9999);
|
||||
var encryptedString = "encryptedString";
|
||||
var json = JsonSerializer.Serialize(expectedConfig);
|
||||
|
||||
_jsonSerializerOptions.TypeInfoResolverChain.Insert(0, SecureConfigTestsJsonContext.Default);
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key, It.IsAny<CancellationToken>())).ReturnsAsync(encryptedString);
|
||||
_mockCryptoProvider.Setup(m => m.Decrypt(encryptedString)).Returns(json);
|
||||
|
||||
var result = await _sut.GetAsync(key, SecureConfigTestsJsonContext.Default.DummyConfig);
|
||||
|
||||
result.Should().BeEquivalentTo(expectedConfig);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -83,10 +158,10 @@ public class SecureConfigTests
|
||||
var encryptedString = "encryptedString";
|
||||
var json = JsonSerializer.Serialize(expectedConfig);
|
||||
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key)).ReturnsAsync(encryptedString);
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key, It.IsAny<CancellationToken>())).ReturnsAsync(encryptedString);
|
||||
_mockCryptoProvider.Setup(m => m.Decrypt(encryptedString)).Returns(json);
|
||||
|
||||
var result = await _sut.GetAsync<DummyConfig>(key);
|
||||
var result = await _sut.GetAsync(key, SecureConfigTestsJsonContext.Default.DummyConfig);
|
||||
|
||||
result.Should().BeEquivalentTo(expectedConfig);
|
||||
}
|
||||
@@ -98,9 +173,9 @@ public class SecureConfigTests
|
||||
var expectedConfig = new DummyConfig("localhost", 9999);
|
||||
var json = JsonSerializer.Serialize(expectedConfig);
|
||||
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key)).ReturnsAsync(string.Empty);
|
||||
_mockStorageProvider.Setup(m => m.ReadAsync(key, It.IsAny<CancellationToken>())).ReturnsAsync(string.Empty);
|
||||
|
||||
var result = await _sut.GetAsync<DummyConfig>(key);
|
||||
var result = await _sut.GetAsync(key, SecureConfigTestsJsonContext.Default.DummyConfig);
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
@@ -110,13 +185,19 @@ public class SecureConfigTests
|
||||
{
|
||||
var key = "Database";
|
||||
|
||||
_mockStorageProvider.Setup(m => m.DeleteAsync(key)).ReturnsAsync(true);
|
||||
_mockStorageProvider.Setup(m => m.DeleteAsync(key, It.IsAny<CancellationToken>())).ReturnsAsync(true);
|
||||
|
||||
var result = await _sut.DeleteAsync(key);
|
||||
|
||||
result.Should().BeTrue();
|
||||
_mockStorageProvider.Verify(m => m.DeleteAsync(key), Times.Once());
|
||||
_mockStorageProvider.Verify(m => m.DeleteAsync(key, It.IsAny<CancellationToken>()), Times.Once());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record DummyConfig(string Host, int Port);
|
||||
internal sealed record DummyConfig(string Host, int Port);
|
||||
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(DummyConfig))]
|
||||
internal partial class SecureConfigTestsJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user