File: InnerLoop\MultiEnvironmentTests.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.
 
using System.Runtime.CompilerServices;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Radius.Tests.InnerLoop;
 
public class MultiEnvironmentTests
{
    [Fact]
    public void MultipleRadiusEnvironments_CanCoexist()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("env1").WithNamespace("ns1");
        builder.AddRadiusEnvironment("env2").WithNamespace("ns2");
 
        using var app = builder.Build();
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environments = model.Resources.OfType<RadiusEnvironmentResource>().ToArray();
        Assert.Equal(2, environments.Length);
        Assert.Contains(environments, e => e.Namespace == "ns1");
        Assert.Contains(environments, e => e.Namespace == "ns2");
    }
 
    [Fact]
    public async Task UntargetedResources_WithMultipleEnvironments_FailFastWithClearError()
    {
        // A2: With multiple compute environments, the core ValidateComputeEnvironments pipeline
        // step rejects untargeted resources with a clear error directing the user to
        // WithComputeEnvironment. The Radius prepare step DependsOn that validation step,
        // so this error surfaces before any Radius-specific code runs (no silent first-env
        // claim, no duplicate annotations across environments).
        // See DistributedApplicationPipeline.ValidateComputeEnvironmentBindings.
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        builder.AddRadiusEnvironment("env1").WithNamespace("ns1");
        builder.AddRadiusEnvironment("env2").WithNamespace("ns2");
        builder.AddContainer("api", "myapp/api:latest");
 
        using var app = builder.Build();
 
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(
            () => ExecuteBeforeStartHooksAsync(app, default));
 
        Assert.Contains("'api'", ex.Message);
        Assert.Contains("WithComputeEnvironment", ex.Message);
    }
 
    [Fact]
    public async Task ExplicitlyTargetedResources_OnlyAnnotatedByOwningEnvironment()
    {
        // With multiple Radius environments and a resource explicitly targeted to one,
        // only that environment's prepare step claims the resource — no duplicate annotation
        // from the sibling environment.
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        var env1 = builder.AddRadiusEnvironment("env1").WithNamespace("ns1");
        var env2 = builder.AddRadiusEnvironment("env2").WithNamespace("ns2");
        builder.AddContainer("api", "myapp/api:latest").WithComputeEnvironment(env1);
 
        using var app = builder.Build();
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
        var apiResource = model.Resources.First(r => r.Name == "api");
        var annotations = apiResource.Annotations.OfType<DeploymentTargetAnnotation>().ToArray();
 
        Assert.Single(annotations);
        Assert.Same(env1.Resource, annotations[0].ComputeEnvironment);
    }
 
    [Fact]
    public async Task DifferentNamespaces_DontCauseNamingCollisions()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        var env1 = builder.AddRadiusEnvironment("env1").WithNamespace("ns1");
        builder.AddRadiusEnvironment("env2").WithNamespace("ns2");
        // Target explicitly to env1 — without explicit targeting, multi-env models fail fast
        // (see UntargetedResources_WithMultipleEnvironments_FailFastWithClearError).
        builder.AddContainer("api", "myapp/api:latest").WithComputeEnvironment(env1);
 
        using var app = builder.Build();
        await ExecuteBeforeStartHooksAsync(app, default);
 
        var model = app.Services.GetRequiredService<DistributedApplicationModel>();
 
        var environments = model.Resources.OfType<RadiusEnvironmentResource>().ToArray();
        Assert.Equal("ns1", environments[0].Namespace);
        Assert.Equal("ns2", environments[1].Namespace);
 
        var allResourceNames = model.Resources.Select(r => r.Name).ToArray();
        Assert.Equal(allResourceNames.Length, allResourceNames.Distinct().Count());
 
        var apiResource = model.Resources.First(r => r.Name == "api");
        var annotations = apiResource.Annotations.OfType<DeploymentTargetAnnotation>().ToArray();
        Assert.Single(annotations);
        Assert.Same(env1.Resource, annotations[0].ComputeEnvironment);
    }
 
    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")]
    private static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken);
}