File: Helpers\LocalDeploymentTestHelpers.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.ComponentModel;
using System.Diagnostics;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests.Helpers;
 
internal static class LocalDeploymentTestHelpers
{
    internal const string DeploymentLabel = "aspire.e2e.deployment";
 
    internal static void WriteReferencingWebProgram(string projectDir, string projectName, string apiResourceName)
    {
        var webDirectory = Path.Combine(projectDir, $"{projectName}.Web");
        Assert.True(File.Exists(Path.Combine(webDirectory, $"{projectName}.Web.csproj")));
        Assert.True(File.Exists(Path.Combine(webDirectory, "Properties", "launchSettings.json")));
        var programPath = Path.Combine(webDirectory, "Program.cs");
        Assert.True(File.Exists(programPath));
 
        // Keep the template projects and launch metadata; replace only the HTTP behavior.
        // The named client must resolve Aspire's injected reference, not a hard-coded host.
        File.WriteAllText(programPath, $$"""
            var builder = WebApplication.CreateBuilder(args);
            builder.AddServiceDefaults();
            builder.Services.AddHttpClient("api", client =>
            {
                client.BaseAddress = new Uri("http://{{apiResourceName}}");
                client.Timeout = TimeSpan.FromSeconds(3);
            });
 
            var app = builder.Build();
            app.MapDefaultEndpoints();
            app.MapGet("/test-deployment", async (IHttpClientFactory clients) =>
                Results.Text("WEB->" + await clients.CreateClient("api").GetStringAsync("/test-deployment")));
            app.Run();
            """);
    }
 
    internal static async Task CleanupLabeledDockerResourcesAsync(string label, ITestOutputHelper output)
    {
        foreach (var kind in new[] { "container", "volume", "network" })
        {
            var resources = await RunDockerCleanupAsync(GetLabeledResourceListArguments(kind, label), output);
            foreach (var resource in resources.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries))
            {
                string[] arguments = kind == "container"
                    ? [kind, "rm", "-f", "-v", resource]
                    : [kind, "rm", resource];
                await RunDockerCleanupAsync(arguments, output);
            }
        }
    }
 
    internal static string[] GetLabeledResourceListArguments(string kind, string label)
    {
        return kind == "container"
            ? [kind, "ls", "--all", "-q", "--filter", $"label={label}"]
            : [kind, "ls", "-q", "--filter", $"label={label}"];
    }
 
    internal static async Task CleanupImageAsync(string image, ITestOutputHelper output)
    {
        await RunDockerCleanupAsync(["image", "rm", image], output);
    }
 
    private static async Task<string> RunDockerCleanupAsync(string[] arguments, ITestOutputHelper output)
    {
        try
        {
            // Cleanup must still run when the test's cancellation token has been cancelled.
            using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(60));
            using var process = new Process
            {
                StartInfo = new ProcessStartInfo("docker")
                {
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false
                }
            };
            foreach (var argument in arguments)
            {
                process.StartInfo.ArgumentList.Add(argument);
            }
 
            process.Start();
            var stdout = process.StandardOutput.ReadToEndAsync(timeout.Token);
            var stderr = process.StandardError.ReadToEndAsync(timeout.Token);
            try
            {
                await process.WaitForExitAsync(timeout.Token);
                var result = await stdout;
                var error = await stderr;
                if (process.ExitCode != 0)
                {
                    output.WriteLine($"Cleanup warning: docker {string.Join(' ', arguments)} failed (exit {process.ExitCode}): {error}");
                    return "";
                }
 
                output.WriteLine($"Cleanup: docker {string.Join(' ', arguments)} succeeded.");
                return result;
            }
            finally
            {
                if (!process.HasExited)
                {
                    process.Kill(entireProcessTree: true);
                }
            }
        }
        catch (Exception ex) when (ex is Win32Exception or IOException or InvalidOperationException or OperationCanceledException)
        {
            // Only cleanup process/IO failures are best-effort. This helper is private so
            // setup, deployment and verification commands cannot accidentally use it.
            output.WriteLine($"Cleanup warning: docker {string.Join(' ', arguments)} failed ({ex.GetType().Name}): {ex.Message}");
            return "";
        }
    }
}