File: GenerateE2EManifestTaskTests.cs
Web Access
Project: src\src\Components\Testing\test\Microsoft.AspNetCore.Components.Testing.Tests.csproj (Microsoft.AspNetCore.Components.Testing.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.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Testing.Infrastructure;
using Microsoft.AspNetCore.Components.Testing.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
 
namespace Microsoft.AspNetCore.Components.Testing.Tests;
 
public class GenerateE2EManifestTaskTests : IDisposable
{
    private readonly string _tempDir;
 
    public GenerateE2EManifestTaskTests()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), "E2EManifestTests_" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(_tempDir);
    }
 
    public void Dispose()
    {
        if (Directory.Exists(_tempDir))
        {
            Directory.Delete(_tempDir, recursive: true);
        }
    }
 
    // ── build mode + Build scenario (local dev) ──────────────────────
    [Fact]
    public void BuildMode_Build_UsesAbsoluteSourcePaths()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "build",
            isPublishing: false);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        Assert.Single(manifest.Apps);
 
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        Assert.Equal("dotnet", entry!.Executable);
        Assert.Equal("run --no-launch-profile", entry.Arguments);
        Assert.Equal(projectDir, entry.WorkingDirectory);
    }
 
    // ── build mode + Publish scenario ────────────────────────────────
    [Fact]
    public void BuildMode_Publish_UsesRelativeSourcePaths()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "build",
            isPublishing: true);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        Assert.Equal("dotnet", entry!.Executable);
        Assert.Equal(Path.Combine("e2e-apps", "MyApp"), entry.WorkingDirectory);
    }
 
    // ── publish mode + Build scenario (errors — no published output) ─
    [Fact]
    public void PublishMode_Build_FailsWhenNoPublishedOutput()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "publish",
            isPublishing: false);
 
        Assert.False(task.Execute());
    }
 
    // ── publish mode + Publish scenario (exe found) ──────────────────
    [Fact]
    public void PublishMode_Publish_WithExe_UsesExeExecutable()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var appsOutputDir = Path.Combine(_tempDir, "e2e-apps");
        CreatePublishedApp(appsOutputDir, "MyApp", createExe: true);
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "publish",
            isPublishing: true,
            appsOutputDir: appsOutputDir);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        var expectedExe = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "MyApp.exe" : "MyApp";
        Assert.Equal(expectedExe, entry!.Executable);
        Assert.Equal("", entry.Arguments);
        Assert.Equal(Path.Combine("e2e-apps", "MyApp"), entry.WorkingDirectory);
    }
 
    // ── publish mode + Publish scenario (dll only) ───────────────────
    [Fact]
    public void PublishMode_Publish_WithDll_UsesDotnetExec()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var appsOutputDir = Path.Combine(_tempDir, "e2e-apps");
        CreatePublishedApp(appsOutputDir, "MyApp", createExe: false);
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "publish",
            isPublishing: true,
            appsOutputDir: appsOutputDir);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        Assert.Equal("dotnet", entry!.Executable);
        Assert.Equal("MyApp.dll", entry.Arguments);
    }
 
    // ── all mode + Build scenario (local dev — build entry only, publish fails) ─
    [Fact]
    public void AllMode_Build_FailsWhenNoPublishedOutput()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "all",
            isPublishing: false);
 
        Assert.False(task.Execute());
    }
 
    // ── all mode + Publish scenario ──────────────────────────────────
    [Fact]
    public void AllMode_Publish_CreatesBothEntries()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var appsOutputDir = Path.Combine(_tempDir, "e2e-apps");
        // In 'all' mode, published output goes to publish/<name>
        CreatePublishedApp(Path.Combine(appsOutputDir, "publish"), "MyApp", createExe: true);
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "all",
            isPublishing: true,
            appsOutputDir: appsOutputDir);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        Assert.Equal(2, manifest.Apps.Count);
 
        // Build entry
        var buildEntry = manifest.GetApp("MyApp");
        Assert.NotNull(buildEntry);
        Assert.Equal("dotnet", buildEntry!.Executable);
        Assert.Equal("run --no-launch-profile", buildEntry.Arguments);
        Assert.Equal(Path.Combine("e2e-apps", "MyApp"), buildEntry.WorkingDirectory);
 
        // Publish entry
        var publishEntry = manifest.GetApp("publish/MyApp");
        Assert.NotNull(publishEntry);
        var expectedExe = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "MyApp.exe" : "MyApp";
        Assert.Equal(expectedExe, publishEntry!.Executable);
        Assert.Equal("", publishEntry.Arguments);
        Assert.Equal(Path.Combine("e2e-apps", "publish", "MyApp"), publishEntry.WorkingDirectory);
    }
 
    // ── Custom E2EAppsRelativeDir ────────────────────────────────────
    [Fact]
    public void BuildMode_Publish_UsesCustomRelativeDir()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("MyApp", projectDir)],
            manifestPath: manifestPath,
            mode: "build",
            isPublishing: true,
            appsRelativeDir: "test-apps");
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        Assert.Equal(Path.Combine("test-apps", "MyApp"), entry!.WorkingDirectory);
    }
 
    // ── Multiple apps ────────────────────────────────────────────────
    [Fact]
    public void BuildMode_Build_MultipleApps_AllPresent()
    {
        var dir1 = CreateFakeProjectDir("App1");
        var dir2 = CreateFakeProjectDir("App2");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var task = CreateTask(
            apps: [CreateAppItem("App1", dir1), CreateAppItem("App2", dir2)],
            manifestPath: manifestPath,
            mode: "build",
            isPublishing: false);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        Assert.Equal(2, manifest.Apps.Count);
        Assert.NotNull(manifest.GetApp("App1"));
        Assert.NotNull(manifest.GetApp("App2"));
    }
 
    // ── PublicUrl metadata flows through ─────────────────────────────
    [Fact]
    public void BuildMode_Build_PublicUrlFlowsThrough()
    {
        var projectDir = CreateFakeProjectDir("MyApp");
        var manifestPath = Path.Combine(_tempDir, "manifest.json");
 
        var item = CreateAppItem("MyApp", projectDir);
        item.SetMetadata("E2EPublicUrl", "https://localhost:5001");
 
        var task = CreateTask(
            apps: [item],
            manifestPath: manifestPath,
            mode: "build",
            isPublishing: false);
 
        Assert.True(task.Execute());
 
        var manifest = ReadManifest(manifestPath);
        var entry = manifest.GetApp("MyApp");
        Assert.NotNull(entry);
        Assert.Equal("https://localhost:5001", entry!.PublicUrl);
    }
 
    // ── Helpers ──────────────────────────────────────────────────────
 
    private string CreateFakeProjectDir(string name)
    {
        var dir = Path.Combine(_tempDir, "src", name);
        Directory.CreateDirectory(dir);
        File.WriteAllText(Path.Combine(dir, name + ".csproj"), "<Project />");
        return dir;
    }
 
    private static void CreatePublishedApp(string appsOutputDir, string name, bool createExe)
    {
        var dir = Path.Combine(appsOutputDir, name);
        Directory.CreateDirectory(dir);
        if (createExe)
        {
            var exeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : "";
            File.WriteAllText(Path.Combine(dir, name + exeSuffix), "fake-exe");
        }
        else
        {
            File.WriteAllText(Path.Combine(dir, name + ".dll"), "fake-dll");
        }
    }
 
    private static TaskItem CreateAppItem(string name, string projectDir)
    {
        var item = new TaskItem(Path.Combine(projectDir, name + ".csproj"));
        item.SetMetadata("E2EApp", "true");
        return item;
    }
 
