File: WaitFailures.cs
Web Access
Project: src\playground\Testing\Testing.Tests\Testing.Tests.csproj (Testing.Tests)
using Xunit;
using Aspire.Hosting.Testing;
using Aspire.Hosting;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Aspire.Hosting.ApplicationModel;
 
namespace Testing.Tests;
 
// These tests cover various scenarios in which a wait operation fails to see how helpful the
// resulting errors / logs are
public class WaitFailures
{
    private static CancellationTokenSource DefaultCancellationTokenSource()
    {
        var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
        cts.CancelAfter(TimeSpan.FromMinutes(1));
        return cts;
    }
 
    public class WaitForExplicitStartResources
    {
        [Fact]
        public async Task Container()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx", "")
                .WithExplicitStart();
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, cancellationToken: cts.Token);
        }
 
        [Fact]
        public async Task Executable()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var pwsh = builder.AddExecutable("pwsh", "pwsh", "")
                .WithExplicitStart();
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            await app.ResourceNotifications.WaitForResourceAsync(pwsh.Resource.Name, cancellationToken: cts.Token);
        }
    }
 
    public class WaitForResource
    {
        [Fact]
        public async Task WaitForResourceThatNeverCompletes()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx");
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            _ = Task.Run(async () =>
            {
                await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, cancellationToken: cts.Token);
                cts.Cancel();
            });
 
            await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, KnownResourceStates.TerminalStates, cancellationToken: cts.Token);
        }
 
        [Fact]
        public async Task WaitForResourceThatNeverHitsState()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx");
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            cts.CancelAfter(TimeSpan.FromMilliseconds(100));
            await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, "StateThatIsNeverUsed", cts.Token);
        }
 
        [Fact]
        public async Task WaitForResourceThatNeverHitsStates()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx");
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            cts.CancelAfter(TimeSpan.FromMilliseconds(100));
            await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, ["States", "That", "Are", "Never", "Used"], cts.Token);
        }
 
        [Fact]
        public async Task WaitForResourceThatNeverHitsPredicate()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx");
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            cts.CancelAfter(TimeSpan.FromMilliseconds(100));
            await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, x => false, cts.Token);
        }
    }
 
    public class WaitForResourceHealthyAsync
    {
        [Fact]
        public async Task WaitForHealthyOnResourceThatNeverStarts()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx")
                .WithExplicitStart();
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            cts.CancelAfter(TimeSpan.FromMilliseconds(100));
            await app.ResourceNotifications.WaitForResourceHealthyAsync(nginx.Resource.Name, cts.Token);
        }
 
        [Fact]
        public async Task WaitForHealthyOnResourceThatStartsButNeverGoesHealthy()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx")
                .WithHttpEndpoint(targetPort: 80)
                .WithHttpEndpoint(name: "fake", targetPort: 1234)
                .WithHttpHealthCheck("/does-not-exist")
                .WithHttpHealthCheck("/throws-exception", endpointName: "fake");
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            _ = Task.Run(async () =>
            {
                await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, x => x.Snapshot.HealthReports.All(x => x.Status.HasValue), cts.Token);
                cts.Cancel();
            });
            await app.ResourceNotifications.WaitForResourceHealthyAsync(nginx.Resource.Name, cts.Token);
        }
 
        [Fact]
        public async Task WaitForHealthyOnResourceThatGoesHealthyButNeverCompletesResourceReady()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var nginx = builder.AddContainer("nginx", "nginx")
                .OnResourceReady((_, _, token) => Task.Delay(Timeout.Infinite, token));
 
            await using var app = builder.Build();
            await app.StartAsync(cts.Token);
 
            _ = Task.Run(async () =>
            {
                await app.ResourceNotifications.WaitForResourceAsync(nginx.Resource.Name, x => x.Snapshot.HealthStatus == HealthStatus.Healthy, cts.Token);
                cts.Cancel();
            });
 
            await app.ResourceNotifications.WaitForResourceHealthyAsync(nginx.Resource.Name, cts.Token);
        }
    }
 
    public class WaitForDependencies
    {
        [Fact]
        public async Task DependencyNeverStarts()
        {
            var cts = DefaultCancellationTokenSource();
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var dependency = builder.AddContainer("dependency", "nginx")
                .WithExplicitStart();
 
            var consumer = builder.AddContainer("consumer", "nginx")
                .WaitForStart(dependency);
 
            await using var app = builder.Build();
 
            // Chicken and egg problem - need `WaitFor` to test `WaitForDependenciesAsync`
            // but adding `WaitFor` causes `StartAsync` to block until all resource dependencies are started
            // But I'm trying to test what happens when the waits are not complete.
            _ = app.StartAsync(cts.Token);
 
            cts.CancelAfter(TimeSpan.FromMilliseconds(100));
            await app.ResourceNotifications.WaitForDependenciesAsync(consumer.Resource, cts.Token);
        }
 
        [Fact]
        public async Task DependencyNeverGoesHealthy()
        {
            var cts = DefaultCancellationTokenSource();
 
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var dependency = builder.AddContainer("dependency", "nginx")
                .WithHttpEndpoint(targetPort: 80)
                .WithHttpHealthCheck("/does-not-exist");
 
            var consumer = builder.AddContainer("consumer", "nginx")
                .WaitFor(dependency);
 
            await using var app = builder.Build();
            _ = app.StartAsync(cts.Token);
 
            _ = Task.Run(async () =>
            {
                await app.ResourceNotifications.WaitForResourceAsync(dependency.Resource.Name, cancellationToken: cts.Token);
                cts.Cancel();
            });
 
            await app.ResourceNotifications.WaitForDependenciesAsync(consumer.Resource, cts.Token);
        }
 
        [Fact]
        public async Task DependencyNeverCompletes()
        {
            var cts = DefaultCancellationTokenSource();
 
            await using var builder = DistributedApplicationTestingBuilder.Create();
 
            var dependency = builder.AddContainer("dependency", "nginx");
 
            var consumer = builder.AddContainer("consumer", "nginx")
                .WaitForCompletion(dependency);
 
            await using var app = builder.Build();
            _ = app.StartAsync(cts.Token);
 
            _ = Task.Run(async () =>
            {
                await app.ResourceNotifications.WaitForResourceAsync(dependency.Resource.Name, cancellationToken: cts.Token);
                cts.Cancel();
            });
 
            await app.ResourceNotifications.WaitForDependenciesAsync(consumer.Resource, cancellationToken: cts.Token);
        }
    }
}