197 lines
5.7 KiB
C#
197 lines
5.7 KiB
C#
using FluentAssertions;
|
|
using Microsoft.Extensions.Configuration;
|
|
using StellaOps.FeatureFlags.Providers;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.FeatureFlags.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public class ConfigurationFeatureFlagProviderTests : IDisposable
|
|
{
|
|
private readonly ConfigurationFeatureFlagProvider _sut;
|
|
private readonly IConfigurationRoot _configuration;
|
|
|
|
public ConfigurationFeatureFlagProviderTests()
|
|
{
|
|
var configData = new Dictionary<string, string?>
|
|
{
|
|
{ "FeatureFlags:SimpleFlag", "true" },
|
|
{ "FeatureFlags:DisabledFlag", "false" },
|
|
{ "FeatureFlags:ComplexFlag:Enabled", "true" },
|
|
{ "FeatureFlags:ComplexFlag:Variant", "blue" },
|
|
{ "FeatureFlags:ComplexFlag:Description", "A complex feature flag" },
|
|
{ "FeatureFlags:VariantOnlyFlag:Enabled", "false" },
|
|
{ "FeatureFlags:VariantOnlyFlag:Variant", "control" },
|
|
{ "CustomSection:MyFlag", "true" }
|
|
};
|
|
|
|
_configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(configData)
|
|
.Build();
|
|
|
|
_sut = new ConfigurationFeatureFlagProvider(_configuration);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_sut.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_ReturnsConfiguration()
|
|
{
|
|
_sut.Name.Should().Be("Configuration");
|
|
}
|
|
|
|
[Fact]
|
|
public void Priority_ReturnsDefaultValue()
|
|
{
|
|
_sut.Priority.Should().Be(50);
|
|
}
|
|
|
|
[Fact]
|
|
public void SupportsWatch_ReturnsTrue()
|
|
{
|
|
_sut.SupportsWatch.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryGetFlagAsync_ReturnsTrueForEnabledSimpleFlag()
|
|
{
|
|
// Act
|
|
var result = await _sut.TryGetFlagAsync("SimpleFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().BeTrue();
|
|
result.Key.Should().Be("SimpleFlag");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryGetFlagAsync_ReturnsFalseForDisabledSimpleFlag()
|
|
{
|
|
// Act
|
|
var result = await _sut.TryGetFlagAsync("DisabledFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryGetFlagAsync_ReturnsNullForUnknownFlag()
|
|
{
|
|
// Act
|
|
var result = await _sut.TryGetFlagAsync("UnknownFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryGetFlagAsync_ParsesComplexFlagWithVariant()
|
|
{
|
|
// Act
|
|
var result = await _sut.TryGetFlagAsync("ComplexFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().BeTrue();
|
|
result.Variant.Should().Be("blue");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryGetFlagAsync_ParsesComplexFlagWithEnabledFalse()
|
|
{
|
|
// Act
|
|
var result = await _sut.TryGetFlagAsync("VariantOnlyFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().BeFalse();
|
|
result.Variant.Should().Be("control");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ListFlagsAsync_ReturnsAllDefinedFlags()
|
|
{
|
|
// Act
|
|
var result = await _sut.ListFlagsAsync();
|
|
|
|
// Assert
|
|
result.Should().HaveCount(4);
|
|
result.Select(f => f.Key).Should().Contain([
|
|
"SimpleFlag", "DisabledFlag", "ComplexFlag", "VariantOnlyFlag"
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ListFlagsAsync_ParsesDefaultValuesCorrectly()
|
|
{
|
|
// Act
|
|
var result = await _sut.ListFlagsAsync();
|
|
|
|
// Assert
|
|
var simpleFlag = result.Single(f => f.Key == "SimpleFlag");
|
|
simpleFlag.DefaultValue.Should().BeTrue();
|
|
|
|
var disabledFlag = result.Single(f => f.Key == "DisabledFlag");
|
|
disabledFlag.DefaultValue.Should().BeFalse();
|
|
|
|
var complexFlag = result.Single(f => f.Key == "ComplexFlag");
|
|
complexFlag.DefaultValue.Should().BeTrue();
|
|
complexFlag.Description.Should().Be("A complex feature flag");
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithCustomSection_ReadsFromThatSection()
|
|
{
|
|
// Arrange
|
|
using var provider = new ConfigurationFeatureFlagProvider(_configuration, "CustomSection");
|
|
|
|
// Act & Assert
|
|
var result = provider.TryGetFlagAsync("MyFlag", FeatureFlagEvaluationContext.Empty).Result;
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithCustomPriority_SetsCorrectPriority()
|
|
{
|
|
// Arrange
|
|
using var provider = new ConfigurationFeatureFlagProvider(_configuration, priority: 25);
|
|
|
|
// Assert
|
|
provider.Priority.Should().Be(25);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("TRUE", true)]
|
|
[InlineData("True", true)]
|
|
[InlineData("true", true)]
|
|
[InlineData("FALSE", false)]
|
|
[InlineData("False", false)]
|
|
[InlineData("false", false)]
|
|
public async Task TryGetFlagAsync_HandlesCaseInsensitiveBooleanValues(string configValue, bool expected)
|
|
{
|
|
// Arrange
|
|
var configData = new Dictionary<string, string?>
|
|
{
|
|
{ "FeatureFlags:CaseFlag", configValue }
|
|
};
|
|
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(configData)
|
|
.Build();
|
|
|
|
using var provider = new ConfigurationFeatureFlagProvider(configuration);
|
|
|
|
// Act
|
|
var result = await provider.TryGetFlagAsync("CaseFlag", FeatureFlagEvaluationContext.Empty);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Enabled.Should().Be(expected);
|
|
}
|
|
}
|