File: Commands\RootCommandTests.cs
Web Access
Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.Tests)
// 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.Commands;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.InternalTesting;
 
namespace Aspire.Cli.Tests.Commands;
 
public class RootCommandTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task RootCommandWithHelpArgumentReturnsZero()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper);
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<RootCommand>();
        var result = command.Parse("--help");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task RootCommandWithNoLogoArgumentReturnsZero()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper);
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<RootCommand>();
        var result = command.Parse("--nologo --help");
 
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Theory]
    [InlineData("1", true)]
    [InlineData("true", true)]
    [InlineData("TRUE", true)]
    [InlineData("True", true)]
    [InlineData("0", false)]
    [InlineData("false", false)]
    [InlineData("", false)]
    [InlineData(null, false)]
    public async Task NoLogoEnvironmentVariable_ParsedCorrectly(string? value, bool expectedNoLogo)
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ConfigurationCallback = config =>
            {
                config[CliConfigNames.NoLogo] = value;
            };
        });
        var provider = services.BuildServiceProvider();
 
        var configuration = provider.GetRequiredService<IConfiguration>();
        var isNoLogo = configuration.GetBool(CliConfigNames.NoLogo, defaultValue: false);
 
        Assert.Equal(expectedNoLogo, isNoLogo);
 
        // Also verify command still works with the configuration
        var command = provider.GetRequiredService<RootCommand>();
        var result = command.Parse("--help");
        var exitCode = await result.InvokeAsync().DefaultTimeout();
        Assert.Equal(0, exitCode);
    }
 
    [Fact]
    public async Task FirstTimeUseNotice_BannerDisplayedWhenSentinelDoesNotExist()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
 
        Assert.True(bannerService.WasBannerDisplayed);
        Assert.True(sentinel.WasCreated);
    }
 
    [Fact]
    public async Task FirstTimeUseNotice_BannerNotDisplayedWhenSentinelExists()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = true };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
 
        Assert.False(bannerService.WasBannerDisplayed);
        Assert.False(sentinel.WasCreated);
    }
 
    [Fact]
    public async Task FirstTimeUseNotice_BannerNotDisplayedWithNoLogoArgument()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.NoLogo]);
 
        Assert.False(bannerService.WasBannerDisplayed);
        Assert.True(sentinel.WasCreated);
    }
 
    [Fact]
    public async Task FirstTimeUseNotice_BannerNotDisplayedWithNoLogoEnvironmentVariable()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
            options.ConfigurationCallback = config =>
            {
                config[CliConfigNames.NoLogo] = "1";
            };
        });
        var provider = services.BuildServiceProvider();
 
        var configuration = provider.GetRequiredService<IConfiguration>();
        var noLogo = configuration.GetBool(CliConfigNames.NoLogo, defaultValue: false);
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
 
        Assert.False(bannerService.WasBannerDisplayed);
        Assert.True(sentinel.WasCreated);
    }
 
    [Fact]
    public async Task Banner_DisplayedWhenExplicitlyRequested()
    {
        // When --banner is passed, banner should show even if not first run
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = true }; // Not first run
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.Banner]);
 
        Assert.True(bannerService.WasBannerDisplayed);
        // Telemetry notice should NOT be shown since it's not first run
        var errorOutput = errorWriter.ToString();
        Assert.DoesNotContain("Telemetry", errorOutput);
    }
 
    [Fact]
    public async Task Banner_CanBeInvokedMultipleTimes()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        // Invoke multiple times (simulating multiple --banner calls)
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.Banner]);
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.Banner]);
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.Banner]);
 
        Assert.Equal(3, bannerService.DisplayCount);
    }
 
    [Fact]
    public void BannerOption_HasCorrectDescription()
    {
        Assert.Equal("--banner", RootCommand.BannerOption.Name);
        Assert.NotNull(RootCommand.BannerOption.Description);
        Assert.NotEmpty(RootCommand.BannerOption.Description);
    }
 
    [Fact]
    public async Task Banner_DisplayedOnFirstRunAndExplicitRequest()
    {
        // When it's a first run AND user explicitly requests --banner,
        // the banner should be shown (only once via the explicit request logic)
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [CommonOptionNames.Banner]);
 
        Assert.True(bannerService.WasBannerDisplayed);
        Assert.True(sentinel.WasCreated);
        // Telemetry notice should be shown on first run
        var errorOutput = errorWriter.ToString();
        Assert.Contains("Telemetry", errorOutput);
    }
 
    [Fact]
    public async Task Banner_TelemetryNoticeShownOnFirstRun()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
 
        var errorOutput = errorWriter.ToString();
        Assert.Contains("Telemetry", errorOutput);
    }
 
    [Fact]
    public async Task Banner_TelemetryNoticeNotShownOnSubsequentRuns()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var errorWriter = new StringWriter();
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = true }; // Not first run
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.ErrorTextWriter = errorWriter;
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
 
        var errorOutput = errorWriter.ToString();
        Assert.DoesNotContain("Telemetry", errorOutput);
    }
 
    [Theory]
    [InlineData("--version")]
    [InlineData("--help")]
    [InlineData("-h")]
    [InlineData("-?")]
    public async Task InformationalFlag_SuppressesBannerAndDoesNotCreateSentinel(string flag)
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, [flag]);
 
        // Informational flags set noLogo, which suppresses banner and telemetry notice
        Assert.False(bannerService.WasBannerDisplayed);
        // Sentinel should NOT be created for informational commands
        Assert.False(sentinel.WasCreated);
    }
 
    [Fact]
    public async Task InformationalFlag_DoesNotCreateSentinel_OnSubsequentFirstRun()
    {
        // Verifies that running --version on first run doesn't mark first-run as complete,
        // so a subsequent normal invocation still shows the first-run experience.
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var sentinel = new TestFirstTimeUseNoticeSentinel { SentinelExists = false };
        var bannerService = new TestBannerService();
 
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.FirstTimeUseNoticeSentinelFactory = _ => sentinel;
            options.BannerServiceFactory = _ => bannerService;
        });
        var provider = services.BuildServiceProvider();
 
        // First invocation with --version: should not create sentinel
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, ["--version"]);
        Assert.False(sentinel.WasCreated);
 
        // Second invocation without informational flag: should create sentinel and show banner
        await Program.DisplayFirstTimeUseNoticeIfNeededAsync(provider, []);
        Assert.True(bannerService.WasBannerDisplayed);
        Assert.True(sentinel.WasCreated);
    }
 
    [Fact]
    public void SetupCommand_NotAvailable_WhenBundleIsNotAvailable()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper);
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<RootCommand>();
        var hasSetupCommand = command.Subcommands.Any(cmd => cmd.Name == "setup");
 
        Assert.False(hasSetupCommand);
    }
 
    [Fact]
    public void SetupCommand_Available_WhenBundleIsAvailable()
    {
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            options.BundleServiceFactory = _ => new TestBundleService(isBundle: true);
        });
        var provider = services.BuildServiceProvider();
 
        var command = provider.GetRequiredService<RootCommand>();
        var hasSetupCommand = command.Subcommands.Any(cmd => cmd.Name == "setup");
 
        Assert.True(hasSetupCommand);
    }
 
}