File: GoCodegenValidationTests.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 Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests;
 
/// <summary>
/// End-to-end tests that validate <c>aspire restore</c> for Go AppHosts by creating a
/// Go AppHost with multiple integrations and verifying the generated Go SDK files.
/// </summary>
public sealed class GoCodegenValidationTests(ITestOutputHelper output)
{
    [Fact]
    [CaptureWorkspaceOnFailure]
    public async Task RestoreGeneratesSdkFiles()
    {
        var repoRoot = CliE2ETestHelpers.GetRepoRoot();
        var strategy = CliInstallStrategy.Detect(output.WriteLine);
        using var workspace = TemporaryWorkspace.Create(output);
 
        using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, variant: CliE2ETestHelpers.DockerfileVariant.PolyglotGo, 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);
        await auto.EnableExperimentalGoSupportAsync(counter);
 
        await auto.AspireInitGoAppHostAsync(counter);
 
        await auto.AspireAddPackageAsync("Aspire.Hosting.Redis", counter);
        await auto.RunCommandAsync("aspire restore --non-interactive", counter, TimeSpan.FromMinutes(3));
 
        var modulesDir = Path.Combine(workspace.WorkspaceRoot.FullName, ".aspire", "modules");
        Assert.True(Directory.Exists(modulesDir), $".aspire/modules directory was not created at {modulesDir}");
 
        // aspire.go is the whole generated SDK in one file, so assert on substrings and keep it out of failure messages.
        var sdkPath = Path.Combine(modulesDir, "aspire.go");
        var codegenHashPath = Path.Combine(modulesDir, ".codegen-hash");
        Assert.True(File.Exists(sdkPath), $"Expected generated file not found: {sdkPath}");
        Assert.True(File.Exists(codegenHashPath), $"Expected generated file not found: {codegenHashPath}");
        Assert.False(
            File.ReadAllText(sdkPath).Contains("AddSqlServer", StringComparison.Ordinal),
            "Baseline SDK already exposes AddSqlServer; test cannot verify refresh behavior after adding Aspire.Hosting.SqlServer.");
        var initialCodegenHash = File.ReadAllText(codegenHashPath);
 
        // `aspire add` regenerates the SDK itself, so once it returns aspire.go already exposes AddSqlServer and
        // .codegen-hash has already changed, and the restore below would have nothing left to prove. Putting the
        // baseline copies back leaves a stale SDK next to an aspire.config.json that lists SqlServer, which is
        // the state a restore has to refresh. The copies run inside the container: the workspace is bind-mounted
        // and its files belong to the container's root user, so a write from the host can fail on Linux.
        await auto.RunCommandAsync("mkdir -p ~/sdk-baseline && cp .aspire/modules/aspire.go .aspire/modules/.codegen-hash ~/sdk-baseline/", counter);
        await auto.AspireAddPackageAsync("Aspire.Hosting.SqlServer", counter);
        await auto.RunCommandAsync("cp ~/sdk-baseline/aspire.go ~/sdk-baseline/.codegen-hash .aspire/modules/", counter);
 
        Assert.False(
            File.ReadAllText(sdkPath).Contains("AddSqlServer", StringComparison.Ordinal),
            "The baseline SDK was not put back after aspire add, so restore would have nothing to refresh.");
        Assert.Equal(initialCodegenHash, File.ReadAllText(codegenHashPath));
 
        await auto.RunCommandAsync("aspire restore --non-interactive", counter, TimeSpan.FromMinutes(3));
 
        var expectedFiles = new[] { "aspire.go", "base.go", "transport.go", "go.mod" };
 
        foreach (var file in expectedFiles)
        {
            var filePath = Path.Combine(modulesDir, file);
            Assert.True(File.Exists(filePath), $"Expected generated file not found: {filePath}");
            Assert.False(string.IsNullOrWhiteSpace(File.ReadAllText(filePath)), $"Generated file is empty: {filePath}");
        }
 
        var sdk = File.ReadAllText(sdkPath);
        Assert.True(sdk.Contains("AddRedis", StringComparison.Ordinal), "aspire.go does not contain AddRedis from Aspire.Hosting.Redis");
        Assert.True(
            sdk.Contains("AddSqlServer", StringComparison.Ordinal),
            "aspire.go does not contain AddSqlServer from Aspire.Hosting.SqlServer; restore did not refresh an already-populated .aspire/modules");
 
        Assert.NotEqual(initialCodegenHash, File.ReadAllText(codegenHashPath));
    }
}