| File: SelfUpdateChannelPersistenceTests.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 System.Text.Json.Nodes; using Aspire.Cli.EndToEnd.Tests.Helpers; using Hex1b.Automation; using Xunit; namespace Aspire.Cli.EndToEnd.Tests; public sealed class SelfUpdateChannelPersistenceTests(ITestOutputHelper output) { [Fact] [CaptureWorkspaceOnFailure] public async Task SelfUpdateToStaging_RelaunchedCliUsesStagingForNewProject() { var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var strategy = CliInstallStrategy.Detect(output.WriteLine); if (strategy.Mode is not (CliInstallMode.LocalHive or CliInstallMode.PullRequest or CliInstallMode.LocalArchive)) { Assert.Skip( "This test must start from a current source build so its first self-update exercises " + "the channel-persistence implementation under test. Run with ASPIRE_E2E_ARCHIVE or in pull request CI."); } var workspace = TemporaryWorkspace.Create(output); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, 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); await auto.PrepareDockerEnvironmentAsync(counter, workspace); await auto.InstallAspireCliAsync(strategy, counter); if (strategy.Mode is CliInstallMode.LocalHive || (strategy.Mode is CliInstallMode.LocalArchive && strategy.LocalArchiveHiveLabel == "local")) { // Local-hive setup pins its original Aspire home to local. Remove that competing // global setting before switching to the isolated script-style install. await auto.RunCommandAsync("aspire config delete channel -g", counter); } // Copy the current build into a dedicated get-aspire-cli.sh-style prefix. This gives the // self-update a realistic writable route without replacing the harness's original install. // // The sidecar's "packages" field pins Aspire package resolution to the harness's local // hive. Without it the relaunched CLI derives its template feed from the persisted identity // (darc-pub-microsoft-aspire-<commit>), which stops carrying matching packages once that // staging build is promoted to GA -- the exact failure reported in // https://github.com/microsoft/aspire/issues/19708. InstallSidecarWriter.PrepareForSelfUpdate // rewrites only channel/version/commit, so this field survives the self-update below. await auto.RunCommandAsync( "install_root=$HOME/.aspire-self-update-e2e; " + "package_path=$(find ~/.aspire/hives -type f -name 'Aspire.Hosting.*.nupkg' -print -quit); " + "test -n \"$package_path\"; " + "packages_dir=$(dirname \"$package_path\"); " + "mkdir -p \"$install_root/bin\"; " + "cp \"$(command -v aspire)\" \"$install_root/bin/aspire\"; " + "chmod +x \"$install_root/bin/aspire\"; " + "printf '{\"source\":\"script\",\"channel\":\"stable\",\"packages\":\"%s\"}\\n' \"$packages_dir\" > \"$install_root/bin/.aspire-install.json\"; " + "export PATH=\"$install_root/bin:$PATH\" ASPIRE_CLI_TELEMETRY_OPTOUT=true; hash -r; " + "test \"$(command -v aspire)\" = \"$install_root/bin/aspire\"", counter); await auto.RunCommandAsync( "aspire update --self --channel staging --non-interactive --yes", counter, timeout: TimeSpan.FromMinutes(10)); await auto.ClearScreenAsync(counter); await auto.RunCommandAsync( "cat \"$install_root/bin/.aspire-install.json\" > \"$ASPIRE_E2E_WORKSPACE/self-updated-install.json\"", counter); var sidecarPath = Path.Combine(workspace.WorkspaceRoot.FullName, "self-updated-install.json"); Assert.Equal("staging", ReadConfig(sidecarPath)["channel"]?.GetValue<string>()); await auto.RunCommandAsync("aspire config set features.updateNotificationsEnabled false -g", counter); // New projects use CLI identity when no channel is supplied. Existing C# updates instead // preserve native NuGet policy unless a channel is selected through options or configuration. const string projectName = "SelfUpdateChannelApp"; await auto.AspireNewCSharpEmptyAppHostAsync( projectName, counter, timeout: TimeSpan.FromMinutes(5)); var configPath = Path.Combine(workspace.WorkspaceRoot.FullName, projectName, "aspire.config.json"); Assert.Equal("staging", ReadConfig(configPath)["channel"]?.GetValue<string>()); } private static JsonObject ReadConfig(string path) { return JsonNode.Parse(File.ReadAllText(path))?.AsObject() ?? throw new InvalidOperationException($"Unable to read {path}."); } }