File: Deployment\DeployPipelineIntegrationTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Radius.Tests\Aspire.Hosting.Radius.Tests.csproj (Aspire.Hosting.Radius.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.ApplicationModel;
using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Radius.Publishing;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Radius.Tests.Deployment;
 
public class DeployPipelineIntegrationTests
{
    [Fact]
    public void DeployStep_IsRegisteredInPipeline()
    {
        // Arrange
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("myenv");
        builder.AddContainer("api", "myapp/api", "latest");
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        // Act
        var envResource = model.Resources.OfType<RadiusEnvironmentResource>().First();
        var pipelineAnnotations = envResource.Annotations.OfType<PipelineStepAnnotation>().ToList();
 
        // Assert — the multi-step PipelineStepAnnotation registered in the resource's
        // constructor expands to publish + deploy + prepare steps. We just verify one
        // annotation exists; the steps themselves are inspected in DeployStep_* tests.
        Assert.Single(pipelineAnnotations);
    }
 
    [Fact]
    public void DeployStep_DependsOnPublishStepOnly_NotPush()
    {
        // Arrange
        var environment = new RadiusEnvironmentResource("testenv");
        var step = new RadiusDeploymentPipelineStep(environment);
 
        // Act
        var pipelineStep = step.CreatePipelineStep();
 
        // Assert
        Assert.Contains("publish-radius-testenv", pipelineStep.DependsOnSteps);
        Assert.Contains("deploy-prereq", pipelineStep.DependsOnSteps);
 
        // Must NOT depend on any push step (e.g. "push", "push-prereq", "push-<resource>") — this
        // supports kind clusters without a container registry. Use the predicate overload so a
        // dependency like "push-api" can't slip past an exact-match check.
        Assert.DoesNotContain(pipelineStep.DependsOnSteps, s => s.Contains("push", StringComparison.OrdinalIgnoreCase));
    }
 
    [Fact]
    public void RadDeployCommand_SynthesizedCorrectly_WithFullBicepPath()
    {
        // Arrange
        var environment = new RadiusEnvironmentResource("myenv");
 
        // Verify the step creates a pipeline step with the correct name format
        var step = new RadiusDeploymentPipelineStep(environment);
        var pipelineStep = step.CreatePipelineStep();
 
        // Assert — step name encodes the environment name
        Assert.Equal("deploy-radius-myenv", pipelineStep.Name);
        Assert.Contains("rad CLI", pipelineStep.Description);
    }
 
    [Fact]
    public void DeployStep_StreamsOutputViaLoggerAndReportingStep()
    {
        // Verify the deployment step is configured to capture stdout/stderr.
        // The actual forwarding happens via process.OutputDataReceived and ErrorDataReceived
        // events in ExecuteAsync, which log via ILogger and context.ReportingStep.
        // We verify this indirectly by checking the step is configured with an async Action
        // that accepts a PipelineStepContext (which provides the ReportingStep for output).
        var environment = new RadiusEnvironmentResource("testenv");
        var step = new RadiusDeploymentPipelineStep(environment);
        var pipelineStep = step.CreatePipelineStep();
 
        // The step has an Action (ExecuteAsync) that processes output
        Assert.NotNull(pipelineStep.Action);
        Assert.Equal("deploy-radius-testenv", pipelineStep.Name);
    }
 
    [Fact]
    public void DeployStep_CommandUsesRadDeployWithBicepFile()
    {
        // The deploy step runs `rad deploy "app.bicep"` against the output directory.
        // Verify the step description references the rad CLI and environment name.
        var environment = new RadiusEnvironmentResource("production");
        var step = new RadiusDeploymentPipelineStep(environment);
        var pipelineStep = step.CreatePipelineStep();
 
        Assert.Contains("rad CLI", pipelineStep.Description);
        Assert.Contains("production", pipelineStep.Description);
    }
 
    [Fact]
    public void DeployStep_HasCorrectDependencyChain()
    {
        // Arrange
        var environment = new RadiusEnvironmentResource("staging");
        var step = new RadiusDeploymentPipelineStep(environment);
 
        // Act
        var pipelineStep = step.CreatePipelineStep();
 
        // Assert — Deploy step depends on:
        // 1. publish-radius-{name} (Bicep must be generated first)
        // 2. deploy-prereq (standard deploy prerequisite)
        Assert.Contains("publish-radius-staging", pipelineStep.DependsOnSteps);
        Assert.Contains("deploy-prereq", pipelineStep.DependsOnSteps);
 
        // And is required by the "deploy" well-known step
        Assert.Contains("deploy", pipelineStep.RequiredBySteps);
    }
 
    [Fact]
    public void BicepGenerationCalledBeforeRadDeploy_ByDependencyOrdering()
    {
        // Arrange
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        var env = builder.AddRadiusEnvironment("myenv");
        builder.AddContainer("api", "myapp/api", "latest");
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var envResource = model.Resources.OfType<RadiusEnvironmentResource>().First();
 
        // Verify we can resolve the publish and deploy steps from annotations
        var annotations = envResource.Annotations.OfType<PipelineStepAnnotation>().ToList();
 
        // The deploy step should exist and depend on the publish step
        // We verify this by checking the step properties directly
        var deployStep = new RadiusDeploymentPipelineStep(envResource);
        var step = deployStep.CreatePipelineStep();
 
        // Assert — the deploy step depends on publish-radius-myenv
        Assert.Contains("publish-radius-myenv", step.DependsOnSteps);
 
        // The publish step (publish-radius-myenv) is RequiredBy "publish"
        // The deploy step depends on publish-radius-myenv
        // This means Bicep generation MUST run before rad deploy
    }
 
    [Fact]
    public void MultipleEnvironments_EachGetSeparateDeployStep()
    {
        // Arrange
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("dev");
        builder.AddRadiusEnvironment("staging");
        builder.AddContainer("api", "myapp/api", "latest");
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        // Act
        var environments = model.Resources.OfType<RadiusEnvironmentResource>().ToList();
 
        // Assert — each environment should have its own pipeline step annotations
        Assert.Equal(2, environments.Count);
 
        foreach (var envResource in environments)
        {
            var annotations = envResource.Annotations.OfType<PipelineStepAnnotation>().ToList();
            Assert.Single(annotations);
        }
    }
}