diff --git a/playground/ParameterEndToEnd/ParameterEndToEnd.AppHost/AppHost.cs b/playground/ParameterEndToEnd/ParameterEndToEnd.AppHost/AppHost.cs index aa4d24e3cb4..5dad5acddcf 100644 --- a/playground/ParameterEndToEnd/ParameterEndToEnd.AppHost/AppHost.cs +++ b/playground/ParameterEndToEnd/ParameterEndToEnd.AppHost/AppHost.cs @@ -22,7 +22,7 @@ var insertionrows = builder.AddParameter("insertionrows") .WithDescription("The number of rows to insert into the database."); -var cs = builder.AddConnectionString("cs", ReferenceExpression.Create($"sql={db};rows={insertionrows}")); +var cs = builder.AddConnectionString("cs", ReferenceExpression.Create($"sql={db.Resource.Parent.PrimaryEndpoint};rows={insertionrows}")); var parameterFromConnectionStringConfig = builder.AddConnectionString("parameterFromConnectionStringConfig"); var throwing = builder.AddParameter("throwing", () => throw new InvalidOperationException("This is a test exception.")); diff --git a/src/Aspire.Hosting/ApplicationModel/ParameterResource.cs b/src/Aspire.Hosting/ApplicationModel/ParameterResource.cs index 76083d8b3b0..6d6ca8ffc04 100644 --- a/src/Aspire.Hosting/ApplicationModel/ParameterResource.cs +++ b/src/Aspire.Hosting/ApplicationModel/ParameterResource.cs @@ -9,7 +9,7 @@ namespace Aspire.Hosting.ApplicationModel; /// /// Represents a parameter resource. /// -public class ParameterResource : Resource, IResourceWithoutLifetime, IManifestExpressionProvider, IValueProvider +public class ParameterResource : Resource, IManifestExpressionProvider, IValueProvider { private readonly Lazy _lazyValue; private readonly Func _valueGetter; diff --git a/src/Aspire.Hosting/CompatibilitySuppressions.xml b/src/Aspire.Hosting/CompatibilitySuppressions.xml index c9df370dcd3..16770fc94f1 100644 --- a/src/Aspire.Hosting/CompatibilitySuppressions.xml +++ b/src/Aspire.Hosting/CompatibilitySuppressions.xml @@ -22,4 +22,25 @@ lib/net8.0/Aspire.Hosting.dll true + + CP0008 + T:Aspire.Hosting.ApplicationModel.ParameterResource + lib/net8.0/Aspire.Hosting.dll + lib/net8.0/Aspire.Hosting.dll + true + + + CP0008 + T:Aspire.Hosting.ConnectionStringResource + lib/net8.0/Aspire.Hosting.dll + lib/net8.0/Aspire.Hosting.dll + true + + + CP0008 + T:Aspire.Hosting.ExternalServiceResource + lib/net8.0/Aspire.Hosting.dll + lib/net8.0/Aspire.Hosting.dll + true + \ No newline at end of file diff --git a/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs b/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs index d6ce9a2f5d2..d3d6bd98a35 100644 --- a/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs +++ b/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Dashboard.Model; using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Logging; namespace Aspire.Hosting; @@ -39,14 +40,79 @@ public static class ConnectionStringBuilderExtensions public static IResourceBuilder AddConnectionString(this IDistributedApplicationBuilder builder, [ResourceName] string name, ReferenceExpression connectionStringExpression) { var cs = new ConnectionStringResource(name, connectionStringExpression); - return builder.AddResource(cs) - .WithReferenceRelationship(connectionStringExpression) - .WithInitialState(new CustomResourceSnapshot - { - ResourceType = KnownResourceTypes.ConnectionString, - State = KnownResourceStates.Waiting, - Properties = [] - }); + + var rb = builder.AddResource(cs); + + // Wait for any referenced resources in the connection string. + // We only look at top level resources with the assumption that they are transitive themselves. + var tasks = new List(); + var resourceNames = new HashSet(StringComparers.ResourceName); + + foreach (var value in cs.ConnectionStringExpression.ValueProviders) + { + if (value is IResourceWithoutLifetime) + { + // We cannot wait for resources without a lifetime. + continue; + } + + if (value is IResource resource) + { + if (resourceNames.Add(resource.Name)) + { + // Wait for the resource. + rb.WaitForStart(builder.CreateResourceBuilder(resource)); + } + } + else if (value is IValueWithReferences valueWithReferences) + { + foreach (var innerRef in valueWithReferences.References.OfType()) + { + if (resourceNames.Add(innerRef.Name)) + { + // Wait for the inner resource. + rb.WaitForStart(builder.CreateResourceBuilder(innerRef)); + } + } + } + } + + return rb.WithReferenceRelationship(connectionStringExpression) + .WithInitialState(new CustomResourceSnapshot + { + ResourceType = KnownResourceTypes.ConnectionString, + State = KnownResourceStates.NotStarted, + Properties = [] + }) + .OnInitializeResource(async (r, @evt, ct) => + { + try + { + // This is where waiting happens + await @evt.Eventing.PublishAsync(new BeforeResourceStartedEvent(r, @evt.Services), ct).ConfigureAwait(false); + + // Publish the update with the connection string value and the state as running. + // This will allow health checks to start running. + await evt.Notifications.PublishUpdateAsync(r, s => s with + { + State = KnownResourceStates.Running + }).ConfigureAwait(false); + + // Publish the connection string available event for other resources that may depend on this resource. + await evt.Eventing.PublishAsync(new ConnectionStringAvailableEvent(r, evt.Services), ct) + .ConfigureAwait(false); + } + catch (Exception ex) + { + evt.Logger.LogError(ex, "Failed to resolve connection string for resource '{ResourceName}'", r.Name); + + // If we fail to resolve the connection string, we set the state to failed. + await evt.Notifications.PublishUpdateAsync(r, s => s with + { + State = KnownResourceStates.FailedToStart + }).ConfigureAwait(false); + } + }); } /// diff --git a/src/Aspire.Hosting/ConnectionStringResource.cs b/src/Aspire.Hosting/ConnectionStringResource.cs index 5d710b6e16a..8b69902a158 100644 --- a/src/Aspire.Hosting/ConnectionStringResource.cs +++ b/src/Aspire.Hosting/ConnectionStringResource.cs @@ -10,7 +10,7 @@ namespace Aspire.Hosting; /// /// The name of the resource. /// The connection string expression. -public sealed class ConnectionStringResource(string name, ReferenceExpression connectionStringExpression) : Resource(name), IResourceWithConnectionString, IResourceWithoutLifetime +public sealed class ConnectionStringResource(string name, ReferenceExpression connectionStringExpression) : Resource(name), IResourceWithConnectionString, IResourceWithWaitSupport { /// /// Describes the connection string format string used for this resource. diff --git a/src/Aspire.Hosting/ExternalServiceResource.cs b/src/Aspire.Hosting/ExternalServiceResource.cs index be6aa1f81d6..2791ef33611 100644 --- a/src/Aspire.Hosting/ExternalServiceResource.cs +++ b/src/Aspire.Hosting/ExternalServiceResource.cs @@ -9,7 +9,7 @@ namespace Aspire.Hosting; /// /// Represents an external service resource with service discovery capabilities. /// -public sealed class ExternalServiceResource : Resource, IResourceWithoutLifetime +public sealed class ExternalServiceResource : Resource { private readonly Uri? _uri; private readonly ParameterResource? _urlParameter; diff --git a/src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs b/src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs index dfeb5974022..3494a0979d2 100644 --- a/src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs +++ b/src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs @@ -9,7 +9,6 @@ using Aspire.Hosting.Dcp; using Aspire.Hosting.Eventing; using Aspire.Hosting.Lifecycle; -using Microsoft.Extensions.Logging; namespace Aspire.Hosting.Orchestrator; @@ -59,7 +58,6 @@ public ApplicationOrchestrator(DistributedApplicationModel model, _eventing.Subscribe(PublishConnectionStringValue); // Implement WaitFor functionality using BeforeResourceStartedEvent. _eventing.Subscribe(WaitForInBeforeResourceStartedEvent); - _eventing.Subscribe(OnResourceInitialized); } private async Task PublishConnectionStringValue(ConnectionStringAvailableEvent @event, CancellationToken token) @@ -306,69 +304,6 @@ private async Task OnResourceEndpointsAllocated(ResourceEndpointsAllocatedEvent await PublishResourceEndpointUrls(@event.Resource, cancellationToken).ConfigureAwait(false); } - private Task OnResourceInitialized(InitializeResourceEvent @event, CancellationToken cancellationToken) - { - var resource = @event.Resource; - - if (resource is ConnectionStringResource connectionStringResource) - { - InitializeConnectionString(connectionStringResource); - } - - void InitializeConnectionString(ConnectionStringResource connectionStringResource) - { - var logger = _loggerService.GetLogger(resource); - var waitFor = new List(); - - var references = connectionStringResource.Annotations.OfType() - .Where(x => x.Type == KnownRelationshipTypes.Reference) - .Select(x => x.Resource); - - foreach (var reference in references) - { - if (reference is IResourceWithEndpoints) - { - var tcs = new TaskCompletionSource(); - logger.LogInformation("Waiting for endpoints to be allocated for resource {ResourceName}", reference.Name); - _eventing.Subscribe(reference, (_, _) => - { - logger.LogInformation("Endpoints allocated for resource {ResourceName}", reference.Name); - tcs.SetResult(); - return Task.CompletedTask; - }); - - waitFor.Add(tcs.Task.WaitAsync(cancellationToken)); - } - - if (reference is IResourceWithConnectionString) - { - var tcs = new TaskCompletionSource(); - logger.LogInformation("Waiting for connection string to be available for resource {ResourceName}", reference.Name); - _eventing.Subscribe(reference, (_, _) => - { - logger.LogInformation("Connection string is available for resource {ResourceName}", reference.Name); - tcs.SetResult(); - return Task.CompletedTask; - }); - - waitFor.Add(tcs.Task.WaitAsync(cancellationToken)); - } - } - - _ = Task.Run(async () => - { - await Task.WhenAll(waitFor).ConfigureAwait(false); - await PublishConnectionStringAvailableEvent(connectionStringResource, cancellationToken).ConfigureAwait(false); - await _notificationService.PublishUpdateAsync(connectionStringResource, s => s with - { - State = new(KnownResourceStates.Active, KnownResourceStateStyles.Success), - }).ConfigureAwait(false); - }, cancellationToken); - } - - return Task.CompletedTask; - } - private async Task OnResourceChanged(OnResourceChangedContext context) { await _notificationService.PublishUpdateAsync(context.Resource, context.DcpResourceName, context.UpdateSnapshot).ConfigureAwait(false); diff --git a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs index c2cb2575790..f24031c8817 100644 --- a/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs +++ b/src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs @@ -70,7 +70,7 @@ await notificationService.PublishUpdateAsync(parameterResource, s => return s with { Properties = s.Properties.SetResourceProperty(KnownProperties.Parameter.Value, value, parameterResource.Secret), - State = new(KnownResourceStates.Active, KnownResourceStateStyles.Success) + State = KnownResourceStates.Running }; }) .ConfigureAwait(false); @@ -190,7 +190,7 @@ await notificationService.PublishUpdateAsync(parameter, s => return s with { Properties = s.Properties.SetResourceProperty(KnownProperties.Parameter.Value, inputValue, parameter.Secret), - State = new(KnownResourceStates.Active, KnownResourceStateStyles.Success) + State = KnownResourceStates.Running }; }) .ConfigureAwait(false); diff --git a/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs b/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs index a8f4e272bfd..4c39193aaa6 100644 --- a/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ParameterResourceBuilderExtensions.cs @@ -216,7 +216,6 @@ internal static IResourceBuilder AddParameter(this IDistributedApplication { ResourceType = KnownResourceTypes.Parameter, Properties = [ - new("parameter.secret", resource.Secret.ToString()), new(CustomResourceKnownProperties.Source, resource.ConfigurationKey) ], State = KnownResourceStates.Waiting diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index 2b90cbac462..e64b2158427 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -1202,16 +1202,6 @@ private static IResourceBuilder WaitForCore(this IResourceBuilder build builder.WaitForCore(parentBuilder, waitBehavior, addRelationship: false); } - // Wait for any referenced resources in the connection string. - if (dependency.Resource is ConnectionStringResource cs) - { - // We only look at top level resources with the assumption that they are transitive themselves. - foreach (var referencedResource in cs.ConnectionStringExpression.ValueProviders.OfType()) - { - builder.WaitForCore(builder.ApplicationBuilder.CreateResourceBuilder(referencedResource), waitBehavior, addRelationship: false); - } - } - if (addRelationship) { builder.WithRelationship(dependency.Resource, KnownRelationshipTypes.WaitFor); diff --git a/tests/Aspire.Hosting.Tests/AddConnectionStringTests.cs b/tests/Aspire.Hosting.Tests/AddConnectionStringTests.cs new file mode 100644 index 00000000000..db70e04beba --- /dev/null +++ b/tests/Aspire.Hosting.Tests/AddConnectionStringTests.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Aspire.Hosting.Utils; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting.Tests; + +public class AddConnectionStringTests +{ + [Fact] + public async Task AddConnectionStringExpressionIsAValueInTheManifest() + { + var appBuilder = DistributedApplication.CreateBuilder(); + + var endpoint = appBuilder.AddParameter("endpoint", "http://localhost:3452"); + var key = appBuilder.AddParameter("key", "secretKey", secret: true); + + // Get the service provider. + appBuilder.AddConnectionString("mycs", ReferenceExpression.Create($"Endpoint={endpoint};Key={key}")); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + + Assert.Equal("mycs", connectionStringResource.Name); + var connectionStringManifest = await ManifestUtils.GetManifest(connectionStringResource).DefaultTimeout(); + + var expectedManifest = $$""" + { + "type": "value.v0", + "connectionString": "Endpoint={endpoint.value};Key={key.value}" + } + """; + + var s = connectionStringManifest.ToString(); + + Assert.Equal(expectedManifest, s); + } + + [Fact] + public void ConnectionStringsAreVisibleByDefault() + { + var appBuilder = DistributedApplication.CreateBuilder(); + var endpoint = appBuilder.AddParameter("endpoint", "http://localhost:3452"); + var key = appBuilder.AddParameter("key", "secretKey", secret: true); + + appBuilder.AddConnectionString("testcs", ReferenceExpression.Create($"Endpoint={endpoint};Key={key}")); + + using var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + var annotation = connectionStringResource.Annotations.OfType().SingleOrDefault(); + + Assert.NotNull(annotation); + + var state = annotation.InitialSnapshot; + + Assert.False(state.IsHidden); + Assert.Equal(KnownResourceTypes.ConnectionString, state.ResourceType); + Assert.Equal(KnownResourceStates.NotStarted, state.State?.Text); + } + + [Fact] + public void ConnectionStringResourceAddsWaitAnnotationsForReferencedResources() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var redis = builder.AddContainer("c", "redis").WithEndpoint(name: "tcp"); + var key = builder.AddParameter("key", "secretKey", secret: true); + var rwl = builder.AddResource(new ResourceWithoutLifetime("rwl")); + + var cs = builder.AddConnectionString("mycs", + ReferenceExpression.Create($"Endpoint={redis.GetEndpoint("tcp")};key={key};{rwl}")); + + cs.Resource.TryGetAnnotationsOfType(out var waitAnnotations); + + Assert.NotNull(waitAnnotations); + + Assert.Collection(waitAnnotations, + wa => + { + Assert.Same(redis.Resource, wa.Resource); + Assert.Equal(WaitType.WaitUntilStarted, wa.WaitType); + }, + wa => + { + Assert.Same(key.Resource, wa.Resource); + Assert.Equal(WaitType.WaitUntilStarted, wa.WaitType); + }); + } + + private sealed class ResourceWithoutLifetime(string name) : Resource(name), IResourceWithConnectionString, IResourceWithoutLifetime + { + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create($"ResourceWithoutLifetime"); + } +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.Tests/AddParameterTests.cs b/tests/Aspire.Hosting.Tests/AddParameterTests.cs index 8588277f976..214489cb6b6 100644 --- a/tests/Aspire.Hosting.Tests/AddParameterTests.cs +++ b/tests/Aspire.Hosting.Tests/AddParameterTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Aspire.Dashboard.Model; using Aspire.Hosting.Publishing; using Aspire.Hosting.Resources; using Aspire.Hosting.Utils; @@ -34,11 +33,6 @@ public void ParametersAreVisibleByDefault() Assert.False(state.IsHidden); Assert.Collection(state.Properties, - prop => - { - Assert.Equal("parameter.secret", prop.Name); - Assert.Equal("True", prop.Value); - }, prop => { Assert.Equal(CustomResourceKnownProperties.Source, prop.Name); @@ -333,37 +327,6 @@ public async Task AddConnectionStringParameterIsASecretParameterInTheManifest() Assert.Equal(expectedManifest, s); } - [Fact] - public async Task AddConnectionStringExpressionIsAValueInTheManifest() - { - var appBuilder = DistributedApplication.CreateBuilder(); - - var endpoint = appBuilder.AddParameter("endpoint", "http://localhost:3452"); - var key = appBuilder.AddParameter("key", "secretKey", secret: true); - - // Get the service provider. - appBuilder.AddConnectionString("mycs", ReferenceExpression.Create($"Endpoint={endpoint};Key={key}")); - - using var app = appBuilder.Build(); - - var appModel = app.Services.GetRequiredService(); - var connectionStringResource = Assert.Single(appModel.Resources.OfType()); - - Assert.Equal("mycs", connectionStringResource.Name); - var connectionStringManifest = await ManifestUtils.GetManifest(connectionStringResource).DefaultTimeout(); - - var expectedManifest = $$""" - { - "type": "value.v0", - "connectionString": "Endpoint={endpoint.value};Key={key.value}" - } - """; - - var s = connectionStringManifest.ToString(); - - Assert.Equal(expectedManifest, s); - } - private sealed class TestParameterDefault(string defaultValue) : ParameterDefault { public override string GetDefaultValue() => defaultValue; @@ -374,31 +337,6 @@ public override void WriteToManifest(ManifestPublishingContext context) } } - [Fact] - public void ConnectionStringsAreVisibleByDefault() - { - var appBuilder = DistributedApplication.CreateBuilder(); - var endpoint = appBuilder.AddParameter("endpoint", "http://localhost:3452"); - var key = appBuilder.AddParameter("key", "secretKey", secret: true); - - appBuilder.AddConnectionString("testcs", ReferenceExpression.Create($"Endpoint={endpoint};Key={key}")); - - using var app = appBuilder.Build(); - - var appModel = app.Services.GetRequiredService(); - - var connectionStringResource = Assert.Single(appModel.Resources.OfType()); - var annotation = connectionStringResource.Annotations.OfType().SingleOrDefault(); - - Assert.NotNull(annotation); - - var state = annotation.InitialSnapshot; - - Assert.False(state.IsHidden); - Assert.Equal(KnownResourceTypes.ConnectionString, state.ResourceType); - Assert.Equal(KnownResourceStates.Waiting, state.State?.Text); - } - [Fact] public void ParameterWithDescription_SetsDescriptionProperty() { diff --git a/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs b/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs index aae26da9132..d121b8f8aa0 100644 --- a/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs @@ -246,17 +246,6 @@ public void ExternalServiceResourceHasExpectedInitialState() Assert.Equal("ExternalService", snapshot.InitialSnapshot.ResourceType); } - [Fact] - public void ExternalServiceResourceImplementsExpectedInterfaces() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var externalService = builder.AddExternalService("nuget", "https://nuget.org/"); - - // Verify the resource implements the expected interfaces - Assert.IsAssignableFrom(externalService.Resource); - } - [Fact] public void ExternalServiceResourceIsExcludedFromPublishingManifest() { diff --git a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs index e02f6282010..1bc1f1c4785 100644 --- a/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs +++ b/tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs @@ -16,7 +16,7 @@ namespace Aspire.Hosting.Tests.Orchestrator; public class ParameterProcessorTests { [Fact] - public async Task InitializeParametersAsync_WithValidParameters_SetsActiveState() + public async Task InitializeParametersAsync_WithValidParameters_SetsRunningState() { // Arrange var parameterProcessor = CreateParameterProcessor(); @@ -41,7 +41,7 @@ public async Task InitializeParametersAsync_WithValidParameters_SetsActiveState( } [Fact] - public async Task InitializeParametersAsync_WithValidParametersAndDashboardEnabled_SetsActiveState() + public async Task InitializeParametersAsync_WithValidParametersAndDashboardEnabled_SetsRunningState() { // Arrange var interactionService = CreateInteractionService(disableDashboard: false); @@ -93,8 +93,7 @@ public async Task InitializeParametersAsync_WithSecretParameter_MarksAsSecret() // Assert var (resource, snapshot) = Assert.Single(updates); Assert.Same(secretParam, resource); - Assert.Equal(KnownResourceStates.Active, snapshot.State?.Text); - Assert.Equal(KnownResourceStateStyles.Success, snapshot.State?.Style); + Assert.Equal(KnownResourceStates.Running, snapshot.State?.Text); } [Fact] @@ -246,17 +245,17 @@ public async Task HandleUnresolvedParametersAsync_WithMultipleUnresolvedParamete Assert.Equal("secretValue", await secretParam.WaitForValueTcs.Task); // Notification service should have received updates for each parameter - // Marking them as Active with the provided values + // Marking them as Running with the provided values await updates.MoveNextAsync(); - Assert.Equal(KnownResourceStates.Active, updates.Current.Snapshot.State?.Text); + Assert.Equal(KnownResourceStates.Running, updates.Current.Snapshot.State?.Text); Assert.Equal("value1", updates.Current.Snapshot.Properties.FirstOrDefault(p => p.Name == KnownProperties.Parameter.Value)?.Value); await updates.MoveNextAsync(); - Assert.Equal(KnownResourceStates.Active, updates.Current.Snapshot.State?.Text); + Assert.Equal(KnownResourceStates.Running, updates.Current.Snapshot.State?.Text); Assert.Equal("value2", updates.Current.Snapshot.Properties.FirstOrDefault(p => p.Name == KnownProperties.Parameter.Value)?.Value); await updates.MoveNextAsync(); - Assert.Equal(KnownResourceStates.Active, updates.Current.Snapshot.State?.Text); + Assert.Equal(KnownResourceStates.Running, updates.Current.Snapshot.State?.Text); Assert.Equal("secretValue", updates.Current.Snapshot.Properties.FirstOrDefault(p => p.Name == KnownProperties.Parameter.Value)?.Value); Assert.True(updates.Current.Snapshot.Properties.FirstOrDefault(p => p.Name == KnownProperties.Parameter.Value)?.IsSensitive ?? false); } diff --git a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs index 1204d57cbd5..f704c215669 100644 --- a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs @@ -302,6 +302,16 @@ public async Task ConnectionStringResourceWithExpressionConnectionString() Assert.True(resource.Resource.TryGetAnnotationsOfType(out var csRelationships)); Assert.Collection(csRelationships, + r => + { + Assert.Equal("WaitFor", r.Type); + Assert.Same(endpoint.Resource, r.Resource); + }, + r => + { + Assert.Equal("WaitFor", r.Type); + Assert.Same(key.Resource, r.Resource); + }, r => { Assert.Equal("Reference", r.Type);