File: BuildOnlyContainerValidationTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.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 ASPIREPIPELINES001
 
using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Utils;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Hosting.Tests;
 
[Trait("Partition", "6")]
public class BuildOnlyContainerValidationTests
{
    [Fact]
    public async Task PublishPrereq_WithUnconsumedBuildOnlyContainer_Throws()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.PublishPrereq);
 
        var consumed = AddBuildOnlyContainer(builder, "frontend");
        AddBuildOnlyContainer(builder, "orphan");
        builder.AddResource(new TestContainerFilesDestinationResource("api"))
            .PublishWithContainerFiles(consumed, "/app/wwwroot");
 
        using var app = builder.Build();
 
        var ex = await Assert.ThrowsAsync<DistributedApplicationException>(
            () => ExecutePipelineAsync(app)).DefaultTimeout();
 
        Assert.Contains("'orphan'", ex.Message);
        Assert.DoesNotContain("'frontend'", ex.Message);
        Assert.Contains("PublishWithContainerFiles", ex.Message);
        Assert.Contains("builder.Pipeline.DisableBuildOnlyContainerValidation", ex.Message);
    }
 
    [Fact]
    public async Task DeployPrereq_WithUnconsumedBuildOnlyContainer_Throws()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.DeployPrereq);
 
        var consumed = AddBuildOnlyContainer(builder, "frontend");
        AddBuildOnlyContainer(builder, "orphan");
        builder.AddResource(new TestContainerFilesDestinationResource("api"))
            .PublishWithContainerFiles(consumed, "/app/wwwroot");
 
        using var app = builder.Build();
 
        var ex = await Assert.ThrowsAsync<DistributedApplicationException>(
            () => ExecutePipelineAsync(app)).DefaultTimeout();
 
        Assert.Contains("'orphan'", ex.Message);
        Assert.DoesNotContain("'frontend'", ex.Message);
    }
 
    [Fact]
    public async Task PublishPrereq_WithConsumedBuildOnlyContainer_DoesNotThrow()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.PublishPrereq);
 
        var source = AddBuildOnlyContainer(builder, "frontend");
        builder.AddResource(new TestContainerFilesDestinationResource("api"))
            .PublishWithContainerFiles(source, "/app/wwwroot");
 
        using var app = builder.Build();
 
        await ExecutePipelineAsync(app).DefaultTimeout();
    }
 
    [Fact]
    public async Task PublishPrereq_WithValidationDisabled_DoesNotThrow()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.PublishPrereq);
 
        PipelineStep? validationStep = null;
        builder.Pipeline.DisableBuildOnlyContainerValidation();
        builder.Pipeline.AddPipelineConfiguration(context =>
        {
            validationStep = context.Steps.Single(step => step.Name == DistributedApplicationPipeline.ValidateBuildOnlyContainerReferencesStepName);
            return Task.CompletedTask;
        });
        AddBuildOnlyContainer(builder, "frontend");
 
        using var app = builder.Build();
 
        await ExecutePipelineAsync(app).DefaultTimeout();
 
        Assert.NotNull(validationStep);
        Assert.Empty(validationStep.RequiredBySteps);
    }
 
    private static IResourceBuilder<TestContainerFilesSourceResource> AddBuildOnlyContainer(
        IDistributedApplicationBuilder builder,
        string name)
    {
        return builder.AddResource(new TestContainerFilesSourceResource(name))
            .WithAnnotation(new DockerfileBuildAnnotation("context", "Dockerfile", null) { HasEntrypoint = false })
            .WithAnnotation(new ContainerFilesSourceAnnotation { SourcePath = "/app/dist" });
    }
 
    private static Task ExecutePipelineAsync(DistributedApplication app)
    {
        var pipeline = app.Services.GetRequiredService<IDistributedApplicationPipeline>();
        var context = new PipelineContext(
            app.Services.GetRequiredService<DistributedApplicationModel>(),
            app.Services.GetRequiredService<DistributedApplicationExecutionContext>(),
            app.Services,
            app.Services.GetRequiredService<ILogger<BuildOnlyContainerValidationTests>>(),
            CancellationToken.None);
 
        return pipeline.ExecuteAsync(context);
    }
 
    private sealed class TestContainerFilesSourceResource(string name) : ContainerResource(name), IResourceWithContainerFiles
    {
    }
 
    private sealed class TestContainerFilesDestinationResource(string name) : ContainerResource(name), IContainerFilesDestinationResource
    {
    }
}