File: Helpers\CliInstallStrategyTests.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 Hex1b;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests.Helpers;
 
[Collection(CliInstallEnvironmentCollection.Name)]
public class CliInstallStrategyTests
{
    [Fact]
    public void GetPullRequestInstallArgs_ReturnsPrNumber()
    {
        Assert.Equal("123", AspireCliShellCommandHelpers.GetPullRequestInstallArgs(123));
    }
 
    [Fact]
    public void GetLocalArchiveInstallCommand_FormatsCorrectly()
    {
        var command = AspireCliShellCommandHelpers.GetLocalArchiveInstallCommand("/tmp/cli-archives", "/opt/aspire-scripts/get-aspire-cli-pr.sh");
        Assert.Equal("/opt/aspire-scripts/get-aspire-cli-pr.sh --local-dir '/tmp/cli-archives'", command);
    }
 
    [Fact]
    public void GetRecordAspireCliVersionCommand_IsBestEffort()
    {
        var strategy = CliInstallStrategy.FromDotnetTool(includePrerelease: true);
 
        var command = CliE2EAutomatorHelpers.GetRecordAspireCliVersionCommand(strategy, "VER", "BASE_VER");
 
        Assert.Contains("if mkdir -p \"$ASPIRE_E2E_CLI_VERSION_OUTPUT_DIR\" && ", command);
        Assert.Contains("} > \"$CLI_VERSION_RECORD\"; then echo \"CLI_VERSION_RECORDED:$CLI_VERSION_RECORD\"; else echo \"CLI_VERSION_RECORD_FAILED:$ASPIRE_E2E_CLI_VERSION_OUTPUT_DIR\"; fi; fi", command);
    }
 
