File: DotnetProjectPublicApiTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Dotnet.Tests\Aspire.Hosting.Dotnet.Tests.csproj (Aspire.Hosting.Dotnet.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable ASPIREDOTNETPROJECT001
 
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Aspire.Hosting.Utils;
 
namespace Aspire.Hosting.Dotnet.Tests;
 
public class DotnetProjectPublicApiTests
{
    // ---- Experimental tagging ------------------------------------------------------
 
    [Fact]
    public void DotnetProjectResourceIsTaggedWithExpectedExperimentalDiagnostic()
    {
        var attribute = Assert.Single(typeof(DotnetProjectResource).GetCustomAttributes<ExperimentalAttribute>());
 
        Assert.Equal("ASPIREDOTNETPROJECT001", attribute.DiagnosticId);
        Assert.Equal("https://aka.ms/aspire/diagnostics/{0}", attribute.UrlFormat);
    }
 
    [Fact]
    public void EveryPublicAddDotnetProjectOverloadIsTaggedWithExpectedExperimentalDiagnostic()
    {
        var overloads = typeof(DotnetProjectHostingExtensions)
            .GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Where(m => m.Name == nameof(DotnetProjectHostingExtensions.AddDotnetProject))
            .ToList();
 
        // Guards against silently losing coverage if an overload is added or removed.
        Assert.Equal(2, overloads.Count);
 
        foreach (var method in overloads)
        {
            var attribute = Assert.Single(method.GetCustomAttributes<ExperimentalAttribute>());
 
            Assert.Equal("ASPIREDOTNETPROJECT001", attribute.DiagnosticId);
            Assert.Equal("https://aka.ms/aspire/diagnostics/{0}", attribute.UrlFormat);
        }
    }
 
    // ---- DotnetProjectResource constructor guards --------------------------------
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void CtorDotnetProjectResourceShouldThrowWhenNameIsNullOrEmpty(bool isNull)
    {
        var name = isNull ? null! : string.Empty;
        const string workingDirectory = "/src/app";
 
        var action = () => new DotnetProjectResource(name, workingDirectory);
 
        var exception = isNull
            ? Assert.Throws<ArgumentNullException>(action)
            : Assert.Throws<ArgumentException>(action);
        Assert.Equal(nameof(name), exception.ParamName);
    }
 
    [Fact]
    public void CtorDotnetProjectResourceShouldThrowWhenWorkingDirectoryIsNull()
    {
        const string name = "app";
 
        var action = () => new DotnetProjectResource(name, workingDirectory: null!);
 
        var exception = Assert.Throws<ArgumentNullException>(action);
        Assert.Equal("workingDirectory", exception.ParamName);
    }
 
    // ---- AddDotnetProject guards -------------------------------------------------
 
    [Fact]
    public void AddDotnetProjectShouldThrowWhenBuilderIsNull()
    {
        IDistributedApplicationBuilder builder = null!;
 
        var action = () => builder.AddDotnetProject("app", "app.csproj");
 
        var exception = Assert.Throws<ArgumentNullException>(action);
        Assert.Equal(nameof(builder), exception.ParamName);
    }
 
    [Fact]
    public void AddDotnetProjectShouldThrowWhenNameIsNull()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var action = () => builder.AddDotnetProject(null!, "app.csproj");
 
        var exception = Assert.Throws<ArgumentNullException>(action);
        Assert.Equal("name", exception.ParamName);
    }
 
    [Fact]
    public void AddDotnetProjectShouldThrowWhenPathIsNull()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var action = () => builder.AddDotnetProject("app", null!);
 
        var exception = Assert.Throws<ArgumentNullException>(action);
        Assert.Equal("path", exception.ParamName);
    }
 
    [Fact]
    public void AddDotnetProjectWithConfigureShouldThrowWhenConfigureIsNull()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var action = () => builder.AddDotnetProject("app", "app.csproj", configure: null!);
 
        var exception = Assert.Throws<ArgumentNullException>(action);
        Assert.Equal("configure", exception.ParamName);
    }
 
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    public void WithBuildEnvironmentShouldThrowWhenNameIsNullOrEmpty(string? name)
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddResource(new DotnetProjectResource("app", builder.AppHostDirectory));
 
        var action = () => project.WithBuildEnvironment(name!, "value");
 
        var exception = name is null
            ? Assert.Throws<ArgumentNullException>(action)
            : Assert.Throws<ArgumentException>(action);
        Assert.Equal(nameof(name), exception.ParamName);
    }
 
    [Fact]
    public void WithBuildEnvironmentOverloadsShouldRejectFileBasedAppsImmediately()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
        var project = builder.AddResource(new DotnetProjectResource("app", builder.AppHostDirectory));
        project.Resource.Annotations.Add(new DotnetProjectMetadata("app.cs", buildConfiguration: null));
        const string expectedMessage =
            "The .NET resource 'app' uses WithBuildEnvironment, which is supported only for project files.";
 
        var valueException = Assert.Throws<DistributedApplicationException>(
            () => project.WithBuildEnvironment("BUILD_FLAVOR", "custom"));
        var callbackException = Assert.Throws<DistributedApplicationException>(
            () => project.WithBuildEnvironment(_ => { }));
        var asyncCallbackException = Assert.Throws<DistributedApplicationException>(
            () => project.WithBuildEnvironment(_ => Task.CompletedTask));
 
        Assert.Equal(expectedMessage, valueException.Message);
        Assert.Equal(expectedMessage, callbackException.Message);
        Assert.Equal(expectedMessage, asyncCallbackException.Message);
    }
}