diff --git a/tests/Aspire.Hosting.MongoDB.Tests/Aspire.Hosting.MongoDB.Tests.csproj b/tests/Aspire.Hosting.MongoDB.Tests/Aspire.Hosting.MongoDB.Tests.csproj index faaeea98eb9..d6469b9e1c9 100644 --- a/tests/Aspire.Hosting.MongoDB.Tests/Aspire.Hosting.MongoDB.Tests.csproj +++ b/tests/Aspire.Hosting.MongoDB.Tests/Aspire.Hosting.MongoDB.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj b/tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj index 7043e4c8011..1e67cf1ac68 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresReplCommandTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresReplCommandTests.cs index fc03b618cbe..377f0fe5127 100644 --- a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresReplCommandTests.cs +++ b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresReplCommandTests.cs @@ -6,6 +6,8 @@ using Aspire.Hosting.Tests; using Aspire.Hosting.Utils; using Aspire.TestUtilities; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.Time.Testing; #pragma warning disable ASPIRETERMINAL001 @@ -72,6 +74,28 @@ public async Task ReplRejectsMissingTargetPort() Assert.Equal("The PostgreSQL REPL port is not available.", exception.Message); } + [Fact] + public async Task ReplReadinessWaitCoversColdImagePull() + { + // A cold postgres image pull took ~55s in CI before the container could even start, so the + // readiness wait must still be alive well past that point. + var coldImagePullAndStartup = TimeSpan.FromMinutes(2); + + using var builder = TestDistributedApplicationBuilder.Create(); + var postgres = builder.AddPostgres("postgres"); + await using var app = builder.Build(); + var timeProvider = new FakeTimeProvider(); + + var waitTask = WaitForReplResourceHealthyAsync(app, postgres.Resource.Name, timeProvider); + timeProvider.Advance(coldImagePullAndStartup); + Assert.False(waitTask.IsCompleted); + + // The wait only observes the resource once it reaches a terminal state, so the wait failing for + // that reason (rather than being cancelled) shows the budget had not run out. + await app.ResourceNotifications.PublishUpdateAsync(postgres.Resource, s => s with { State = KnownResourceStates.FailedToStart }); + await Assert.ThrowsAsync(() => waitTask).DefaultTimeout(); + } + [Theory] [InlineData(null)] [InlineData(5433)] diff --git a/tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj b/tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj index c7308f132cc..c0ff10d0f6e 100644 --- a/tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj +++ b/tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj @@ -2,6 +2,7 @@ + diff --git a/tests/Aspire.Hosting.SqlServer.Tests/Aspire.Hosting.SqlServer.Tests.csproj b/tests/Aspire.Hosting.SqlServer.Tests/Aspire.Hosting.SqlServer.Tests.csproj index 2a187782b4c..4a4e7c2e6b1 100644 --- a/tests/Aspire.Hosting.SqlServer.Tests/Aspire.Hosting.SqlServer.Tests.csproj +++ b/tests/Aspire.Hosting.SqlServer.Tests/Aspire.Hosting.SqlServer.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/tests/Shared/ContainerReplCommandTestBase.cs b/tests/Shared/ContainerReplCommandTestBase.cs index 001755a1b35..73942f65f72 100644 --- a/tests/Shared/ContainerReplCommandTestBase.cs +++ b/tests/Shared/ContainerReplCommandTestBase.cs @@ -5,6 +5,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Tests.Utils; using Aspire.Hosting.Utils; +using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; @@ -160,10 +161,7 @@ protected static async Task VerifyReplAsync( await app.StartAsync(startup.Token); } - using (var readiness = new CancellationTokenSource(TimeSpan.FromMinutes(1))) - { - await app.ResourceNotifications.WaitForResourceHealthyAsync(resource.Name, readiness.Token); - } + await WaitForReplResourceHealthyAsync(app, resource.Name, TimeProvider.System); using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(1)); await using var terminal = await TerminalCommandTestHelpers.ExecuteTerminalCommandAsync(app, resource, "repl", cancellation.Token); @@ -187,4 +185,13 @@ protected static async Task VerifyReplAsync( throw new TimeoutException($"REPL did not produce the expected output. Terminal screen:{Environment.NewLine}{terminal.GetScreenText()}", exception); } } + + protected static async Task WaitForReplResourceHealthyAsync(DistributedApplication app, string resourceName, TimeProvider timeProvider) + { + // StartAsync returns before DCP pulls the container image, so this wait also has to cover a + // cold image pull. A cold postgres pull alone took ~55s in CI, which exhausted the previous + // 1-minute budget before the container could become healthy. + using var readiness = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan, timeProvider); + await app.ResourceNotifications.WaitForResourceHealthyAsync(resourceName, readiness.Token); + } }