    [Fact]
    public void Detect_ReturnsLocalArchive_WhenArchiveDirIsSet()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", null),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, tempDir.FullName),
                ("CI", null),
                ("GITHUB_ACTIONS", null));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.LocalArchive, strategy.Mode);
            Assert.Equal(tempDir.FullName, strategy.ArchiveDir);
        }
        finally
        {
            tempDir.Delete(true);
        }
    }
 
    [Fact]
    public void Detect_ReturnsLocalArchive_WhenBothPrMetadataAndArchiveDirAreSet()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", null),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", "16131"),
                ("GITHUB_PR_HEAD_SHA", "52669a7cac3d4f10c6269909fc38e77124ed177c"),
                (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, tempDir.FullName),
                ("CI", null),
                ("GITHUB_ACTIONS", null));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.LocalArchive, strategy.Mode);
            Assert.Equal(tempDir.FullName, strategy.ArchiveDir);
        }
        finally
        {
            tempDir.Delete(true);
        }
    }
 
    [Fact]
    public void Detect_FallsBackToDevQuality_WhenNoArchiveContextInCI()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, null),
            ("CI", null),
            ("GITHUB_ACTIONS", "true"));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.InstallScript, strategy.Mode);
    }
 
    [Fact]
    public void ConfigureContainer_MountsArchiveDirForLocalArchive()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", null),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, tempDir.FullName));
 
            var strategy = CliInstallStrategy.Detect();
            var options = new DockerContainerOptions();
 
            strategy.ConfigureContainer(options);
 
            Assert.Contains($"{tempDir.FullName}:/tmp/aspire-cli-archives:ro", options.Volumes);
        }
        finally
        {
            tempDir.Delete(true);
        }
    }
 
    [Fact]
    public void ConfigureContainer_AddsPrMetadataForPullRequest()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", null),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", null),
            ("GITHUB_PR_NUMBER", "16131"),
            ("GITHUB_PR_HEAD_SHA", "52669a7cac3d4f10c6269909fc38e77124ed177c"),
            (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, null));
 
        var strategy = CliInstallStrategy.Detect();
        var options = new DockerContainerOptions();
 
        strategy.ConfigureContainer(options);
 
        Assert.Equal("16131", options.Environment["GITHUB_PR_NUMBER"]);
        Assert.Equal("52669a7cac3d4f10c6269909fc38e77124ed177c", options.Environment["GITHUB_PR_HEAD_SHA"]);
    }
 
    [Fact]
    public void ConfigureContainer_AddsUbuntuAptMirrorBuildArgWhenEnvironmentVariableIsSet()
    {
        using var environment = new EnvironmentVariableScope(
            (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, "http://azure.archive.ubuntu.com/ubuntu/"));
 
        var strategy = CliInstallStrategy.LatestGa();
        var options = new DockerContainerOptions();
 
        strategy.ConfigureContainer(options);
 
        Assert.Equal("http://azure.archive.ubuntu.com/ubuntu/", options.BuildArgs[CliInstallStrategy.UbuntuAptMirrorBuildArgName]);
    }
 
    [Fact]
    public void ConfigureContainer_DoesNotAddUbuntuAptMirrorBuildArgWhenEnvironmentVariableIsEmpty()
    {
        using var environment = new EnvironmentVariableScope(
            (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, null));
 
        var strategy = CliInstallStrategy.LatestGa();
        var options = new DockerContainerOptions();
 
        strategy.ConfigureContainer(options);
 
        Assert.DoesNotContain(CliInstallStrategy.UbuntuAptMirrorBuildArgName, options.BuildArgs.Keys);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_UsesDotNetImageWhenEnvironmentVariableIsSet()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, "aspire-cli-e2e-dotnet:prebuilt"),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet);
 
        Assert.Equal("aspire-cli-e2e-dotnet:prebuilt", options.Image);
        Assert.True(string.IsNullOrEmpty(options.DockerfilePath));
        Assert.True(string.IsNullOrEmpty(options.BuildContext));
    }
 
    [Fact]
    public void ConfigureContainer_BuildArgsCanBeClearedForPrebuiltImage()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, "aspire-cli-e2e-dotnet:prebuilt"),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"),
            (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, "http://azure.archive.ubuntu.com/ubuntu/"));
        var strategy = CliInstallStrategy.LatestGa();
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet);
        CliE2ETestHelpers.ConfigureDockerContainerStrategy(options, strategy, prebuiltImageSelected: true);
 
        Assert.Equal("aspire-cli-e2e-dotnet:prebuilt", options.Image);
        Assert.Empty(options.BuildArgs);
    }
 
    [Fact]
    public void ConfigureContainer_ThrowsWhenPrebuiltImageAlsoHasDockerfileConfiguration()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, "aspire-cli-e2e-dotnet:prebuilt"),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"));
        var strategy = CliInstallStrategy.LatestGa();
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet);
        options.DockerfilePath = "/unexpected/Dockerfile";
        options.BuildContext = "/unexpected";
 
        var exception = Assert.Throws<InvalidOperationException>(() =>
            CliE2ETestHelpers.ConfigureDockerContainerStrategy(options, strategy, prebuiltImageSelected: true));
 
        Assert.Contains("prebuilt CLI E2E image", exception.Message);
    }
 
    [Fact]
    public void ConfigureContainer_PreservesBuildArgsForDockerfileVariant()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, "aspire-cli-e2e-dotnet:prebuilt"),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"),
            (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, "http://azure.archive.ubuntu.com/ubuntu/"));
        var strategy = CliInstallStrategy.LatestGa();
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.Polyglot);
        CliE2ETestHelpers.ConfigureDockerContainerStrategy(options, strategy);
 
        Assert.Equal(Path.Combine("/repo", "tests", "Shared", "Docker", "Dockerfile.e2e-polyglot-base"), options.DockerfilePath);
        Assert.Equal("true", options.BuildArgs["SKIP_SOURCE_BUILD"]);
        Assert.Equal("http://azure.archive.ubuntu.com/ubuntu/", options.BuildArgs[CliInstallStrategy.UbuntuAptMirrorBuildArgName]);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_UsesPolyglotImageWhenEnvironmentVariableIsSet()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.PolyglotImageEnvironmentVariableName, "aspire-cli-e2e-polyglot:prebuilt"),
            (CliE2ETestHelpers.RequirePolyglotImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.Polyglot);
 
        Assert.Equal("aspire-cli-e2e-polyglot:prebuilt", options.Image);
        Assert.True(string.IsNullOrEmpty(options.DockerfilePath));
        Assert.True(string.IsNullOrEmpty(options.BuildContext));
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_RequiresPolyglotImageWhenConfigured()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.PolyglotImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequirePolyglotImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        var exception = Assert.Throws<InvalidOperationException>(() =>
            CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.Polyglot));
 
        Assert.Contains(CliE2ETestHelpers.PolyglotImageEnvironmentVariableName, exception.Message);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_UsesPolyglotJavaImageWhenEnvironmentVariableIsSet()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.PolyglotJavaImageEnvironmentVariableName, "aspire-cli-e2e-polyglot-java:prebuilt"),
            (CliE2ETestHelpers.RequirePolyglotJavaImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.PolyglotJava);
 
        Assert.Equal("aspire-cli-e2e-polyglot-java:prebuilt", options.Image);
        Assert.True(string.IsNullOrEmpty(options.DockerfilePath));
        Assert.True(string.IsNullOrEmpty(options.BuildContext));
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_RequiresPolyglotJavaImageWhenConfigured()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.PolyglotJavaImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequirePolyglotJavaImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        var exception = Assert.Throws<InvalidOperationException>(() =>
            CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.PolyglotJava));
 
        Assert.Contains(CliE2ETestHelpers.PolyglotJavaImageEnvironmentVariableName, exception.Message);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_IgnoresPolyglotJavaImageForPolyglotVariant()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.PolyglotJavaImageEnvironmentVariableName, "aspire-cli-e2e-polyglot-java:prebuilt"),
            (CliE2ETestHelpers.RequirePolyglotJavaImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.Polyglot);
 
        Assert.Equal(Path.Combine("/repo", "tests", "Shared", "Docker", "Dockerfile.e2e-polyglot-base"), options.DockerfilePath);
        Assert.Equal("/repo", options.BuildContext);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_FallsBackToDockerfileOutsideCI()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, null),
            ("GITHUB_ACTIONS", null));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet);
 
        Assert.Equal(Path.Combine("/repo", "tests", "Shared", "Docker", "Dockerfile.e2e"), options.DockerfilePath);
        Assert.Equal("/repo", options.BuildContext);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_RequiresDotNetImageWhenConfigured()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        var exception = Assert.Throws<InvalidOperationException>(() =>
            CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet));
 
        Assert.Contains(CliE2ETestHelpers.DotNetImageEnvironmentVariableName, exception.Message);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_FallsBackToDockerfileInCIWhenDotNetImageIsNotRequired()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, null),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.DotNet);
 
        Assert.Equal(Path.Combine("/repo", "tests", "Shared", "Docker", "Dockerfile.e2e"), options.DockerfilePath);
        Assert.Equal("/repo", options.BuildContext);
    }
 
    [Fact]
    public void ConfigureDockerContainerSource_IgnoresDotNetImageForPolyglotVariant()
    {
        using var environment = WithCleanCliE2ETestEnvironment(
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, "aspire-cli-e2e-dotnet:prebuilt"),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, "true"),
            ("GITHUB_ACTIONS", "true"));
        var options = new DockerContainerOptions();
 
        CliE2ETestHelpers.ConfigureDockerContainerSource(options, "/repo", CliE2ETestHelpers.DockerfileVariant.Polyglot);
 
        Assert.Equal(Path.Combine("/repo", "tests", "Shared", "Docker", "Dockerfile.e2e-polyglot-base"), options.DockerfilePath);
        Assert.Equal("/repo", options.BuildContext);
    }
 
    [Fact]
    public void Detect_DotnetTool_WhenEnvironmentVariableIsSet()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.Null(strategy.Version);
        Assert.Null(strategy.NupkgSourcePath);
        Assert.False(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void Detect_DotnetTool_WithVersion()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", "9.5.0"),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.Equal("9.5.0", strategy.Version);
        Assert.Null(strategy.NupkgSourcePath);
        Assert.False(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void Detect_DotnetToolLocalSource_WithVersionAndPath()
    {
        var tempDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
 
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", tempDir.FullName),
                ("ASPIRE_E2E_DOTNET_TOOL", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", "13.3.0-preview.1.25175.1"),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                ("CI", null),
                ("GITHUB_ACTIONS", null));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
            Assert.Equal("13.3.0-preview.1.25175.1", strategy.Version);
            Assert.Equal(tempDir.FullName, strategy.NupkgSourcePath);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void Detect_DotnetToolLocalSource_InfersVersion()
    {
        var tempDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
 
        try
        {
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.13.3.0-preview.1.25175.1.nupkg"), "");
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.linux-x64.13.3.0-preview.1.25175.1.nupkg"), "");
 
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", tempDir.FullName),
                ("ASPIRE_E2E_DOTNET_TOOL", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", null),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                ("CI", null),
                ("GITHUB_ACTIONS", null));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
            Assert.Equal("13.3.0-preview.1.25175.1", strategy.Version);
            Assert.Equal(tempDir.FullName, strategy.NupkgSourcePath);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void Detect_DotnetTool_TakesPriorityOverQuality()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", "dev"),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.True(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void Detect_DotnetTool_IncludesPrereleaseForStagingQuality()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", "staging"),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.True(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void Detect_DotnetTool_DoesNotIncludePrereleaseForReleaseQuality()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", "release"),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.False(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void Detect_DotnetTool_DoesNotIncludePrereleaseWhenVersionIsSpecified()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", "dev"),
            ("ASPIRE_E2E_VERSION", "13.3.0-preview.1.25175.1"),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.Equal("13.3.0-preview.1.25175.1", strategy.Version);
        Assert.False(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void DotnetToolSmokeTests_ThrowsWhenPublishedFeedFallbackHasNoSelector()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("BUILT_NUGETS_PATH", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_QUALITY", null));
 
        var exception = Assert.Throws<InvalidOperationException>(DotnetToolSmokeTests.GetDotnetToolStrategy);
 
        Assert.Contains("ASPIRE_E2E_QUALITY", exception.Message);
    }
 
    [Fact]
    public void DotnetToolSmokeTests_UsesPublishedFeedWhenQualityIsSet()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("BUILT_NUGETS_PATH", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_QUALITY", "staging"));
 
        var strategy = DotnetToolSmokeTests.GetDotnetToolStrategy();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.True(strategy.IncludePrerelease);
        Assert.Null(strategy.Version);
    }
 
    [Fact]
    public void DotnetToolSmokeTests_UsesPublishedFeedWhenVersionIsSet()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("BUILT_NUGETS_PATH", null),
            ("ASPIRE_E2E_VERSION", "13.3.0"),
            ("ASPIRE_E2E_QUALITY", null));
 
        var strategy = DotnetToolSmokeTests.GetDotnetToolStrategy();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
        Assert.Equal("13.3.0", strategy.Version);
        Assert.False(strategy.IncludePrerelease);
    }
 
    [Fact]
    public void ConfigureContainer_MountsNupkgSourceForDotnetToolLocalSource()
    {
        var tempDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
 
        try
        {
            var strategy = CliInstallStrategy.FromDotnetToolLocalSource(tempDir.FullName, "13.3.0");
            var options = new DockerContainerOptions();
 
            strategy.ConfigureContainer(options);
 
            Assert.Contains(options.Volumes, v => v.Contains("/tmp/aspire-nupkg-source:ro"));
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void ConfigureContainer_NoVolumeForDotnetToolPublishedFeed()
    {
        var strategy = CliInstallStrategy.FromDotnetTool("9.5.0");
        var options = new DockerContainerOptions();
 
        strategy.ConfigureContainer(options);
 
        Assert.DoesNotContain(options.Volumes, v => v.Contains("aspire-nupkg-source"));
    }
 
    [Fact]
    public void GetDotnetToolInstallCommandInDocker_WithVersionOnly()
    {
        var strategy = CliInstallStrategy.FromDotnetTool("9.5.0");
        var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);
 
        Assert.Equal("dotnet tool install --global Aspire.Cli --version 9.5.0", command);
    }
 
    [Fact]
    public void GetDotnetToolInstallCommandInDocker_WithPrereleaseVersion()
    {
        var strategy = CliInstallStrategy.FromDotnetTool("13.3.0-preview.1.25175.1");
        var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);
 
        Assert.Equal("dotnet tool install --global Aspire.Cli --version 13.3.0-preview.1.25175.1 --configfile '/opt/aspire-scripts/NuGet.config'", command);
    }
 
    [Fact]
    public void GetDotnetToolInstallCommandInDocker_WithLocalSource()
    {
        var tempDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
 
        try
        {
            var strategy = CliInstallStrategy.FromDotnetToolLocalSource(tempDir.FullName, "13.3.0");
            var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);
 
            Assert.Equal("dotnet tool install --global Aspire.Cli --version 13.3.0 --add-source '/tmp/aspire-nupkg-source'", command);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void GetDotnetToolInstallCommandInDocker_WithoutVersion()
    {
        var strategy = CliInstallStrategy.FromDotnetTool();
        var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);
 
        Assert.Equal("dotnet tool install --global Aspire.Cli", command);
    }
 
    [Fact]
    public void GetDotnetToolInstallCommandInDocker_WithPrerelease()
    {
        var strategy = CliInstallStrategy.FromDotnetTool(includePrerelease: true);
        var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);
 
        Assert.Equal("dotnet tool install --global Aspire.Cli --prerelease --configfile '/opt/aspire-scripts/NuGet.config'", command);
    }
 
    [Fact]
    public void GetDotnetAddPackageCommand_UsesLocalHivePackageVersionWhenPresent()
    {
        var command = AspireCliShellCommandHelpers.GetDotnetAddPackageCommand("K8sDeployTest.ApiService", "Aspire.StackExchange.Redis");
 
        Assert.Contains("PKG_PATH=$(find \"$HOME/.aspire/hives\" -path \"*/packages/$PKG.[0-9]*.nupkg\"", command);
        Assert.Contains("PKG_VERSION=${PKG_FILE#\"$PKG.\"}", command);
        Assert.Contains("dotnet add \"$TARGET\" package \"$PKG\" --version \"$PKG_VERSION\"", command);
        Assert.Contains("else dotnet add \"$TARGET\" package \"$PKG\" --prerelease", command);
    }
 
    [Fact]
    public void Detect_ReturnsLocalArchive_WhenArchiveDirIsSetInCIWithoutPrMetadata()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
                ("ASPIRE_E2E_DOTNET_TOOL", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", null),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, tempDir.FullName),
                ("CI", "true"),
                ("GITHUB_ACTIONS", "true"));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.LocalArchive, strategy.Mode);
            Assert.Equal(tempDir.FullName, strategy.ArchiveDir);
        }
        finally
        {
            tempDir.Delete(true);
        }
    }
 
    [Fact]
    public void FromLocalArchive_ThrowsWhenDirectoryDoesNotExist()
    {
        Assert.Throws<DirectoryNotFoundException>(() =>
            CliInstallStrategy.FromLocalArchive("/nonexistent/cli-archives-path"));
    }
 
    [Fact]
    public void FromLocalArchive_ExtractsExpectedVersionFromNupkg()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.13.3.0-preview.1.12345.1.nupkg"), "");
 
            var strategy = CliInstallStrategy.FromLocalArchive(tempDir.FullName);
 
            Assert.Equal("13.3.0-preview.1.12345.1", strategy.ExpectedVersion);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void FromLocalArchive_IgnoresRidSpecificCliNupkg()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.linux-x64.13.3.0.nupkg"), "");
 
            var strategy = CliInstallStrategy.FromLocalArchive(tempDir.FullName);
 
            Assert.Null(strategy.ExpectedVersion);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void FromLocalArchive_ThrowsWhenMultiplePointerCliNupkgsArePresent()
    {
        var tempDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.13.3.0.nupkg"), "");
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.13.4.0.nupkg"), "");
 
            var exception = Assert.Throws<InvalidOperationException>(() => CliInstallStrategy.FromLocalArchive(tempDir.FullName));
 
            Assert.Contains("Found 2 Aspire.Cli pointer nupkg files", exception.Message);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void FromDotnetToolLocalSource_ThrowsWhenOnlyRidSpecificPackagesArePresent()
    {
        var tempDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
 
        try
        {
            File.WriteAllText(Path.Combine(tempDir.FullName, "Aspire.Cli.linux-x64.13.3.0.nupkg"), "");
 
            var exception = Assert.Throws<InvalidOperationException>(() => CliInstallStrategy.FromDotnetToolLocalSource(tempDir.FullName));
 
            Assert.Contains("No Aspire.Cli tool nupkg found", exception.Message);
        }
        finally
        {
            tempDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void Detect_ReturnsDotnetToolLocalSource_WhenBothToolSourceAndArchiveDirAreSet()
    {
        var nupkgDir = Directory.CreateTempSubdirectory("aspire-test-nupkg-");
        var archiveDir = Directory.CreateTempSubdirectory("cli-archives-test");
        try
        {
            using var environment = new EnvironmentVariableScope(
                ("ASPIRE_E2E_ARCHIVE", null),
                ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", nupkgDir.FullName),
                ("ASPIRE_E2E_DOTNET_TOOL", null),
                ("ASPIRE_E2E_QUALITY", null),
                ("ASPIRE_E2E_VERSION", "10.0.0-dev.12345.1"),
                ("ASPIRE_E2E_PREINSTALLED", null),
                ("GITHUB_PR_NUMBER", null),
                ("GITHUB_PR_HEAD_SHA", null),
                (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, archiveDir.FullName),
                ("CI", null),
                ("GITHUB_ACTIONS", null));
 
            var strategy = CliInstallStrategy.Detect();
 
            Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
            Assert.Equal(nupkgDir.FullName, strategy.NupkgSourcePath);
            Assert.Equal("10.0.0-dev.12345.1", strategy.Version);
        }
        finally
        {
            nupkgDir.Delete(recursive: true);
            archiveDir.Delete(recursive: true);
        }
    }
 
    [Fact]
    public void Detect_ReturnsDotnetTool_WhenBothToolFlagAndPrMetadataAreSet()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", "true"),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", null),
            ("GITHUB_PR_NUMBER", "16131"),
            ("GITHUB_PR_HEAD_SHA", "abc123"),
            (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
    }
 
    [Fact]
    public void FromDotnetToolLocalSource_ThrowsWhenDirectoryDoesNotExist()
    {
        Assert.Throws<DirectoryNotFoundException>(() =>
            CliInstallStrategy.FromDotnetToolLocalSource("/nonexistent/nupkg-path", "1.0.0"));
    }
 
    [Fact]
    public void FromPullRequest_ThrowsWhenPrMetadataIsMissing()
    {
        using var environment = new EnvironmentVariableScope(
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null));
 
        Assert.Throws<InvalidOperationException>(CliInstallStrategy.FromPullRequest);
    }
 
    [Fact]
    public void TryGetPullRequestHeadSha_ReturnsFalseOutsidePrContext()
    {
        using var environment = new EnvironmentVariableScope(
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            ("GITHUB_EVENT_NAME", null),
            ("GITHUB_SHA", "52669a7cac3d4f10c6269909fc38e77124ed177c"));
 
        var result = CliE2ETestHelpers.TryGetPullRequestHeadSha(out var commitSha);
 
        Assert.False(result);
        Assert.Equal(string.Empty, commitSha);
    }
 
    [Fact]
    public void TryGetPullRequestHeadSha_ThrowsInPrContextWithoutHeadSha()
    {
        using var environment = new EnvironmentVariableScope(
            ("GITHUB_PR_NUMBER", "16131"),
            ("GITHUB_PR_HEAD_SHA", null),
            ("GITHUB_EVENT_NAME", null));
 
        var exception = Assert.Throws<InvalidOperationException>(() => CliE2ETestHelpers.TryGetPullRequestHeadSha(out _));
 
        Assert.Contains("GITHUB_PR_HEAD_SHA must be set", exception.Message);
    }
 
    [Fact]
    public void TryGetPullRequestHeadSha_ThrowsInPrContextWithInvalidHeadSha()
    {
        using var environment = new EnvironmentVariableScope(
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", "abc123"),
            ("GITHUB_EVENT_NAME", "pull_request"));
 
        var exception = Assert.Throws<InvalidOperationException>(() => CliE2ETestHelpers.TryGetPullRequestHeadSha(out _));
 
        Assert.Contains("40-character commit SHA", exception.Message);
    }
 
    [Fact]
    public void TryGetPullRequestHeadSha_ReturnsHeadShaInPrContext()
    {
        const string expectedSha = "52669a7cac3d4f10c6269909fc38e77124ed177c";
        using var environment = new EnvironmentVariableScope(
            ("GITHUB_PR_NUMBER", "16131"),
            ("GITHUB_PR_HEAD_SHA", expectedSha),
            ("GITHUB_EVENT_NAME", null));
 
        var result = CliE2ETestHelpers.TryGetPullRequestHeadSha(out var commitSha);
 
        Assert.True(result);
        Assert.Equal(expectedSha, commitSha);
    }
 
    [Fact]
    public void Detect_ReturnsPreinstalled_WhenPreinstalledIsSet()
    {
        using var environment = new EnvironmentVariableScope(
            ("ASPIRE_E2E_ARCHIVE", null),
            ("ASPIRE_E2E_DOTNET_TOOL_SOURCE", null),
            ("ASPIRE_E2E_DOTNET_TOOL", null),
            ("ASPIRE_E2E_QUALITY", null),
            ("ASPIRE_E2E_VERSION", null),
            ("ASPIRE_E2E_PREINSTALLED", "true"),
            ("GITHUB_PR_NUMBER", null),
            ("GITHUB_PR_HEAD_SHA", null),
            (CliE2ETestHelpers.CliArchiveDirEnvironmentVariableName, null),
            ("CI", null),
            ("GITHUB_ACTIONS", null));
 
        var strategy = CliInstallStrategy.Detect();
 
        Assert.Equal(CliInstallMode.Preinstalled, strategy.Mode);
    }
 
    private static EnvironmentVariableScope WithCleanCliE2ETestEnvironment(params (string Name, string? Value)[] variables)
    {
        (string Name, string? Value)[] defaults =
        [
            (CliE2ETestHelpers.DotNetImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequireDotNetImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.PolyglotImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequirePolyglotImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.PolyglotJavaImageEnvironmentVariableName, null),
            (CliE2ETestHelpers.RequirePolyglotJavaImageEnvironmentVariableName, null),
            ("GITHUB_ACTIONS", null),
        ];
 
        var cleanVariables = defaults.ToDictionary(variable => variable.Name, variable => variable.Value);
        foreach (var (name, value) in variables)
        {
            cleanVariables[name] = value;
        }
 
        return new EnvironmentVariableScope([.. cleanVariables.Select(variable => (variable.Key, variable.Value))]);
    }
 
    private sealed class EnvironmentVariableScope : IDisposable
    {
        private readonly Dictionary<string, string?> _originalValues;
 
        public EnvironmentVariableScope(params (string Name, string? Value)[] variables)
        {
            _originalValues = variables.ToDictionary(
                variable => variable.Name,
                variable => Environment.GetEnvironmentVariable(variable.Name));
 
            foreach (var (name, value) in variables)
            {
                Environment.SetEnvironmentVariable(name, value);
            }
        }
 
        public void Dispose()
        {
            foreach (var (name, value) in _originalValues)
            {
                Environment.SetEnvironmentVariable(name, value);
            }
        }
    }
}
 
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class CliInstallEnvironmentCollection
{
    public const string Name = nameof(CliInstallEnvironmentCollection);
}