File: TemplateManifest\TemplateManifestGenerationTests.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 System.IO.Compression;
using Aspire.TestUtilities;
using Xunit;
 
namespace Infrastructure.Tests.TemplateManifest;
 
public sealed class TemplateManifestGenerationTests(ITestOutputHelper output)
{
    [Theory]
    [InlineData("Debug", false)]
    [InlineData("Release", false)]
    [InlineData("Release", true)]
    [RequiresTools(["pwsh"])]
    public async Task GeneratesEveryTimeUsingThePackedTemplateVersion(string configuration, bool customFeed)
    {
        using var workspace = TemporaryWorkspace.Create(output);
        var root = workspace.CreateDirectory("repository with spaces").FullName;
        var feed = customFeed ? Path.Combine(root, "custom feed") : Path.Combine(root, "artifacts", "packages", configuration, "Shipping");
        Directory.CreateDirectory(feed);
        CreateTemplatePackage(feed, "14.0.0-pr.123");
        CreateDotnetStub(root);
        var report = Path.Combine(root, "invocations.txt");
 
        using var command = CreateCommand()
            .WithEnvironmentVariable("TEMPLATE_CG_REPORT", report)
            .WithEnvironmentVariable("TEMPLATE_CG_EXIT_CODE", "0");
        string[] arguments = ["-RepositoryRoot", $"\"{root}\"", "-Configuration", configuration];
        if (customFeed)
        {
            arguments = [.. arguments, "-PackageDirectory", $"\"{feed}\""];
        }
 
        (await command.ExecuteAsync(arguments)).EnsureSuccessful();
 
        // An existing output must not turn the next package build into a skipped invocation.
        var manifestPath = Path.Combine(root, "artifacts", "cg", "templates", "cgmanifest.json");
        Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)!);
        File.WriteAllText(manifestPath, """{"version":1,"registrations":[]}""");
        (await command.ExecuteAsync(arguments)).EnsureSuccessful();
 
        var lines = File.ReadAllLines(report);
        Assert.Equal(2, lines.Length);
        Assert.All(lines, line =>
        {
            Assert.Contains("-target:GenerateTemplateCgManifest", line);
            Assert.Contains($"-property:Configuration={configuration}", line);
            Assert.Contains("-property:PackageVersion=14.0.0-pr.123", line);
            Assert.Contains($"-property:TemplateCgPackageDirectory={feed}", line);
        });
    }
 
    [Theory]
    [InlineData("missing-feed", "Shipping packages not found")]
    [InlineData("no-template-package", "Expected exactly one Aspire.ProjectTemplates package")]
    [InlineData("multiple-template-packages", "Expected exactly one Aspire.ProjectTemplates package")]
    [InlineData("failed-generation", "Template component manifest generation failed")]
    [RequiresTools(["pwsh"])]
    public async Task FailsInsteadOfSkippingMissingInputsOrFailedGeneration(string scenario, string expectedError)
    {
        using var workspace = TemporaryWorkspace.Create(output);
        var root = workspace.CreateDirectory("repo").FullName;
        var feed = Path.Combine(root, "artifacts", "packages", "Debug", "Shipping");
        if (scenario != "missing-feed")
        {
            Directory.CreateDirectory(feed);
        }
        if (scenario is "multiple-template-packages" or "failed-generation")
        {
            CreateTemplatePackage(feed, "14.0.0-pr.123");
        }
        if (scenario == "multiple-template-packages")
        {
            CreateTemplatePackage(feed, "14.0.0-pr.124");
        }
        CreateDotnetStub(root);
        using var command = CreateCommand()
            .WithEnvironmentVariable("TEMPLATE_CG_REPORT", Path.Combine(root, "invocations.txt"))
            .WithEnvironmentVariable("TEMPLATE_CG_EXIT_CODE", "1");
 
        var result = await command.ExecuteAsync("-RepositoryRoot", $"\"{root}\"");
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(expectedError, result.Output);
    }
 
    private PowerShellCommand CreateCommand() =>
        new PowerShellCommand(Path.Combine(RepoRoot.Path, "eng", "scripts", "generate-template-cgmanifest.ps1"), output)
            .WithTimeout(TimeSpan.FromMinutes(1));
 
    private static void CreateTemplatePackage(string directory, string version)
    {
        using var archive = ZipFile.Open(Path.Combine(directory, $"Aspire.ProjectTemplates.{version}.nupkg"), ZipArchiveMode.Create);
        using var writer = new StreamWriter(archive.CreateEntry("Aspire.ProjectTemplates.nuspec").Open());
        writer.Write($"""
            <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
              <metadata><id>Aspire.ProjectTemplates</id><version>{version}</version></metadata>
            </package>
            """);
    }
 
    private static void CreateDotnetStub(string root)
    {
        if (OperatingSystem.IsWindows())
        {
            File.WriteAllText(Path.Combine(root, "dotnet.cmd"), """
                @echo off
                echo %*>>"%TEMPLATE_CG_REPORT%"
                exit /b %TEMPLATE_CG_EXIT_CODE%
                """);
        }
        else
        {
            var path = Path.Combine(root, "dotnet.sh");
            File.WriteAllText(path, """
                #!/bin/sh
                printf '%s\n' "$*" >> "$TEMPLATE_CG_REPORT"
                exit "$TEMPLATE_CG_EXIT_CODE"
                """);
            File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
        }
    }
}