File: DeploymentE2E\DeployEndToEndTests.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.Radius.Publishing;
using Aspire.Hosting.Utils;
using Aspire.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Radius.Tests.DeploymentE2E;
 
/// <summary>
/// End-to-end deployment tests that require a running kind/minikube cluster
/// with Radius installed and the <c>rad</c> CLI on PATH. Marked
/// <see cref="OuterloopTestAttribute"/> so they only run in the outerloop CI
/// workflow (not on every PR).
/// </summary>
public class DeployEndToEndTests
{
    [Fact]
    [OuterloopTest("Requires the rad CLI and a Radius-enabled Kubernetes cluster")]
    public async Task Deploy_SimpleApp_GeneratesBicepAndInvokesRad()
    {
        // Hard prerequisite: rad CLI must be on PATH. Skip with a visible reason rather than
        // silently returning, so an outerloop run that "passes" can never mean "did nothing".
        var radAvailable = await RadiusDeploymentPipelineStep.DetectRadCliAsync();
        Assert.SkipUnless(radAvailable, "rad CLI not available on PATH");
 
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("e2e");
        var redis = builder.AddRedis("cache");
        builder.AddContainer("api", "myapp/api", "latest")
            .WithReference(redis);
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var radiusEnv = model.Resources.OfType<RadiusEnvironmentResource>().First();
 
        RadiusTestHelper.AttachDeploymentTargets(radiusEnv, model);
 
        var context = new RadiusBicepPublishingContext(radiusEnv);
        var bicep = context.GenerateBicep(model);
 
        Assert.Contains("extension radius", bicep);
        Assert.Contains("Radius.Core/environments", bicep);
        Assert.Contains("Radius.Compute/containers", bicep);
        Assert.Contains("connections", bicep);
    }
 
    [Fact]
    [OuterloopTest("Requires the rad CLI and a Radius-enabled Kubernetes cluster")]
    public async Task Deploy_GeneratedBicep_ContainsCorrectResourceReferences()
    {
        var radAvailable = await RadiusDeploymentPipelineStep.DetectRadCliAsync();
        Assert.SkipUnless(radAvailable, "rad CLI not available on PATH");
 
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("e2e");
        var redis = builder.AddRedis("cache");
        var sql = builder.AddSqlServer("sqlserver").AddDatabase("appdb");
        builder.AddContainer("api", "myapp/api", "latest")
            .WithReference(redis)
            .WithReference(sql);
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var radiusEnv = model.Resources.OfType<RadiusEnvironmentResource>().First();
        RadiusTestHelper.AttachDeploymentTargets(radiusEnv, model);
        var context = new RadiusBicepPublishingContext(radiusEnv);
        var bicep = context.GenerateBicep(model);
 
        // Secrets must flow through valueless Bicep `param`s (resolved at deploy time via
        // `rad deploy --parameters`), never embedded as literals. The word "password"
        // legitimately appears as a parameter *name* (e.g. `param sqlserver_password string`)
        // and as an environment-variable reference to that param — that is the secure shape.
        // Assert the invariant precisely rather than banning the substring "password":
        //   * at least one secret is surfaced as a valueless `param ... string` (no default), and
        //   * no secret param carries a hardcoded literal default value.
        Assert.Matches(@"(?im)^\s*param\s+\w*password\w*\s+string\s*$", bicep);
        Assert.DoesNotMatch(@"(?i)param\s+\w*password\w*\s+string\s*=", bicep);
        Assert.Contains(".id", bicep);
    }
 
    [Fact]
    [OuterloopTest("Requires the rad CLI to be installed")]
    public async Task Deploy_RadCliAvailability_Detected()
    {
        var radAvailable = await RadiusDeploymentPipelineStep.DetectRadCliAsync();
        Assert.SkipUnless(radAvailable, "rad CLI not available on PATH");
 
        // If we get here, rad was detected successfully.
        Assert.True(radAvailable);
    }
 
    [Fact]
    [OuterloopTest("Requires the rad CLI to be installed")]
    public async Task Deploy_CleanupViaRadDelete_Supported()
    {
        var radAvailable = await RadiusDeploymentPipelineStep.DetectRadCliAsync();
        Assert.SkipUnless(radAvailable, "rad CLI not available on PATH");
 
        // Verify the deploy step exists and has proper dependencies for orchestration.
        var environment = new RadiusEnvironmentResource("cleanup-test");
        var step = new RadiusDeploymentPipelineStep(environment);
        var pipelineStep = step.CreatePipelineStep();
 
        Assert.Equal("deploy-radius-cleanup-test", pipelineStep.Name);
        Assert.Contains("publish-radius-cleanup-test", pipelineStep.DependsOnSteps);
    }
}