diff --git a/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs b/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs index 838a1210c44..bb5dbf2f59e 100644 --- a/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs +++ b/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs @@ -177,15 +177,14 @@ public async Task ResourceEventsForContainersFireForSpecificResources() var beforeResourceStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using var builder = TestDistributedApplicationBuilder.Create(); - var redis = builder.AddRedis("redis"); - - builder.Eventing.Subscribe(redis.Resource, (e, ct) => - { - Assert.NotNull(e.Services); - Assert.NotNull(e.Resource); - beforeResourceStartedTcs.TrySetResult(); - return Task.CompletedTask; - }); + var redis = builder.AddRedis("redis") + .OnBeforeResourceStarted((_, e, _) => + { + Assert.NotNull(e.Services); + Assert.NotNull(e.Resource); + beforeResourceStartedTcs.TrySetResult(); + return Task.CompletedTask; + }); using var app = builder.Build(); await app.StartAsync().DefaultTimeout(TestConstants.DefaultOrchestratorTestTimeout); diff --git a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs index 5d74675f18a..b99d9d06272 100644 --- a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs @@ -145,15 +145,14 @@ public async Task ResourcesWithHealthCheck_StopsAndRestartsMonitoringWithResourc return HealthCheckResult.Healthy(); }); - var resource = builder.AddResource(new ParentResource("resource")) - .WithHealthCheck("healthcheck_a"); - var channel = Channel.CreateUnbounded(); - builder.Eventing.Subscribe(resource.Resource, (@event, ct) => - { - channel.Writer.TryWrite(@event); - return Task.CompletedTask; - }); + var resource = builder.AddResource(new ParentResource("resource")) + .WithHealthCheck("healthcheck_a") + .OnResourceReady((_, @event, _) => + { + channel.Writer.TryWrite(@event); + return Task.CompletedTask; + }); await using var app = await builder.BuildAsync().DefaultTimeout(); @@ -335,14 +334,14 @@ await app.ResourceNotifications.PublishUpdateAsync(resource.Resource, s => s wit public async Task ResourcesWithoutHealthCheckAnnotationsGetReadyEventFired() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); - var resource = builder.AddResource(new ParentResource("resource")); var blockAssert = new TaskCompletionSource(); - builder.Eventing.Subscribe(resource.Resource, (@event, ct) => - { - blockAssert.SetResult(@event); - return Task.CompletedTask; - }); + var resource = builder.AddResource(new ParentResource("resource")) + .OnResourceReady((_, @event, _) => + { + blockAssert.SetResult(@event); + return Task.CompletedTask; + }); using var app = builder.Build(); var pendingStart = app.StartAsync(); @@ -410,17 +409,16 @@ public async Task ResourceHealthCheckServiceDoesNotRunHealthChecksUnlessResource return checkStatus; }); - var parent = builder.AddResource(new ParentResource("parent")) - .WithHealthCheck("parent_test"); - // Handle ResourceReadyEvent and use it to control when we drop through to do our assert // on the health test being executed. var resourceReadyEventFired = new TaskCompletionSource(); - builder.Eventing.Subscribe(parent.Resource, (@event, ct) => - { - resourceReadyEventFired.SetResult(@event); - return Task.CompletedTask; - }); + var parent = builder.AddResource(new ParentResource("parent")) + .WithHealthCheck("parent_test") + .OnResourceReady((_, @event, _) => + { + resourceReadyEventFired.SetResult(@event); + return Task.CompletedTask; + }); using var app = builder.Build(); var pendingStart = app.StartAsync().DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -468,18 +466,17 @@ public async Task ResourceHealthCheckServiceOnlyRaisesResourceReadyOnce() return HealthCheckResult.Healthy(); }); - var parent = builder.AddResource(new ParentResource("parent")) - .WithHealthCheck("parent_test"); - // Handle ResourceReadyEvent and use it to control when we drop through to do our assert // on the health test being executed. var eventHits = 0; var resourceReadyEventFired = new TaskCompletionSource(); - builder.Eventing.Subscribe(parent.Resource, (@event, ct) => - { - Interlocked.Increment(ref eventHits); - return Task.CompletedTask; - }); + var parent = builder.AddResource(new ParentResource("parent")) + .WithHealthCheck("parent_test") + .OnResourceReady((_, @event, _) => + { + Interlocked.Increment(ref eventHits); + return Task.CompletedTask; + }); using var app = builder.Build(); var pendingStart = app.StartAsync().DefaultTimeout(); @@ -516,24 +513,23 @@ public async Task VerifyThatChildResourceWillBecomeHealthyOnceParentBecomesHealt using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper); builder.Services.AddHealthChecks().AddCheck("parent_test", () => HealthCheckResult.Healthy()); - var parent = builder.AddResource(new ParentResource("parent")) - .WithHealthCheck("parent_test"); var parentReady = new TaskCompletionSource(); - builder.Eventing.Subscribe(parent.Resource, (@event, ct) => - { - parentReady.SetResult(@event); - return Task.CompletedTask; - }); - - var child = builder.AddResource(new ChildResource("child", parent.Resource)); + var parent = builder.AddResource(new ParentResource("parent")) + .WithHealthCheck("parent_test") + .OnResourceReady((_, @event, _) => + { + parentReady.SetResult(@event); + return Task.CompletedTask; + }); var childReady = new TaskCompletionSource(); - builder.Eventing.Subscribe(child.Resource, (@event, ct) => - { - childReady.SetResult(@event); - return Task.CompletedTask; - }); + var child = builder.AddResource(new ChildResource("child", parent.Resource)) + .OnResourceReady((_, @event, _) => + { + childReady.SetResult(@event); + return Task.CompletedTask; + }); using var app = builder.Build(); var pendingStart = app.StartAsync(); diff --git a/tests/Aspire.Hosting.Tests/Orchestrator/ApplicationOrchestratorTests.cs b/tests/Aspire.Hosting.Tests/Orchestrator/ApplicationOrchestratorTests.cs index 013b839b6ba..cbac272013c 100644 --- a/tests/Aspire.Hosting.Tests/Orchestrator/ApplicationOrchestratorTests.cs +++ b/tests/Aspire.Hosting.Tests/Orchestrator/ApplicationOrchestratorTests.cs @@ -120,10 +120,17 @@ public async Task InitializeResourceEventPublished() var events = new DcpExecutorEvents(); var resourceNotificationService = ResourceNotificationServiceTestHelpers.Create(); - var applicationEventing = new DistributedApplicationEventing(); + var applicationEventing = builder.Eventing; var initResourceTcs = new TaskCompletionSource(); InitializeResourceEvent? initEvent = null; + resource.OnInitializeResource((_, @event, _) => + { + initEvent = @event; + initResourceTcs.SetResult(); + return Task.CompletedTask; + }); + applicationEventing.Subscribe(resource.Resource, (@event, ct) => { initEvent = @event; @@ -136,7 +143,7 @@ public async Task InitializeResourceEventPublished() await events.PublishAsync(new OnResourcesPreparedContext(CancellationToken.None)); - await initResourceTcs.Task.DefaultTimeout(); + await initResourceTcs.Task; //.DefaultTimeout(); Assert.True(initResourceTcs.Task.IsCompletedSuccessfully); Assert.NotNull(initEvent); @@ -433,7 +440,7 @@ private static ApplicationOrchestrator CreateOrchestrator( DistributedApplicationModel distributedAppModel, ResourceNotificationService notificationService, DcpExecutorEvents? dcpEvents = null, - DistributedApplicationEventing? applicationEventing = null, + IDistributedApplicationEventing? applicationEventing = null, ResourceLoggerService? resourceLoggerService = null) { var serviceProvider = new ServiceCollection().BuildServiceProvider(); diff --git a/tests/Aspire.Hosting.Tests/WaitForTests.cs b/tests/Aspire.Hosting.Tests/WaitForTests.cs index 806a2da0a3f..aa15a07ef17 100644 --- a/tests/Aspire.Hosting.Tests/WaitForTests.cs +++ b/tests/Aspire.Hosting.Tests/WaitForTests.cs @@ -531,13 +531,13 @@ public async Task WaitForObservedResultOfResourceReadyEvent() }); var resourceReadyTcs = new TaskCompletionSource(); - var dependency = builder.AddResource(new CustomResource("test")); + var dependency = builder.AddResource(new CustomResource("test")) + .OnResourceReady((_, _, _) => resourceReadyTcs.Task); + var nginx = builder.AddContainer("nginx", "mcr.microsoft.com/cbl-mariner/base/nginx", "1.22") .WithReference(dependency) .WaitFor(dependency); - builder.Eventing.Subscribe(dependency.Resource, (e, ct) => resourceReadyTcs.Task); - using var app = builder.Build(); // StartAsync will currently block until the dependency resource moves diff --git a/tests/Aspire.Hosting.Tests/WithUrlsTests.cs b/tests/Aspire.Hosting.Tests/WithUrlsTests.cs index e9e631cef6b..932d0a4464d 100644 --- a/tests/Aspire.Hosting.Tests/WithUrlsTests.cs +++ b/tests/Aspire.Hosting.Tests/WithUrlsTests.cs @@ -50,23 +50,23 @@ public async Task WithUrlsCallsCallbackAfterBeforeResourceStartedEvent() using var builder = TestDistributedApplicationBuilder.Create(); var called = false; - var projectA = builder.AddProject("projecta") - .WithUrls(c => called = true); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - // Should not be called at this point - Assert.False(called); - return Task.CompletedTask; - }); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - // Should be called by the time resource is started - Assert.True(called); - tcs.SetResult(); - return Task.CompletedTask; - }); + + builder.AddProject("projecta") + .WithUrls(c => called = true) + .OnResourceEndpointsAllocated((_, _, _) => + { + // Should not be called at this point + Assert.False(called); + return Task.CompletedTask; + }) + .OnBeforeResourceStarted((_, _, _) => + { + // Should be called by the time resource is started + Assert.True(called); + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -82,15 +82,14 @@ public async Task WithUrlsProvidesLoggerInstanceOnCallbackContextAllocated() using var builder = TestDistributedApplicationBuilder.Create(); ILogger logger = NullLogger.Instance; - var projectA = builder.AddProject("projecta") - .WithUrls(c => logger = c.Logger); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + var projectA = builder.AddProject("projecta") + .WithUrls(c => logger = c.Logger) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -137,15 +136,14 @@ public async Task WithUrlsAddsUrlAnnotations() { using var builder = TestDistributedApplicationBuilder.Create(); - var projectA = builder.AddProject("projecta") - .WithUrls(c => c.Urls.Add(new() { Url = "https://example.com", DisplayText = "Example" })); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + var projectA = builder.AddProject("projecta") + .WithUrls(c => c.Urls.Add(new() { Url = "https://example.com", DisplayText = "Example" })) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -162,11 +160,10 @@ public async Task WithUrlAddsUrlAnnotation() { using var builder = TestDistributedApplicationBuilder.Create(); - var projectA = builder.AddProject("projecta") - .WithUrl("https://example.com", "Example"); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => + var projectA = builder.AddProject("projecta") + .WithUrl("https://example.com", "Example") + .OnBeforeResourceStarted((_, _, _) => { tcs.SetResult(); return Task.CompletedTask; @@ -189,14 +186,14 @@ public async Task WithUrlInterpolatedStringAddsUrlAnnotation() var projectA = builder.AddProject("projecta") .WithHttpsEndpoint(); - projectA.WithUrl($"{projectA.Resource.GetEndpoint("https")}/test", "Example"); var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + projectA.WithUrl($"{projectA.Resource.GetEndpoint("https")}/test", "Example") + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -217,15 +214,14 @@ public async Task EndpointsResultInUrls() { using var builder = TestDistributedApplicationBuilder.Create(); - var projectA = builder.AddProject("projecta") - .WithHttpEndpoint(name: "test"); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + var projectA = builder.AddProject("projecta") + .WithHttpEndpoint(name: "test") + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -242,14 +238,13 @@ public async Task ProjectLaunchProfileRelativeLaunchUrlIsAddedToEndpointUrl() { using var builder = TestDistributedApplicationBuilder.Create(); - var projectA = builder.AddProject("projectb"); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + var projectA = builder.AddProject("projectb") + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -266,14 +261,13 @@ public async Task ProjectLaunchProfileAbsoluteLaunchUrlIsUsedAsEndpointUrl() { using var builder = TestDistributedApplicationBuilder.Create(); - var projectA = builder.AddProject("projectb", launchProfileName: "custom"); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); + var projectA = builder.AddProject("projectb", launchProfileName: "custom") + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; + }); var app = await builder.BuildAsync(); await app.StartAsync(); @@ -290,6 +284,7 @@ public async Task WithUrlForEndpointUpdatesUrlForEndpoint() { using var builder = TestDistributedApplicationBuilder.Create(); + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrlForEndpoint("test", u => @@ -297,15 +292,13 @@ public async Task WithUrlForEndpointUpdatesUrlForEndpoint() u.Url = "https://example.com"; u.DisplayText = "Link Text"; u.DisplayOrder = 1000; + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task; @@ -413,40 +406,39 @@ public async Task UrlsAreInExpectedStateForResourcesGivenTheirLifecycle() CreationTimeStamp = DateTime.UtcNow, State = KnownResourceStates.NotStarted, Properties = [] - }); - - builder.Eventing.Subscribe(custom.Resource, async (e, ct) => - { - // Mark all the endpoints on custom resource as allocated so that the URLs are initialized - if (custom.Resource.TryGetEndpoints(out var endpoints)) + }) + .OnInitializeResource(async (custom, e, ct) => { - var startingPort = 1234; - foreach (var endpoint in endpoints) + // Mark all the endpoints on custom resource as allocated so that the URLs are initialized + if (custom.TryGetEndpoints(out var endpoints)) { - endpoint.AllocatedEndpoint = new(endpoint, endpoint.TargetHost, endpoint.Port ?? endpoint.TargetPort ?? startingPort++); + var startingPort = 1234; + foreach (var endpoint in endpoints) + { + endpoint.AllocatedEndpoint = new(endpoint, endpoint.TargetHost, endpoint.Port ?? endpoint.TargetPort ?? startingPort++); + } } - } - // Publish the ResourceEndpointsAllocatedEvent for the resource - await e.Eventing.PublishAsync(new ResourceEndpointsAllocatedEvent(custom.Resource, e.Services), EventDispatchBehavior.BlockingConcurrent, ct); + // Publish the ResourceEndpointsAllocatedEvent for the resource + await e.Eventing.PublishAsync(new ResourceEndpointsAllocatedEvent(custom, e.Services), EventDispatchBehavior.BlockingConcurrent, ct); - // Publish the BeforeResourceStartedEvent for the resource - await e.Eventing.PublishAsync(new BeforeResourceStartedEvent(custom.Resource, e.Services), EventDispatchBehavior.BlockingSequential, ct); + // Publish the BeforeResourceStartedEvent for the resource + await e.Eventing.PublishAsync(new BeforeResourceStartedEvent(custom, e.Services), EventDispatchBehavior.BlockingSequential, ct); - // Mark all the endpoint URLs as active (this makes them visible in the dashboard) - await e.Notifications.PublishUpdateAsync(custom.Resource, s => s with - { - Urls = [.. s.Urls.Select(u => u with { IsInactive = false })] - }); - - // Move resource to the running state - await e.Services.GetRequiredService() - .PublishUpdateAsync(e.Resource, s => s with + // Mark all the endpoint URLs as active (this makes them visible in the dashboard) + await e.Notifications.PublishUpdateAsync(custom, s => s with { - StartTimeStamp = DateTime.UtcNow, - State = KnownResourceStates.Running + Urls = [.. s.Urls.Select(u => u with { IsInactive = false })] }); - }); + + // Move resource to the running state + await e.Services.GetRequiredService() + .PublishUpdateAsync(e.Resource, s => s with + { + StartTimeStamp = DateTime.UtcNow, + State = KnownResourceStates.Running + }); + }); var app = await builder.BuildAsync(); @@ -616,20 +608,19 @@ public async Task WithUrlForEndpointUpdateDoesNotThrowOrCallCallbackIfEndpointNo using var builder = TestDistributedApplicationBuilder.Create(); var called = false; + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrlForEndpoint("non-existant", u => { called = true; + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task; @@ -645,21 +636,20 @@ public async Task WithUrlForEndpointAddDoesNotThrowOrCallCallbackIfEndpointNotFo using var builder = TestDistributedApplicationBuilder.Create(); var called = false; + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrlForEndpoint("non-existant", ep => { called = true; return new() { Url = "https://example.com" }; + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task; @@ -674,20 +664,19 @@ public async Task WithUrlForEndpointUpdateTurnsRelativeUrlIntoAbsoluteUrl() { using var builder = TestDistributedApplicationBuilder.Create(); + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrlForEndpoint("test", url => { url.Url = "/sub-path"; + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task; @@ -705,20 +694,19 @@ public async Task WithUrlForEndpointAddTurnsRelativeUrlIntoAbsoluteUrl() { using var builder = TestDistributedApplicationBuilder.Create(); + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrlForEndpoint("test", ep => { return new() { Url = "/sub-path" }; + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task; @@ -736,20 +724,19 @@ public async Task WithUrlsTurnsRelativeEndpointUrlsIntoAbsoluteUrls() { using var builder = TestDistributedApplicationBuilder.Create(); + var tcs = new TaskCompletionSource(); var projectA = builder.AddProject("projecta") .WithHttpEndpoint(name: "test") .WithUrls(c => { c.Urls.Add(new() { Endpoint = c.GetEndpoint("test"), Url = "/sub-path" }); + }) + .OnBeforeResourceStarted((_, _, _) => + { + tcs.SetResult(); + return Task.CompletedTask; }); - var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe(projectA.Resource, (e, ct) => - { - tcs.SetResult(); - return Task.CompletedTask; - }); - var app = await builder.BuildAsync(); await app.StartAsync(); await tcs.Task;