using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace StellaOps.FeatureFlags.Tests; [Trait("Category", "Unit")] public class FeatureFlagServiceCollectionExtensionsTests { [Fact] public void AddFeatureFlags_RegistersFeatureFlagService() { // Arrange var services = new ServiceCollection(); services.AddLogging(); // Act services.AddFeatureFlags(); var provider = services.BuildServiceProvider(); // Assert var service = provider.GetService(); service.Should().NotBeNull(); service.Should().BeOfType(); } [Fact] public void AddFeatureFlags_WithOptions_ConfiguresOptions() { // Arrange var services = new ServiceCollection(); services.AddLogging(); // Act services.AddFeatureFlags(options => { options.EnableCaching = true; options.CacheDuration = TimeSpan.FromMinutes(5); options.DefaultValue = true; }); var provider = services.BuildServiceProvider(); var options = provider.GetRequiredService>(); // Assert options.Value.EnableCaching.Should().BeTrue(); options.Value.CacheDuration.Should().Be(TimeSpan.FromMinutes(5)); options.Value.DefaultValue.Should().BeTrue(); } [Fact] public void AddConfigurationFeatureFlags_RegistersConfigurationProvider() { // Arrange var services = new ServiceCollection(); services.AddLogging(); var configData = new Dictionary { { "FeatureFlags:TestFlag", "true" } }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(configData) .Build(); services.AddSingleton(configuration); services.AddFeatureFlags(); services.AddConfigurationFeatureFlags(); // Act var provider = services.BuildServiceProvider(); var featureFlagProviders = provider.GetServices().ToList(); // Assert featureFlagProviders.Should().ContainSingle(); featureFlagProviders[0].Name.Should().Be("Configuration"); } [Fact] public void AddConfigurationFeatureFlags_WithCustomSectionName_UsesCorrectSection() { // Arrange var services = new ServiceCollection(); services.AddLogging(); var configData = new Dictionary { { "CustomFlags:MyFlag", "true" } }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(configData) .Build(); services.AddSingleton(configuration); services.AddFeatureFlags(); services.AddConfigurationFeatureFlags(sectionName: "CustomFlags"); // Act var provider = services.BuildServiceProvider(); var featureFlagService = provider.GetRequiredService(); var result = featureFlagService.IsEnabledAsync("MyFlag").Result; // Assert result.Should().BeTrue(); } [Fact] public void AddInMemoryFeatureFlags_RegistersInMemoryProvider() { // Arrange var services = new ServiceCollection(); services.AddLogging(); var flags = new Dictionary { { "InMemoryFlag", true } }; services.AddFeatureFlags(); services.AddInMemoryFeatureFlags(flags); // Act var provider = services.BuildServiceProvider(); var featureFlagService = provider.GetRequiredService(); var result = featureFlagService.IsEnabledAsync("InMemoryFlag").Result; // Assert result.Should().BeTrue(); } [Fact] public void AddInMemoryFeatureFlags_WithPriority_SetsCorrectPriority() { // Arrange var services = new ServiceCollection(); services.AddLogging(); services.AddFeatureFlags(); services.AddInMemoryFeatureFlags(new Dictionary { { "Flag", true } }, priority: 5); // Act var provider = services.BuildServiceProvider(); var featureFlagProviders = provider.GetServices().ToList(); // Assert featureFlagProviders.Single().Priority.Should().Be(5); } [Fact] public void AddFeatureFlagProvider_RegistersCustomProvider() { // Arrange var services = new ServiceCollection(); services.AddLogging(); services.AddFeatureFlags(); services.AddFeatureFlagProvider(); // Act var provider = services.BuildServiceProvider(); var featureFlagProviders = provider.GetServices().ToList(); // Assert featureFlagProviders.Should().ContainSingle(); featureFlagProviders[0].Should().BeOfType(); } [Fact] public void AddFeatureFlagProvider_WithFactory_RegistersCustomProvider() { // Arrange var services = new ServiceCollection(); services.AddLogging(); services.AddFeatureFlags(); services.AddFeatureFlagProvider(_ => new TestFeatureFlagProvider()); // Act var provider = services.BuildServiceProvider(); var featureFlagProviders = provider.GetServices().ToList(); // Assert featureFlagProviders.Should().ContainSingle(); featureFlagProviders[0].Should().BeOfType(); } [Fact] public void MultipleProviders_AreRegisteredInPriorityOrder() { // Arrange var services = new ServiceCollection(); services.AddLogging(); var configData = new Dictionary { { "FeatureFlags:SharedFlag", "false" } }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(configData) .Build(); services.AddSingleton(configuration); services.AddFeatureFlags(); services.AddConfigurationFeatureFlags(priority: 50); services.AddInMemoryFeatureFlags(new Dictionary { { "SharedFlag", true } }, priority: 10); // Act var provider = services.BuildServiceProvider(); var featureFlagService = provider.GetRequiredService(); var result = featureFlagService.IsEnabledAsync("SharedFlag").Result; // Assert - InMemory has lower priority number (higher precedence), so it wins result.Should().BeTrue(); } private class TestFeatureFlagProvider : FeatureFlagProviderBase { public override string Name => "Test"; public override Task TryGetFlagAsync( string flagKey, FeatureFlagEvaluationContext context, CancellationToken ct = default) { return Task.FromResult( new FeatureFlagResult(flagKey, true, null, "Test", Name)); } } }