diff --git a/src/Aspire.Cli/Commands/BaseCommand.cs b/src/Aspire.Cli/Commands/BaseCommand.cs index 5a3b2a37b9f..ae15a2f2cda 100644 --- a/src/Aspire.Cli/Commands/BaseCommand.cs +++ b/src/Aspire.Cli/Commands/BaseCommand.cs @@ -33,7 +33,7 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU var exitCode = await ExecuteAsync(parseResult, cancellationToken); - if (UpdateNotificationsEnabled && features.IsFeatureEnabled(KnownFeatures.UpdateNotificationsEnabled, true)) + if (UpdateNotificationsEnabled && features.Enabled()) { try { diff --git a/src/Aspire.Cli/Commands/RootCommand.cs b/src/Aspire.Cli/Commands/RootCommand.cs index 42cb2a73444..a496840e6dc 100644 --- a/src/Aspire.Cli/Commands/RootCommand.cs +++ b/src/Aspire.Cli/Commands/RootCommand.cs @@ -113,7 +113,7 @@ public RootCommand( Subcommands.Add(extensionInternalCommand); Subcommands.Add(mcpCommand); - if (featureFlags.IsFeatureEnabled(KnownFeatures.ExecCommandEnabled, false)) + if (featureFlags.Enabled()) { Subcommands.Add(execCommand); } diff --git a/src/Aspire.Cli/Commands/RunCommand.cs b/src/Aspire.Cli/Commands/RunCommand.cs index da1902b1c26..7927589642b 100644 --- a/src/Aspire.Cli/Commands/RunCommand.cs +++ b/src/Aspire.Cli/Commands/RunCommand.cs @@ -138,7 +138,7 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell await _certificateService.EnsureCertificatesTrustedAsync(_runner, cancellationToken); - var watch = !isSingleFileAppHost && (_features.IsFeatureEnabled(KnownFeatures.DefaultWatchEnabled, defaultValue: false) || (isExtensionHost && !startDebugSession)); + var watch = !isSingleFileAppHost && (_features.Enabled() || (isExtensionHost && !startDebugSession)); if (!watch) { diff --git a/src/Aspire.Cli/Commands/UpdateCommand.cs b/src/Aspire.Cli/Commands/UpdateCommand.cs index cab276b9f6c..694f6483a05 100644 --- a/src/Aspire.Cli/Commands/UpdateCommand.cs +++ b/src/Aspire.Cli/Commands/UpdateCommand.cs @@ -65,7 +65,7 @@ public UpdateCommand( Options.Add(selfOption); // Customize description based on whether staging channel is enabled - var isStagingEnabled = _features.IsFeatureEnabled(KnownFeatures.StagingChannelEnabled, false); + var isStagingEnabled = _features.Enabled(); var channelOption = new Option("--channel") { diff --git a/src/Aspire.Cli/Configuration/FeatureFlags.cs b/src/Aspire.Cli/Configuration/FeatureFlags.cs new file mode 100644 index 00000000000..348fa4e0d1d --- /dev/null +++ b/src/Aspire.Cli/Configuration/FeatureFlags.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Cli.Configuration; + +/// +/// Feature flag for enabling update notifications. +/// +internal sealed class UpdateNotificationsEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "updateNotificationsEnabled"; + public bool DefaultValue => true; +} + +/// +/// Feature flag for enabling minimum SDK version checking. +/// +internal sealed class MinimumSdkCheckEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "minimumSdkCheckEnabled"; + public bool DefaultValue => true; +} + +/// +/// Feature flag for enabling the exec command. +/// +internal sealed class ExecCommandEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "execCommandEnabled"; + public bool DefaultValue => false; +} + +/// +/// Feature flag for enabling orphan detection with timestamp. +/// +internal sealed class OrphanDetectionWithTimestampEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "orphanDetectionWithTimestampEnabled"; + public bool DefaultValue => true; +} + +/// +/// Feature flag for showing deprecated packages. +/// +internal sealed class ShowDeprecatedPackagesFeature : IFeatureFlag +{ + public string ConfigurationKey => "showDeprecatedPackages"; + public bool DefaultValue => false; +} + +/// +/// Feature flag for enabling package search disk caching. +/// +internal sealed class PackageSearchDiskCachingEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "packageSearchDiskCachingEnabled"; + public bool DefaultValue => true; +} + +/// +/// Feature flag for enabling the staging channel. +/// +internal sealed class StagingChannelEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "stagingChannelEnabled"; + public bool DefaultValue => false; +} + +/// +/// Feature flag for enabling watch mode by default. +/// +internal sealed class DefaultWatchEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "defaultWatchEnabled"; + public bool DefaultValue => false; +} + +/// +/// Feature flag for showing all templates. +/// +internal sealed class ShowAllTemplatesFeature : IFeatureFlag +{ + public string ConfigurationKey => "showAllTemplates"; + public bool DefaultValue => false; +} + +/// +/// Feature flag for enabling .NET SDK installation. +/// +internal sealed class DotNetSdkInstallationEnabledFeature : IFeatureFlag +{ + public string ConfigurationKey => "dotnetSdkInstallationEnabled"; + public bool DefaultValue => true; +} diff --git a/src/Aspire.Cli/Configuration/Features.cs b/src/Aspire.Cli/Configuration/Features.cs index 28d11c87817..e3ead10eef8 100644 --- a/src/Aspire.Cli/Configuration/Features.cs +++ b/src/Aspire.Cli/Configuration/Features.cs @@ -7,6 +7,8 @@ namespace Aspire.Cli.Configuration; internal sealed class Features(IConfiguration configuration) : IFeatures { + private static readonly Dictionary s_featureFlagCache = new(); + public bool IsFeatureEnabled(string feature, bool defaultValue) { var configKey = $"features:{feature}"; @@ -18,6 +20,23 @@ public bool IsFeatureEnabled(string feature, bool defaultValue) return defaultValue; } - return bool.TryParse(value, out var enabled) && enabled; + if (bool.TryParse(value, out var enabled)) + { + return enabled; + } + + return defaultValue; + } + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + // Cache the feature flag instance to avoid repeated allocations + if (!s_featureFlagCache.TryGetValue(typeof(TFeatureFlag), out var featureFlag)) + { + featureFlag = new TFeatureFlag(); + s_featureFlagCache[typeof(TFeatureFlag)] = featureFlag; + } + + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); } } \ No newline at end of file diff --git a/src/Aspire.Cli/Configuration/IFeatureFlag.cs b/src/Aspire.Cli/Configuration/IFeatureFlag.cs new file mode 100644 index 00000000000..7b73148c8da --- /dev/null +++ b/src/Aspire.Cli/Configuration/IFeatureFlag.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Cli.Configuration; + +/// +/// Represents a feature flag with its configuration key and default value. +/// +internal interface IFeatureFlag +{ + /// + /// Gets the configuration key used to look up the feature flag value. + /// + string ConfigurationKey { get; } + + /// + /// Gets the default value for the feature flag when not explicitly configured. + /// + bool DefaultValue { get; } +} diff --git a/src/Aspire.Cli/Configuration/IFeatures.cs b/src/Aspire.Cli/Configuration/IFeatures.cs index 3ed4ea470b4..80ed05c0280 100644 --- a/src/Aspire.Cli/Configuration/IFeatures.cs +++ b/src/Aspire.Cli/Configuration/IFeatures.cs @@ -6,4 +6,11 @@ namespace Aspire.Cli.Configuration; internal interface IFeatures { bool IsFeatureEnabled(string featureFlag, bool defaultValue); + + /// + /// Checks if a feature flag is enabled using a type-safe feature flag definition. + /// + /// The type of feature flag to check. + /// True if the feature is enabled, false otherwise. + bool Enabled() where TFeatureFlag : IFeatureFlag, new(); } \ No newline at end of file diff --git a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs index 9cad830834c..e98a7dc550e 100644 --- a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs +++ b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs @@ -269,7 +269,7 @@ public async Task RunAsync(FileInfo projectFile, bool watch, bool noBuild, } // Check if update notifications are disabled and set version check environment variable - if (!features.IsFeatureEnabled(KnownFeatures.UpdateNotificationsEnabled, defaultValue: true)) + if (!features.Enabled()) { // Copy the environment if we haven't already if (finalEnv == env) @@ -300,7 +300,7 @@ public async Task RunAsync(FileInfo projectFile, bool watch, bool noBuild, } } - if (features.IsFeatureEnabled(KnownFeatures.DotNetSdkInstallationEnabled, true)) + if (features.Enabled()) { if (finalEnv == env) { @@ -551,7 +551,7 @@ public virtual async Task ExecuteAsync(string[] args, IDictionary()) { startInfo.EnvironmentVariables[KnownConfigNames.CliProcessStarted] = GetCurrentProcessStartTime().ToString(CultureInfo.InvariantCulture); } @@ -928,7 +928,7 @@ public async Task ComputeNuGetConfigHierarchySha256Async(DirectoryInfo w using var activity = telemetry.ActivitySource.StartActivity(); string? rawKey = null; - bool cacheEnabled = useCache && features.IsFeatureEnabled(KnownFeatures.PackageSearchDiskCachingEnabled, defaultValue: true); + bool cacheEnabled = useCache && features.Enabled(); if (cacheEnabled) { try diff --git a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs index 5f89e672d52..15d76ff38df 100644 --- a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs +++ b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs @@ -48,7 +48,7 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf } } - if (!features.IsFeatureEnabled(KnownFeatures.MinimumSdkCheckEnabled, true)) + if (!features.Enabled()) { // If the feature is disabled, we assume the SDK is available return (true, null, minimumVersion, forceInstall); diff --git a/src/Aspire.Cli/NuGet/NuGetPackageCache.cs b/src/Aspire.Cli/NuGet/NuGetPackageCache.cs index d4d02b7d77f..0cb2c045120 100644 --- a/src/Aspire.Cli/NuGet/NuGetPackageCache.cs +++ b/src/Aspire.Cli/NuGet/NuGetPackageCache.cs @@ -134,7 +134,7 @@ public async Task> GetPackagesAsync(DirectoryInfo work var isOfficialPackage = IsOfficialOrCommunityToolkitPackage(p.Id); // Apply deprecated package filter unless the user wants to show deprecated packages - if (isOfficialPackage && !features.IsFeatureEnabled(KnownFeatures.ShowDeprecatedPackages, defaultValue: false)) + if (isOfficialPackage && !features.Enabled()) { return !s_deprecatedPackages.Contains(p.Id); } diff --git a/src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs b/src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs index ccaded3665d..f1fb0e72704 100644 --- a/src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs +++ b/src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs @@ -61,7 +61,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _ = Task.Run(async () => { - if (features.IsFeatureEnabled(KnownFeatures.UpdateNotificationsEnabled, true)) + if (features.Enabled()) { try { diff --git a/src/Aspire.Cli/Packaging/PackagingService.cs b/src/Aspire.Cli/Packaging/PackagingService.cs index 5ac90df5fef..fd4f1a1607e 100644 --- a/src/Aspire.Cli/Packaging/PackagingService.cs +++ b/src/Aspire.Cli/Packaging/PackagingService.cs @@ -54,7 +54,7 @@ public Task> GetChannelsAsync(CancellationToken canc var channels = new List([defaultChannel, stableChannel]); // Add staging channel if feature is enabled (after stable, before daily) - if (features.IsFeatureEnabled(KnownFeatures.StagingChannelEnabled, false)) + if (features.Enabled()) { var stagingChannel = CreateStagingChannel(); if (stagingChannel is not null) diff --git a/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs b/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs index b44f521cc2f..bd9ca008a3b 100644 --- a/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs +++ b/src/Aspire.Cli/Templating/DotNetTemplateFactory.cs @@ -29,7 +29,7 @@ internal class DotNetTemplateFactory( { public IEnumerable GetTemplates() { - var showAllTemplates = features.IsFeatureEnabled(KnownFeatures.ShowAllTemplates, false); + var showAllTemplates = features.Enabled(); return GetTemplatesCore(showAllTemplates); } diff --git a/src/Aspire.Cli/Utils/SdkInstallHelper.cs b/src/Aspire.Cli/Utils/SdkInstallHelper.cs index 803b399f7c8..d5bacb219e8 100644 --- a/src/Aspire.Cli/Utils/SdkInstallHelper.cs +++ b/src/Aspire.Cli/Utils/SdkInstallHelper.cs @@ -52,9 +52,9 @@ public static async Task EnsureSdkInstalledAsync( } // Only offer to install if: - // 1. The feature is enabled (default: false) + // 1. The feature is enabled // 2. We support interactive input OR forceInstall is true (for testing) - if (features.IsFeatureEnabled(KnownFeatures.DotNetSdkInstallationEnabled, defaultValue: false) && + if (features.Enabled() && (hostEnvironment?.SupportsInteractiveInput == true || forceInstall)) { bool shouldInstall; diff --git a/tests/Aspire.Cli.Tests/Configuration/FeaturesTests.cs b/tests/Aspire.Cli.Tests/Configuration/FeaturesTests.cs new file mode 100644 index 00000000000..1c970adcced --- /dev/null +++ b/tests/Aspire.Cli.Tests/Configuration/FeaturesTests.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Cli.Configuration; +using Microsoft.Extensions.Configuration; + +namespace Aspire.Cli.Tests.Configuration; + +public class FeaturesTests +{ + [Fact] + public void Enabled_WithNoConfiguration_ReturnsDefaultValue() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var features = new Features(configuration); + + // Act & Assert - Test various feature flags with different defaults + Assert.True(features.Enabled()); + Assert.True(features.Enabled()); + Assert.False(features.Enabled()); + Assert.True(features.Enabled()); + Assert.False(features.Enabled()); + Assert.True(features.Enabled()); + Assert.False(features.Enabled()); + Assert.False(features.Enabled()); + Assert.False(features.Enabled()); + Assert.True(features.Enabled()); + } + + [Fact] + public void Enabled_WithConfigurationSetToTrue_ReturnsTrue() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:execCommandEnabled"] = "true" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert + Assert.True(features.Enabled()); + } + + [Fact] + public void Enabled_WithConfigurationSetToFalse_ReturnsFalse() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:updateNotificationsEnabled"] = "false" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert + Assert.False(features.Enabled()); + } + + [Fact] + public void Enabled_WithInvalidConfigurationValue_ReturnsDefaultValue() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:updateNotificationsEnabled"] = "invalid" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert - Should return default value when configuration is invalid + Assert.True(features.Enabled()); + } + + [Fact] + public void Enabled_WithEmptyConfigurationValue_ReturnsDefaultValue() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:updateNotificationsEnabled"] = "" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert - Should return default value when configuration is empty + Assert.True(features.Enabled()); + } + + [Fact] + public void Enabled_MultipleFeatureFlags_EachUsesItsOwnConfiguration() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:updateNotificationsEnabled"] = "false", + ["features:execCommandEnabled"] = "true", + ["features:showAllTemplates"] = "true" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert - Each feature should use its own configuration + Assert.False(features.Enabled()); + Assert.True(features.Enabled()); + Assert.True(features.Enabled()); + // Features not in configuration should use their defaults + Assert.True(features.Enabled()); + Assert.False(features.Enabled()); + } + + [Theory] + [InlineData("True")] + [InlineData("TRUE")] + [InlineData("true")] + public void Enabled_WithVariousTrueValues_ReturnsTrue(string value) + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:execCommandEnabled"] = value + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert + Assert.True(features.Enabled()); + } + + [Theory] + [InlineData("False")] + [InlineData("FALSE")] + [InlineData("false")] + public void Enabled_WithVariousFalseValues_ReturnsFalse(string value) + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:updateNotificationsEnabled"] = value + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert + Assert.False(features.Enabled()); + } + + [Fact] + public void IsFeatureEnabled_LegacyMethod_StillWorks() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["features:customFeature"] = "true" + }) + .Build(); + var features = new Features(configuration); + + // Act & Assert - Legacy method should still work for backward compatibility + Assert.True(features.IsFeatureEnabled("customFeature", defaultValue: false)); + Assert.False(features.IsFeatureEnabled("nonExistentFeature", defaultValue: false)); + Assert.True(features.IsFeatureEnabled("nonExistentFeature", defaultValue: true)); + } + + [Fact] + public void FeatureFlags_HaveCorrectConfigurationKeys() + { + // Assert - Verify that each feature flag has the expected configuration key + Assert.Equal("updateNotificationsEnabled", new UpdateNotificationsEnabledFeature().ConfigurationKey); + Assert.Equal("minimumSdkCheckEnabled", new MinimumSdkCheckEnabledFeature().ConfigurationKey); + Assert.Equal("execCommandEnabled", new ExecCommandEnabledFeature().ConfigurationKey); + Assert.Equal("orphanDetectionWithTimestampEnabled", new OrphanDetectionWithTimestampEnabledFeature().ConfigurationKey); + Assert.Equal("showDeprecatedPackages", new ShowDeprecatedPackagesFeature().ConfigurationKey); + Assert.Equal("packageSearchDiskCachingEnabled", new PackageSearchDiskCachingEnabledFeature().ConfigurationKey); + Assert.Equal("stagingChannelEnabled", new StagingChannelEnabledFeature().ConfigurationKey); + Assert.Equal("defaultWatchEnabled", new DefaultWatchEnabledFeature().ConfigurationKey); + Assert.Equal("showAllTemplates", new ShowAllTemplatesFeature().ConfigurationKey); + Assert.Equal("dotnetSdkInstallationEnabled", new DotNetSdkInstallationEnabledFeature().ConfigurationKey); + } + + [Fact] + public void FeatureFlags_HaveCorrectDefaultValues() + { + // Assert - Verify that each feature flag has the expected default value + Assert.True(new UpdateNotificationsEnabledFeature().DefaultValue); + Assert.True(new MinimumSdkCheckEnabledFeature().DefaultValue); + Assert.False(new ExecCommandEnabledFeature().DefaultValue); + Assert.True(new OrphanDetectionWithTimestampEnabledFeature().DefaultValue); + Assert.False(new ShowDeprecatedPackagesFeature().DefaultValue); + Assert.True(new PackageSearchDiskCachingEnabledFeature().DefaultValue); + Assert.False(new StagingChannelEnabledFeature().DefaultValue); + Assert.False(new DefaultWatchEnabledFeature().DefaultValue); + Assert.False(new ShowAllTemplatesFeature().DefaultValue); + Assert.True(new DotNetSdkInstallationEnabledFeature().DefaultValue); + } +} diff --git a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs index 63a8e2e9d4b..775586f6e84 100644 --- a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs @@ -382,15 +382,21 @@ public void EmbeddedScripts_AreAccessible() } } -public class MinimumSdkCheckFeature(bool enabled = true) : IFeatures +internal class MinimumSdkCheckFeature(bool enabled = true) : IFeatures { public bool IsFeatureEnabled(string featureName, bool defaultValue = false) { return featureName == KnownFeatures.MinimumSdkCheckEnabled ? enabled : false; } + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } } -public class TestFeatures : IFeatures +internal class TestFeatures : IFeatures { private readonly Dictionary _features = new(); @@ -404,4 +410,10 @@ public bool IsFeatureEnabled(string featureName, bool defaultValue = false) { return _features.TryGetValue(featureName, out var value) ? value : defaultValue; } + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } } diff --git a/tests/Aspire.Cli.Tests/Packaging/NuGetConfigMergerSnapshotTests.cs b/tests/Aspire.Cli.Tests/Packaging/NuGetConfigMergerSnapshotTests.cs index e576caf5e7c..3af82247901 100644 --- a/tests/Aspire.Cli.Tests/Packaging/NuGetConfigMergerSnapshotTests.cs +++ b/tests/Aspire.Cli.Tests/Packaging/NuGetConfigMergerSnapshotTests.cs @@ -32,6 +32,12 @@ private sealed class FakeNuGetPackageCache : INuGetPackageCache private sealed class FakeFeatures : IFeatures { public bool IsFeatureEnabled(string featureFlag, bool defaultValue) => defaultValue; + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } } private static PackagingService CreatePackagingService(CliExecutionContext executionContext) diff --git a/tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs b/tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs index dbaaa2f2d54..4cb8d0af146 100644 --- a/tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs +++ b/tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs @@ -30,6 +30,12 @@ public bool IsFeatureEnabled(string featureFlag, bool defaultValue) return _features.TryGetValue(featureFlag, out var value) ? value : defaultValue; } + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } + public void SetFeature(string featureFlag, bool enabled) { _features[featureFlag] = enabled; diff --git a/tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs b/tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs index b4320371414..0df8e12a414 100644 --- a/tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs +++ b/tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs @@ -737,7 +737,7 @@ public string GetSettingsFilePath(bool isGlobal) } } - public class TestFeatures : IFeatures + internal class TestFeatures : IFeatures { private readonly Dictionary _features = new(); @@ -751,6 +751,12 @@ public bool IsFeatureEnabled(string featureName, bool defaultValue = false) { return _features.TryGetValue(featureName, out var value) ? value : defaultValue; } + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } } private static ProjectLocator CreateProjectLocatorWithSingleFileEnabled(CliExecutionContext executionContext) diff --git a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs index f8a2cfe33f1..95160613051 100644 --- a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs +++ b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs @@ -361,6 +361,12 @@ public bool IsFeatureEnabled(string featureFlag, bool defaultValue) _ => defaultValue }; } + + public bool Enabled() where TFeatureFlag : IFeatureFlag, new() + { + var featureFlag = new TFeatureFlag(); + return IsFeatureEnabled(featureFlag.ConfigurationKey, featureFlag.DefaultValue); + } } private sealed class TestInteractionService : IInteractionService