| File: DotnetToolSmokeTests.cs | Web Access |
| Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.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.EndToEnd.Tests.Helpers; using Hex1b.Automation; using Hex1b.Input; using Xunit; namespace Aspire.Cli.EndToEnd.Tests; /// <summary> /// End-to-end smoke tests for the Aspire CLI installed via <c>dotnet tool install --global Aspire.Cli</c>. /// Unlike the general smoke tests that use <see cref="CliInstallStrategy.Detect"/>, these tests /// explicitly construct a DotnetTool strategy to validate the dotnet tool distribution channel. /// <para> /// The strategy is resolved from (in priority order): /// <list type="number"> /// <item><c>ASPIRE_E2E_DOTNET_TOOL_SOURCE</c> — install from local nupkg directory</item> /// <item><c>BUILT_NUGETS_PATH</c> — auto-discover from CI-built nupkgs directory</item> /// <item>Published NuGet feed when <c>ASPIRE_E2E_VERSION</c> or <c>ASPIRE_E2E_QUALITY</c> is set</item> /// </list> /// </para> /// </summary> public sealed class DotnetToolSmokeTests(ITestOutputHelper output) { [CaptureWorkspaceOnFailure] [Fact] public async Task CreateAndRunAspireStarterProject() { var strategy = GetDotnetToolStrategy(); output.WriteLine($"DotnetTool strategy resolved: {strategy}"); var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var workspace = TemporaryWorkspace.Create(output); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, mountDockerSocket: true, workspace: workspace); var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await using var terminalRun = CliE2ETestHelpers.StartRun(terminal, workspace, auto, counter, output, TestContext.Current.CancellationToken); // Prepare Docker environment (prompt counting, umask, env vars) await auto.PrepareDockerEnvironmentAsync(counter, workspace); // Install the Aspire CLI via dotnet tool await auto.InstallAspireCliAsync(strategy, counter); // Verify the tool is installed via dotnet tool list await auto.TypeAsync("dotnet tool list -g"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("aspire.cli", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptAsync(counter); // Verify aspire is accessible from the dotnet tools path await auto.TypeAsync("command -v aspire"); await auto.EnterAsync(); await auto.WaitUntilTextAsync(".dotnet/tools/aspire", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptAsync(counter); // Verify self-update uses the dotnet tool update path for dotnet tool installs. await auto.TypeAsync("aspire update --self"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("dotnet tool update -g Aspire.Cli", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptAsync(counter); // Verify the installed version matches expectations await auto.TypeAsync("aspire --version"); await auto.EnterAsync(); if (strategy.Version is not null) { // When a specific version was requested, verify it was actually installed output.WriteLine($"Verifying installed version matches expected: {strategy.Version}"); await auto.WaitUntilTextAsync(strategy.Version, timeout: TimeSpan.FromSeconds(10)); } await auto.WaitForSuccessPromptAsync(counter); // Create a new project using aspire new await auto.AspireNewAsync("AspireToolApp", counter); // Run the project with aspire run. Use an explicit AppHost startup budget so a cold daily-feed // restore + build doesn't trip the CLI's default 120s timeout under CI contention. (Older published // CLIs simply ignore the env var, so this stays safe across the versions these tests target.) await auto.TypeAsync(CliE2EAutomatorHelpers.GetAspireRunCommand()); await auto.EnterAsync(); await auto.WaitUntilAsync(s => { if (s.ContainsText("Select an AppHost to use:")) { throw new InvalidOperationException( "Unexpected apphost selection prompt detected! " + "This indicates multiple apphosts were incorrectly detected."); } // Dotnet tool smoke tests can run against older published packages that used different AppHost casing. return s.GetScreenText().Contains("Press CTRL+C to stop the AppHost and exit.", StringComparison.OrdinalIgnoreCase); }, timeout: CliE2EAutomatorHelpers.AspireRunReadyTimeout, description: "Press CTRL+C message (aspire run started)"); // Stop the running apphost with Ctrl+C await auto.Ctrl().KeyAsync(Hex1bKey.C); await auto.WaitForSuccessPromptAsync(counter); } /// <summary> /// Explicitly constructs a DotnetTool strategy from env vars, bypassing <see cref="CliInstallStrategy.Detect"/>. /// Checks (in order): /// <list type="number"> /// <item><c>ASPIRE_E2E_DOTNET_TOOL_SOURCE</c> — explicit local nupkg directory</item> /// <item><c>BUILT_NUGETS_PATH</c> — auto-discover from CI-built nupkgs directory</item> /// <item>Published NuGet feed when <c>ASPIRE_E2E_VERSION</c> or <c>ASPIRE_E2E_QUALITY</c> is set</item> /// </list> /// </summary> internal static CliInstallStrategy GetDotnetToolStrategy() { // 1. Explicit local nupkg source (user-provided) var source = Environment.GetEnvironmentVariable("ASPIRE_E2E_DOTNET_TOOL_SOURCE"); if (!string.IsNullOrEmpty(source)) { var version = Environment.GetEnvironmentVariable("ASPIRE_E2E_VERSION"); return string.IsNullOrEmpty(version) ? CliInstallStrategy.FromDotnetToolLocalSource(source) : CliInstallStrategy.FromDotnetToolLocalSource(source, version); } // 2. Auto-discover from CI-built nupkgs (BUILT_NUGETS_PATH contains the tool nupkg // directly since the dotnet tool is built as part of the normal package build) var builtNugetsPath = Environment.GetEnvironmentVariable("BUILT_NUGETS_PATH"); if (!string.IsNullOrEmpty(builtNugetsPath) && Directory.Exists(builtNugetsPath)) { return CliInstallStrategy.FromDotnetToolLocalSource(builtNugetsPath); } // 3. Published NuGet feed. Require an explicit selector so local dotnet test runs // don't silently install the latest package from the public feed. var requestedVersion = Environment.GetEnvironmentVariable("ASPIRE_E2E_VERSION"); if (string.IsNullOrWhiteSpace(requestedVersion)) { requestedVersion = null; } var quality = CliInstallStrategy.GetQualityFromEnvironment(); if (requestedVersion is null && quality is null) { throw new InvalidOperationException( "Dotnet tool smoke tests require ASPIRE_E2E_QUALITY to select a daily published feed, " + "ASPIRE_E2E_VERSION to install a specific published version, " + "ASPIRE_E2E_DOTNET_TOOL_SOURCE to install from a local feed, " + "or BUILT_NUGETS_PATH containing exactly one Aspire.Cli pointer nupkg."); } return CliInstallStrategy.FromPublishedDotnetToolFeed(requestedVersion, quality); } }