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 { { "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 { { "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); } }