File: MauiMacCatalystExtensionsTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Maui.Tests\Aspire.Hosting.Maui.Tests.csproj (Aspire.Hosting.Maui.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.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Aspire.Hosting.Maui.Utilities;
using Aspire.Hosting.Tests.Utils;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Tests;
 
public class MauiMacCatalystExtensionsTests
{
    [Fact]
    public void AddMacCatalystDevice_CreatesResource()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act
            var macCatalyst = maui.AddMacCatalystDevice();
 
            // Assert
            Assert.NotNull(macCatalyst);
            Assert.Equal("mauiapp-maccatalyst", macCatalyst.Resource.Name);
            Assert.Equal(maui.Resource, macCatalyst.Resource.Parent);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_WithCustomName_UsesProvidedName()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act
            var macCatalyst = maui.AddMacCatalystDevice("custom-maccatalyst");
 
            // Assert
            Assert.Equal("custom-maccatalyst", macCatalyst.Resource.Name);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_DuplicateName_ThrowsException()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
            maui.AddMacCatalystDevice("device1");
 
            // Act & Assert
            var exception = Assert.Throws<DistributedApplicationException>(() => maui.AddMacCatalystDevice("device1"));
            Assert.Contains("already exists", exception.Message);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_MultipleDevices_AllowsMultipleWithDifferentNames()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act
            var device1 = maui.AddMacCatalystDevice("device1");
            var device2 = maui.AddMacCatalystDevice("device2");
 
            // Assert
            Assert.Equal(2, appBuilder.Resources.OfType<Maui.MauiMacCatalystPlatformResource>().Count());
            Assert.Contains(device1.Resource, appBuilder.Resources);
            Assert.Contains(device2.Resource, appBuilder.Resources);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_SetsCorrectResourceProperties()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act
            var macCatalyst = maui.AddMacCatalystDevice();
 
            // Assert
            var executableAnnotation = macCatalyst.Resource.Annotations.OfType<ExecutableAnnotation>().Single();
            Assert.Equal("dotnet", executableAnnotation.Command);
            Assert.NotNull(executableAnnotation.WorkingDirectory);
            Assert.Equal(maui.Resource, macCatalyst.Resource.Parent);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public async Task AddMacCatalystDevice_SetsCorrectCommandLineArguments()
    {
        // Arrange - Create a temporary project file with macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act
            var macCatalyst = maui.AddMacCatalystDevice();
 
            using var app = appBuilder.Build();
 
            // Assert
            var args = await ArgumentEvaluator.GetArgumentListAsync(macCatalyst.Resource);
            Assert.Contains("run", args);
            Assert.Contains("-f", args);
            Assert.Contains("net10.0-maccatalyst", args);
            Assert.Contains("-p:OpenArguments=-W", args);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public async Task AddMacCatalystDevice_WithoutMacCatalystTfm_ThrowsOnBeforeStartEvent()
    {
        // Arrange - Create a temporary project file without macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-windows10.0.19041.0</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            var appBuilder = DistributedApplication.CreateBuilder();
            var maui = appBuilder.AddMauiProject("mauiapp", tempFile);
 
            // Act - Adding the device should succeed (validation deferred to start)
            var macCatalyst = maui.AddMacCatalystDevice();
            
            // Assert - Resource is created
            Assert.NotNull(macCatalyst);
            Assert.Equal("mauiapp-maccatalyst", macCatalyst.Resource.Name);
            
            // Build the app to get access to eventing
            await using var app = appBuilder.Build();
            
            // Trigger the BeforeResourceStartedEvent which should throw
            var exception = await Assert.ThrowsAsync<DistributedApplicationException>(async () =>
            {
                await app.Services.GetRequiredService<IDistributedApplicationEventing>()
                    .PublishAsync(new BeforeResourceStartedEvent(macCatalyst.Resource, app.Services), CancellationToken.None);
            });
            
            Assert.Contains("Unable to detect Mac Catalyst target framework", exception.Message);
            Assert.Contains(tempFile, exception.Message);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_DetectsMacCatalystTfmFromMultiTargetedProject()
    {
        // Arrange - Create a temporary project file with multiple TFMs including macOS Catalyst
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0</TargetFrameworks>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            // Act
            var tfm = ProjectFileReader.GetPlatformTargetFramework(tempFile, "maccatalyst");
 
            // Assert
            Assert.NotNull(tfm);
            Assert.Equal("net10.0-maccatalyst", tfm);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    [Fact]
    public void AddMacCatalystDevice_DetectsMacCatalystTfmFromSingleTargetProject()
    {
        // Arrange - Create a temporary project file with single macOS Catalyst TFM
        var projectContent = """
            <Project Sdk="Microsoft.NET.Sdk">
                <PropertyGroup>
                    <TargetFramework>net10.0-maccatalyst</TargetFramework>
                </PropertyGroup>
            </Project>
            """;
        var tempFile = CreateTempProjectFile(projectContent);
 
        try
        {
            // Act
            var tfm = ProjectFileReader.GetPlatformTargetFramework(tempFile, "maccatalyst");
 
            // Assert
            Assert.NotNull(tfm);
            Assert.Equal("net10.0-maccatalyst", tfm);
        }
        finally
        {
            CleanupTempFile(tempFile);
        }
    }
 
    private static string CreateTempProjectFile(string content)
    {
        var tempFile = Path.GetTempFileName();
        var tempProjectFile = Path.ChangeExtension(tempFile, ".csproj");
        if (File.Exists(tempFile))
        {
            File.Delete(tempFile);
        }
        File.WriteAllText(tempProjectFile, content);
        return tempProjectFile;
    }
 
    private static void CleanupTempFile(string tempFile)
    {
        if (File.Exists(tempFile))
        {
            File.Delete(tempFile);
        }
    }
}