File: CreateLayout\CreateLayoutTests.cs
Web Access
Project: src\tests\Infrastructure.Tests\Infrastructure.Tests.csproj (Infrastructure.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.Tools.CreateLayout;
using Xunit;
 
namespace Infrastructure.Tests.CreateLayout;
 
public class CreateLayoutTests(ITestOutputHelper testOutputHelper)
{
    [Theory]
    [InlineData("Debug", "Release")]
    [InlineData("Release", "Debug")]
    [InlineData("Custom", "Release")]
    public void FindPublishPath_UsesOnlyRequestedConfiguration(string configuration, string otherConfiguration)
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, "win-x64", configuration, "1.0.0", verbose: false);
        var expectedPaths = GetPublishPaths(workspace.Path, configuration);
 
        foreach (var path in expectedPaths.Concat(GetPublishPaths(workspace.Path, otherConfiguration)))
        {
            Directory.CreateDirectory(path);
        }
 
        foreach (var expectedPath in expectedPaths)
        {
            Assert.Equal(expectedPath, builder.FindPublishPath("Aspire.Dashboard", "net11.0"));
            Directory.Delete(expectedPath);
        }
 
        Assert.Null(builder.FindPublishPath("Aspire.Dashboard", "net11.0"));
    }
 
    [Fact]
    public void FindPublishPath_RequireRidSpecific_IgnoresNonRidPublish()
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false);
        var nonRidPath = GetPublishPaths(workspace.Path, "Debug")[^1];
        Directory.CreateDirectory(nonRidPath);
 
        Assert.Null(builder.FindPublishPath("Aspire.Dashboard", "net11.0", requireRidSpecific: true));
        Assert.Equal(nonRidPath, builder.FindPublishPath("Aspire.Dashboard", "net11.0"));
    }
 
    [Fact]
    public async Task Main_RequiresConfiguration()
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        var outputPath = Path.Combine(workspace.Path, "layout");
 
        var exitCode = await Aspire.Tools.CreateLayout.Program.Main(
        [
            "--output", outputPath,
            "--artifacts", workspace.Path,
            "--rid", "win-x64"
        ]);
 
        Assert.Equal(1, exitCode);
        Assert.False(Directory.Exists(outputPath));
    }
 
    [Theory]
    [InlineData("win-x64", "e_sqlite3.dll")]
    [InlineData("win-arm64", "e_sqlite3.dll")]
    [InlineData("linux-x64", "libe_sqlite3.so")]
    [InlineData("linux-arm64", "libe_sqlite3.so")]
    [InlineData("linux-musl-x64", "libe_sqlite3.so")]
    [InlineData("osx-x64", "libe_sqlite3.dylib")]
    [InlineData("osx-arm64", "libe_sqlite3.dylib")]
    public void CopyDashboard_CopiesCompletePublishPayloadWithoutSymbols(string rid, string sqliteLibraryName)
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        var publishPath = CreateDashboardPublishOutput(workspace.Path, rid, sqliteLibraryName);
        var executableName = rid.StartsWith("win-", StringComparison.Ordinal) ? "Aspire.Dashboard.exe" : "Aspire.Dashboard";
        string[] expectedFiles =
        [
            executableName,
            sqliteLibraryName,
            "appsettings.json",
            "other-native-library.dll",
            "locales/en/messages.json",
            "wwwroot/_framework/blazor.web.js"
        ];
        string[] symbolFiles =
        [
            "Aspire.Dashboard.pdb",
            "Aspire.Dashboard.dbg",
            "Aspire.Dashboard.dSYM/Contents/Resources/DWARF/Aspire.Dashboard",
            "locales/nested.pdb",
            "locales/nested.dbg",
            "locales/nested.dSYM/Contents/Resources/DWARF/nested"
        ];
        foreach (var relativePath in expectedFiles.Concat(symbolFiles))
        {
            var filePath = Path.Combine(publishPath, relativePath);
            Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
            File.WriteAllText(filePath, relativePath);
        }
 
        var outputPath = Path.Combine(workspace.Path, "layout");
        using var builder = new LayoutBuilder(outputPath, workspace.Path, rid, "Debug", "1.0.0", verbose: false);
 
        builder.CopyDashboard();
 
        var dashboardPath = Path.Combine(outputPath, "dashboard");
        var actualFiles = Directory.GetFiles(dashboardPath, "*", SearchOption.AllDirectories)
            .Select(path => Path.GetRelativePath(dashboardPath, path).Replace(Path.DirectorySeparatorChar, '/'));
        Assert.Equal(expectedFiles.Order(StringComparer.Ordinal), actualFiles.Order(StringComparer.Ordinal));
        foreach (var relativePath in expectedFiles)
        {
            Assert.Equal(File.ReadAllBytes(Path.Combine(publishPath, relativePath)), File.ReadAllBytes(Path.Combine(dashboardPath, relativePath)));
        }
    }
 
    [Fact]
    public void CopyDashboard_MissingStaticAssets_Throws()
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        var publishPath = CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
        var wwwrootPath = Path.Combine(publishPath, "wwwroot");
        Directory.Delete(wwwrootPath, recursive: true);
        var outputPath = Path.Combine(workspace.Path, "layout");
        using var builder = new LayoutBuilder(outputPath, workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false);
 
        var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
 
        Assert.Equal($"Native AOT Dashboard static assets not found at {wwwrootPath}", exception.Message);
        Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void CopyDashboard_MissingOrEmptyBlazorScript_Throws(bool empty)
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        var publishPath = CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
        var blazorScriptPath = Path.Combine(publishPath, "wwwroot", "_framework", "blazor.web.js");
        if (empty)
        {
            File.WriteAllBytes(blazorScriptPath, []);
        }
        else
        {
            File.Delete(blazorScriptPath);
        }
 
        var outputPath = Path.Combine(workspace.Path, "layout");
        using var builder = new LayoutBuilder(outputPath, workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false);
 
        var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
 
        Assert.Equal($"Native AOT Dashboard Blazor script missing or empty at {blazorScriptPath}", exception.Message);
        Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
    }
 
    [Theory]
    [InlineData("win-x64", "e_sqlite3.dll", false)]
    [InlineData("win-x64", "e_sqlite3.dll", true)]
    [InlineData("linux-x64", "libe_sqlite3.so", false)]
    [InlineData("linux-x64", "libe_sqlite3.so", true)]
    [InlineData("osx-arm64", "libe_sqlite3.dylib", false)]
    [InlineData("osx-arm64", "libe_sqlite3.dylib", true)]
    public void CopyDashboard_MissingOrEmptySqliteLibrary_Throws(string rid, string sqliteLibraryName, bool empty)
    {
        using var workspace = TemporaryWorkspace.Create(testOutputHelper);
        var publishPath = CreateDashboardPublishOutput(workspace.Path, rid, sqliteLibraryName);
        var sqliteLibraryPath = Path.Combine(publishPath, sqliteLibraryName);
        if (empty)
        {
            File.WriteAllBytes(sqliteLibraryPath, []);
        }
        else
        {
            File.Delete(sqliteLibraryPath);
        }
 
        var outputPath = Path.Combine(workspace.Path, "layout");
        using var builder = new LayoutBuilder(outputPath, workspace.Path, rid, "Debug", "1.0.0", verbose: false);
 
        var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
 
        Assert.Equal($"Native AOT Dashboard SQLite library missing or empty at {sqliteLibraryPath}", exception.Message);
        Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
    }
 
    private static string CreateDashboardPublishOutput(string artifactsPath, string rid, string sqliteLibraryName)
    {
        var publishPath = Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", rid, "Debug", "net11.0", rid, "publish");
        var frameworkPath = Path.Combine(publishPath, "wwwroot", "_framework");
        Directory.CreateDirectory(frameworkPath);
        var executableName = rid.StartsWith("win-", StringComparison.Ordinal) ? "Aspire.Dashboard.exe" : "Aspire.Dashboard";
        File.WriteAllText(Path.Combine(publishPath, executableName), "executable");
        File.WriteAllText(Path.Combine(publishPath, sqliteLibraryName), "sqlite");
        File.WriteAllText(Path.Combine(frameworkPath, "blazor.web.js"), "static asset");
 
        return publishPath;
    }
 
    private static string[] GetPublishPaths(string artifactsPath, string configuration) =>
    [
        Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", "win-x64", configuration, "net11.0", "win-x64", "publish"),
        Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, "net11.0", "win-x64", "publish"),
        Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, "net11.0", "win-x64", "native"),
        Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, "net11.0", "publish")
    ];
}