File: DurableTaskResourceExtensionsTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Azure.Tests\Aspire.Hosting.Azure.Tests.csproj (Aspire.Hosting.Azure.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 ASPIREDURABLETASK001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
 
namespace Aspire.Hosting.Azure.Tests;
 
public class DurableTaskResourceExtensionsTests
{
    [Fact]
    public async Task AddDurableTaskScheduler_RunAsEmulator_ResolvedConnectionString()
    {
        string expectedConnectionString = "Endpoint=http://localhost:8080;Authentication=None";
 
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder
            .AddDurableTaskScheduler("dts")
            .RunAsEmulator(e =>
            {
                e.WithEndpoint("grpc", e => e.AllocatedEndpoint = new(e, "localhost", 8080));
                e.WithEndpoint("http", e => e.AllocatedEndpoint = new(e, "localhost", 8081));
                e.WithEndpoint("dashboard", e => e.AllocatedEndpoint = new(e, "localhost", 8082));
            });
 
        var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default);
 
        Assert.Equal(expectedConnectionString, connectionString);
    }
 
    [Fact]
    public async Task AddDurableTaskScheduler_RunAsExisting_ResolvedConnectionString()
    {
        string expectedConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure";
 
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder
            .AddDurableTaskScheduler("dts")
            .RunAsExisting(expectedConnectionString);
 
        var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default);
 
        Assert.Equal(expectedConnectionString, connectionString);
    }
 
    [Fact]
    public async Task AddDurableTaskScheduler_RunAsExisting_ResolvedConnectionStringParameter()
    {
        string expectedConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure";
 
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var connectionStringParameter = builder.AddParameter("dts-connection-string", expectedConnectionString);
 
        var dts = builder
            .AddDurableTaskScheduler("dts")
            .RunAsExisting(connectionStringParameter);
 
        var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default);
 
        Assert.Equal(expectedConnectionString, connectionString);
    }
 
    [Theory]
    [InlineData(null, "mytaskhub")]
    [InlineData("myrealtaskhub", "myrealtaskhub")]
    public async Task AddDurableTaskHub_RunAsExisting_ResolvedConnectionStringParameter(string? taskHubName, string expectedTaskHubName)
    {
        string dtsConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure";
        string expectedConnectionString = $"{dtsConnectionString};TaskHub={expectedTaskHubName}";
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder
            .AddDurableTaskScheduler("dts")
            .RunAsExisting(dtsConnectionString);
 
        var taskHub = dts.AddTaskHub("mytaskhub");
        
        if (taskHubName is not null)
        {
            taskHub = taskHub.WithTaskHubName(taskHubName);   
        }
 
        var connectionString = await taskHub.Resource.ConnectionStringExpression.GetValueAsync(default);
 
        Assert.Equal(expectedConnectionString, connectionString);
    }
 
    [Fact]
    public void AddDurableTaskScheduler_IsExcludedFromPublishingManifest()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts");
 
        Assert.True(dts.Resource.TryGetAnnotationsOfType<ManifestPublishingCallbackAnnotation>(out var manifestAnnotations));
        var annotation = Assert.Single(manifestAnnotations);
        Assert.Equal(ManifestPublishingCallbackAnnotation.Ignore, annotation);
    }
 
    [Fact]
    public void AddDurableTaskHub_IsExcludedFromPublishingManifest()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts").RunAsExisting("Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure");
        var taskHub = dts.AddTaskHub("hub");
 
        Assert.True(taskHub.Resource.TryGetAnnotationsOfType<ManifestPublishingCallbackAnnotation>(out var manifestAnnotations));
        var annotation = Assert.Single(manifestAnnotations);
        Assert.Equal(ManifestPublishingCallbackAnnotation.Ignore, annotation);
    }
 
    [Fact]
    public void RunAsExisting_InPublishMode_DoesNotApplyConnectionStringAnnotation()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var dts = builder.AddDurableTaskScheduler("dts")
            .RunAsExisting("Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure");
 
        Assert.False(dts.ApplicationBuilder.ExecutionContext.IsRunMode);
        Assert.True(dts.ApplicationBuilder.ExecutionContext.IsPublishMode);
 
        var ex = Assert.Throws<InvalidOperationException>(() => _ = dts.Resource.ConnectionStringExpression);
        Assert.Contains("Unable to resolve the Durable Task Scheduler connection string", ex.Message);
    }
 
    [Fact]
    public void RunAsEmulator_InPublishMode_IsNoOp()
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
 
        var dts = builder.AddDurableTaskScheduler("dts")
            .RunAsEmulator();
 
        Assert.False(dts.Resource.IsEmulator);
        Assert.DoesNotContain(dts.Resource.Annotations, a => a is EmulatorResourceAnnotation);
 
        Assert.Throws<InvalidOperationException>(() => _ = dts.Resource.ConnectionStringExpression);
    }
 
    [Fact]
    public void RunAsEmulator_AddsEmulatorAnnotationContainerImageAndEndpoints()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts")
            .RunAsEmulator();
 
        Assert.True(dts.Resource.IsEmulator);
 
        var emulatorAnnotation = dts.Resource.Annotations.OfType<EmulatorResourceAnnotation>().SingleOrDefault();
        Assert.NotNull(emulatorAnnotation);
 
        var containerImageAnnotation = dts.Resource.Annotations.OfType<ContainerImageAnnotation>().SingleOrDefault();
        Assert.NotNull(containerImageAnnotation);
        Assert.Equal("mcr.microsoft.com", containerImageAnnotation.Registry);
        Assert.Equal("dts/dts-emulator", containerImageAnnotation.Image);
        Assert.Equal("latest", containerImageAnnotation.Tag);
 
        var endpointAnnotations = dts.Resource.Annotations.OfType<EndpointAnnotation>().ToList();
 
        var grpc = endpointAnnotations.SingleOrDefault(e => e.Name == "grpc");
        Assert.NotNull(grpc);
        Assert.Equal(8080, grpc.TargetPort);
 
        var http = endpointAnnotations.SingleOrDefault(e => e.Name == "http");
        Assert.NotNull(http);
        Assert.Equal(8081, http.TargetPort);
        Assert.Equal("http", http.UriScheme);
 
        var dashboard = endpointAnnotations.SingleOrDefault(e => e.Name == "dashboard");
        Assert.NotNull(dashboard);
        Assert.Equal(8082, dashboard.TargetPort);
        Assert.Equal("http", dashboard.UriScheme);
    }
 
    [Fact]
    public async Task RunAsEmulator_SetsSingleDtsTaskHubNamesEnvironmentVariable()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts").RunAsEmulator();
 
        _ = dts.AddTaskHub("hub1").WithTaskHubName("realhub1");
 
        var env = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance);
 
        Assert.Equal("realhub1", env["DTS_TASK_HUB_NAMES"]);
    }
 
    [Fact]
    public async Task RunAsEmulator_SetsMultipleDtsTaskHubNamesEnvironmentVariable()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts").RunAsEmulator();
 
        _ = dts.AddTaskHub("hub1");
        _ = dts.AddTaskHub("hub2").WithTaskHubName("realhub2");
 
        var env = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance);
 
        Assert.Equal("hub1, realhub2", env["DTS_TASK_HUB_NAMES"]);
    }
 
    [Fact]
    public async Task RunAsEmulator_DtsTaskHubNamesOnlyIncludesHubsForSameScheduler()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts1 = builder.AddDurableTaskScheduler("dts1").RunAsEmulator();
        var dts2 = builder.AddDurableTaskScheduler("dts2").RunAsEmulator();
 
        _ = dts1.AddTaskHub("hub1");
        _ = dts2.AddTaskHub("hub2");
 
        var env1 = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts1.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance);
        var env2 = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts2.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance);
 
        Assert.Equal("hub1", env1["DTS_TASK_HUB_NAMES"]);
        Assert.Equal("hub2", env2["DTS_TASK_HUB_NAMES"]);
    }
 
    [Fact]
    public async Task WithTaskHubName_Parameter_ResolvedConnectionString()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        const string dtsConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure";
        var hubNameParameter = builder.AddParameter("hub-name", "parameterHub");
 
        var dts = builder.AddDurableTaskScheduler("dts")
            .RunAsExisting(dtsConnectionString);
 
        var hub = dts.AddTaskHub("ignored").WithTaskHubName(hubNameParameter);
 
        var connectionString = await hub.Resource.ConnectionStringExpression.GetValueAsync(default);
        Assert.Equal($"{dtsConnectionString};TaskHub=parameterHub", connectionString);
    }
 
    [Fact]
    public void DurableTaskSchedulerResource_WithoutEmulatorOrExistingConnectionString_Throws()
    {
        using var builder = TestDistributedApplicationBuilder.Create();
 
        var dts = builder.AddDurableTaskScheduler("dts");
 
        var ex = Assert.Throws<InvalidOperationException>(() => _ = dts.Resource.ConnectionStringExpression);
        Assert.Contains("Unable to resolve the Durable Task Scheduler connection string", ex.Message);
    }
}