#nullable enable
 
    private GenerateE2EManifest CreateTask(
        TaskItem[] apps,
        string manifestPath,
        string mode,
        bool isPublishing,
        string? appsOutputDir = null,
        string appsRelativeDir = "e2e-apps")
    {
        return new GenerateE2EManifest
        {
            AppItems = apps,
            ManifestPath = manifestPath,
            E2EAppsOutputDir = appsOutputDir ?? Path.Combine(_tempDir, "e2e-apps"),
            E2EAppsRelativeDir = appsRelativeDir,
            E2EAppMode = mode,
            IsPublishing = isPublishing ? "true" : "false",
            BuildEngine = new MockBuildEngine(),
        };
    }
 
    private static E2EManifest ReadManifest(string path)
    {
        var json = File.ReadAllText(path);
        var manifest = JsonSerializer.Deserialize<E2EManifest>(json);
        Assert.NotNull(manifest);
        return manifest!;
    }
 
    private sealed class MockBuildEngine : IBuildEngine
    {
        public bool ContinueOnError => false;
        public int LineNumberOfTaskNode => 0;
        public int ColumnNumberOfTaskNode => 0;
        public string ProjectFileOfTaskNode => "";
 
        public void LogErrorEvent(BuildErrorEventArgs e) { }
        public void LogWarningEvent(BuildWarningEventArgs e) { }
        public void LogMessageEvent(BuildMessageEventArgs e) { }
        public void LogCustomEvent(CustomBuildEventArgs e) { }
        public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs) => false;
    }
}