diff --git a/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs b/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs index 158ab53b5d9..a8c5dc5105e 100644 --- a/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/ProvisioningContextProviderTests.cs @@ -556,4 +556,256 @@ public async Task PublishMode_CreateProvisioningContextAsync_ReturnsValidContext Assert.NotNull(context.Principal); Assert.Equal("westus2", context.Location.Name); } + + [Fact] + public async Task CreateProvisioningContextAsync_SubscriptionInputStartsDisabledWhenNotConfigured() + { + // Arrange + var testInteractionService = new TestInteractionService(); + var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null, location: null, resourceGroup: null); + var environment = ProvisioningTestHelpers.CreateEnvironment(); + var logger = ProvisioningTestHelpers.CreateLogger(); + var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider(); + var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider(); + var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider(); + var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager(); + + var provider = new RunModeProvisioningContextProvider( + testInteractionService, + options, + environment, + logger, + armClientProvider, + userPrincipalProvider, + tokenCredentialProvider, + deploymentStateManager, + new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); + + // Act + _ = provider.CreateProvisioningContextAsync(CancellationToken.None); + + // Assert - Wait for the first interaction (message bar) + var messageBarInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + messageBarInteraction.CompletionTcs.SetResult(InteractionResult.Ok(true)); + + // Wait for the inputs interaction + var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + + // Find the subscription input + var subscriptionInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName]; + + // Assert that subscription ID input starts disabled when not configured + Assert.True(subscriptionInput.Disabled, "Subscription ID input should be disabled initially when not configured"); + Assert.NotNull(subscriptionInput.DynamicLoading); + Assert.Equal(InputType.Choice, subscriptionInput.InputType); + + // Assert Resource Group input has no default value initially + var resourceGroupInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName]; + Assert.Null(resourceGroupInput.Value); + } + + [Fact] + public async Task CreateProvisioningContextAsync_SubscriptionInputDependsOnTenantWhenNotConfigured() + { + // Arrange + var testInteractionService = new TestInteractionService(); + var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null, location: null, resourceGroup: null); + var environment = ProvisioningTestHelpers.CreateEnvironment(); + var logger = ProvisioningTestHelpers.CreateLogger(); + var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider(); + var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider(); + var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider(); + var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager(); + + var provider = new RunModeProvisioningContextProvider( + testInteractionService, + options, + environment, + logger, + armClientProvider, + userPrincipalProvider, + tokenCredentialProvider, + deploymentStateManager, + new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); + + // Act + _ = provider.CreateProvisioningContextAsync(CancellationToken.None); + + // Assert - Wait for the first interaction (message bar) + var messageBarInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + messageBarInteraction.CompletionTcs.SetResult(InteractionResult.Ok(true)); + + // Wait for the inputs interaction + var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + + // Find the subscription input + var subscriptionInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName]; + + // Assert that subscription ID has dynamic loading that depends on tenant + Assert.NotNull(subscriptionInput.DynamicLoading); + Assert.NotNull(subscriptionInput.DynamicLoading.DependsOnInputs); + var dependsOnInputs = Assert.Single(subscriptionInput.DynamicLoading.DependsOnInputs); + Assert.Equal(BaseProvisioningContextProvider.TenantName, dependsOnInputs); + } + + [Fact] + public async Task CreateProvisioningContextAsync_SubscriptionInputBecomesEnabledAfterTenantSelection() + { + // Arrange + var testInteractionService = new TestInteractionService(); + var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null, location: null, resourceGroup: null); + var environment = ProvisioningTestHelpers.CreateEnvironment(); + var logger = ProvisioningTestHelpers.CreateLogger(); + var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider(); + var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider(); + var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider(); + var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager(); + + var provider = new RunModeProvisioningContextProvider( + testInteractionService, + options, + environment, + logger, + armClientProvider, + userPrincipalProvider, + tokenCredentialProvider, + deploymentStateManager, + new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); + + // Act + var createTask = provider.CreateProvisioningContextAsync(CancellationToken.None); + + // Assert - Wait for the first interaction (message bar) + var messageBarInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + messageBarInteraction.CompletionTcs.SetResult(InteractionResult.Ok(true)); + + // Wait for the inputs interaction + var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + + // Set tenant ID + inputsInteraction.Inputs[BaseProvisioningContextProvider.TenantName].Value = "87654321-4321-4321-4321-210987654321"; + + // Find the subscription input + var subscriptionInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName]; + + // Assert subscription is initially disabled + Assert.True(subscriptionInput.Disabled); + + // Trigger dynamic loading callback for subscription based on tenant selection + await subscriptionInput.DynamicLoading!.LoadCallback(new LoadInputContext + { + AllInputs = inputsInteraction.Inputs, + CancellationToken = CancellationToken.None, + Input = subscriptionInput, + Services = new ServiceCollection().BuildServiceProvider() + }); + + // Assert that subscription input is now enabled after tenant selection + Assert.False(subscriptionInput.Disabled, "Subscription ID input should be enabled after tenant selection"); + Assert.NotNull(subscriptionInput.Options); + Assert.NotEmpty(subscriptionInput.Options); + } + + [Fact] + public async Task CreateProvisioningContextAsync_SubscriptionInputHasNoDynamicLoadingWhenConfigured() + { + // Arrange + var testInteractionService = new TestInteractionService(); + var subscriptionId = "12345678-1234-1234-1234-123456789012"; + var options = ProvisioningTestHelpers.CreateOptions(subscriptionId, location: null, resourceGroup: null); + var environment = ProvisioningTestHelpers.CreateEnvironment(); + var logger = ProvisioningTestHelpers.CreateLogger(); + var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider(); + var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider(); + var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider(); + var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager(); + + var provider = new RunModeProvisioningContextProvider( + testInteractionService, + options, + environment, + logger, + armClientProvider, + userPrincipalProvider, + tokenCredentialProvider, + deploymentStateManager, + new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); + + // Act + _ = provider.CreateProvisioningContextAsync(CancellationToken.None); + + // Assert - Wait for the first interaction (message bar) + var messageBarInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + messageBarInteraction.CompletionTcs.SetResult(InteractionResult.Ok(true)); + + // Wait for the inputs interaction + var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + + // Find the subscription input + var subscriptionInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName]; + + // Assert that subscription ID has no dynamic loading when pre-configured + Assert.Null(subscriptionInput.DynamicLoading); + Assert.True(subscriptionInput.Disabled, "Subscription ID input should remain disabled when pre-configured"); + Assert.Equal(InputType.Text, subscriptionInput.InputType); + Assert.Equal(subscriptionId, subscriptionInput.Value); + } + + [Fact] + public async Task CreateProvisioningContextAsync_ResourceGroupHasNoDefaultValueInitially() + { + // Arrange + var testInteractionService = new TestInteractionService(); + var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null, location: null, resourceGroup: null); + var environment = ProvisioningTestHelpers.CreateEnvironment(); + var logger = ProvisioningTestHelpers.CreateLogger(); + var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider(); + var userPrincipalProvider = ProvisioningTestHelpers.CreateUserPrincipalProvider(); + var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider(); + var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager(); + + var provider = new RunModeProvisioningContextProvider( + testInteractionService, + options, + environment, + logger, + armClientProvider, + userPrincipalProvider, + tokenCredentialProvider, + deploymentStateManager, + new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run)); + + // Act + _ = provider.CreateProvisioningContextAsync(CancellationToken.None); + + // Assert - Wait for the first interaction (message bar) + var messageBarInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + messageBarInteraction.CompletionTcs.SetResult(InteractionResult.Ok(true)); + + // Wait for the inputs interaction + var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync(); + + // Find the resource group input + var resourceGroupInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName]; + + // Resource group should have no default value initially (PR #13065 change) + Assert.Null(resourceGroupInput.Value); + + // Set subscription ID to trigger resource group loading + inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName].Value = "12345678-1234-1234-1234-123456789012"; + + // Trigger dynamic loading callback for resource group + await resourceGroupInput.DynamicLoading!.LoadCallback(new LoadInputContext + { + AllInputs = inputsInteraction.Inputs, + CancellationToken = CancellationToken.None, + Input = resourceGroupInput, + Services = new ServiceCollection().BuildServiceProvider() + }); + + // After dynamic loading with existing resource groups found, value should still be null + // Default value is only set when NO existing resource groups are found + Assert.Null(resourceGroupInput.Value); + Assert.NotEmpty(resourceGroupInput.Options!); + } }