// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable ASPIREPIPELINES002
#pragma warning disable ASPIREPIPELINES001
using System.Reflection;
using Aspire.Hosting.Azure.Provisioning;
using Aspire.Hosting.Azure.Provisioning.Internal;
using Aspire.Hosting.Azure.Resources;
using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Tests;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Aspire.Hosting.Azure.Tests;
public class ProvisioningContextProviderTests
{
private readonly TestInteractionService _defaultInteractionService = new() { IsAvailable = false };
[Fact]
public async Task CreateProvisioningContextAsync_ReturnsValidContext()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context);
Assert.NotNull(context.Credential);
Assert.NotNull(context.ArmClient);
Assert.NotNull(context.Subscription);
Assert.NotNull(context.ResourceGroup);
Assert.NotNull(context.Tenant);
Assert.NotNull(context.Location.DisplayName);
Assert.NotNull(context.Principal);
Assert.Equal("westus2", context.Location.Name);
}
[Fact]
public async Task CreateProvisioningContextAsync_ThrowsWhenSubscriptionIdMissing()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(subscriptionId: null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act & Assert
var exception = await Assert.ThrowsAsync<MissingConfigurationException>(
() => provider.CreateProvisioningContextAsync(CancellationToken.None));
Assert.Contains("Azure subscription id is required", exception.Message);
}
[Fact]
public async Task CreateProvisioningContextAsync_DoesNotReuseStaleInMemoryOptionsAfterReset()
{
// Arrange
var optionValues = new AzureProvisionerOptions();
var options = Options.Create(optionValues);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Simulate previously prompted values still hanging around in memory after reset.
optionValues.SubscriptionId = "12345678-1234-1234-1234-123456789012";
optionValues.Location = "westus2";
optionValues.ResourceGroup = "stale-rg";
optionValues.AllowResourceGroupCreation = true;
// Act & Assert
var exception = await Assert.ThrowsAsync<MissingConfigurationException>(
() => provider.CreateProvisioningContextAsync(CancellationToken.None));
Assert.Contains("Azure subscription id is required", exception.Message);
}
[Fact]
public async Task CreateProvisioningContextAsync_RehydratesStringBooleanAllowResourceGroupCreation()
{
var optionValues = new AzureProvisionerOptions();
var options = Options.Create(optionValues);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var azureSection = await deploymentStateManager.AcquireSectionAsync("Azure", CancellationToken.None);
azureSection.Data["SubscriptionId"] = "12345678-1234-1234-1234-123456789012";
azureSection.Data["Location"] = "westus3";
azureSection.Data["ResourceGroup"] = "rehydrated-rg";
azureSection.Data["AllowResourceGroupCreation"] = "true";
await deploymentStateManager.SaveSectionAsync(azureSection, CancellationToken.None);
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
Assert.Equal("westus3", context.Location.Name);
Assert.Equal("rehydrated-rg", context.ResourceGroup.Name);
Assert.True(optionValues.AllowResourceGroupCreation);
}
[Fact]
public async Task CreateProvisioningContextAsync_ThrowsWhenLocationMissing()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(location: null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
new TestInteractionService() { IsAvailable = false },
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act & Assert
var exception = await Assert.ThrowsAsync<MissingConfigurationException>(
() => provider.CreateProvisioningContextAsync(CancellationToken.None));
Assert.Contains("azure location/region is required", exception.Message);
}
[Fact]
public async Task CreateProvisioningContextAsync_GeneratesResourceGroupNameWhenNotProvided()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions(resourceGroup: null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.ResourceGroup);
Assert.NotNull(context.ResourceGroup.Name);
}
[Fact]
public async Task CreateProvisioningContextAsync_UsesProvidedResourceGroupName()
{
// Arrange
var resourceGroupName = "my-custom-rg";
var options = ProvisioningTestHelpers.CreateOptions(resourceGroup: resourceGroupName);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.ResourceGroup);
Assert.Equal(resourceGroupName, context.ResourceGroup.Name);
}
[Fact]
public async Task CreateProvisioningContextAsync_RetrievesPrincipal()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.Principal);
Assert.Equal("test@example.com", context.Principal.Name);
Assert.Equal(Guid.Parse("11111111-2222-3333-4444-555555555555"), context.Principal.Id);
}
[Fact]
public async Task CreateProvisioningContextAsync_SetsCorrectTenant()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.Tenant);
Assert.Equal(Guid.Parse("87654321-4321-4321-4321-210987654321"), context.Tenant.TenantId);
Assert.Equal("testdomain.onmicrosoft.com", context.Tenant.DefaultDomain);
}
[Fact]
public async Task EnsureProvisioningOptionsAsync_PromptsIfNoOptions()
{
// Arrange
var testInteractionService = new TestInteractionService();
var options = ProvisioningTestHelpers.CreateOptions(null, null, null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// Wait for the inputs interaction
var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync();
Assert.Equal(AzureProvisioningStrings.InputsTitle, inputsInteraction.Title);
var interactionOptions = Assert.IsType<InputsDialogInteractionOptions>(inputsInteraction.Options);
Assert.True(interactionOptions.EnableMessageMarkdown);
Assert.Equal(AzureProvisioningStrings.InputsPrimaryButtonText, interactionOptions.PrimaryButtonText);
Assert.Equal(AzureProvisioningStrings.InputsSecondaryButtonText, interactionOptions.SecondaryButtonText);
Assert.Collection(inputsInteraction.Inputs,
input =>
{
Assert.Equal(BaseProvisioningContextProvider.TenantName, input.Name);
Assert.Equal("Tenant ID", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.SubscriptionIdName, input.Name);
Assert.Equal("Subscription ID", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.ResourceGroupName, input.Name);
Assert.Equal("Resource group", input.Label);
Assert.Equal(InputType.Text, input.InputType);
Assert.False(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.LocationName, input.Name);
Assert.Equal("Location", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Required);
});
inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName].Value = "12345678-1234-1234-1234-123456789012";
// Set a custom resource group name (new resource group)
inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName].Value = "test-new-rg";
// Trigger dynamic update of locations based on subscription and resource group.
await inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].DynamicLoading!.LoadCallback(new LoadInputContext
{
AllInputs = inputsInteraction.Inputs,
CancellationToken = CancellationToken.None,
Input = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName],
Services = new ServiceCollection().BuildServiceProvider()
});
inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Value = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Options!.First(kvp => kvp.Key == "westus").Value;
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Ok(inputsInteraction.Inputs));
Assert.True(await ensureTask);
await provider.PersistProvisioningOptionsAsync(CancellationToken.None);
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.Tenant);
Assert.Equal(Guid.Parse("87654321-4321-4321-4321-210987654321"), context.Tenant.TenantId);
Assert.Equal("testdomain.onmicrosoft.com", context.Tenant.DefaultDomain);
Assert.Equal("/subscriptions/12345678-1234-1234-1234-123456789012", context.Subscription.Id.ToString());
Assert.Equal("westus", context.Location.Name);
Assert.Equal("test-new-rg", context.ResourceGroup.Name);
}
[Fact]
public async Task EnsureProvisioningOptionsAsync_Prompt_ValidatesSubAndResourceGroup()
{
var testInteractionService = new TestInteractionService();
var options = ProvisioningTestHelpers.CreateOptions(null, null, null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// Wait for the inputs interaction
var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync();
inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName].Value = "not a guid";
// Trigger dynamic update of locations based on subscription.
await inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].DynamicLoading!.LoadCallback(new LoadInputContext
{
AllInputs = inputsInteraction.Inputs,
CancellationToken = CancellationToken.None,
Input = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName],
Services = new ServiceCollection().BuildServiceProvider()
});
inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Value = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Options!.First(kvp => kvp.Key == "westus").Value;
inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName].Value = "invalid group";
var context = new InputsDialogValidationContext
{
CancellationToken = CancellationToken.None,
Services = new ServiceCollection().BuildServiceProvider(),
Inputs = inputsInteraction.Inputs
};
var inputOptions = Assert.IsType<InputsDialogInteractionOptions>(inputsInteraction.Options);
Assert.NotNull(inputOptions.ValidationCallback);
await inputOptions.ValidationCallback(context);
Assert.True((bool)context.GetType().GetProperty("HasErrors", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(context, null)!);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
[Fact]
public async Task EnsureProvisioningOptionsAsync_IncludesTenantWhenSubscriptionIdProvided()
{
// Arrange
var testInteractionService = new TestInteractionService();
var subscriptionId = "12345678-1234-1234-1234-123456789012";
var options = ProvisioningTestHelpers.CreateOptions(subscriptionId, null, null);
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// Wait for the inputs interaction
var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync();
Assert.Equal(AzureProvisioningStrings.InputsTitle, inputsInteraction.Title);
Assert.True(inputsInteraction.Options!.EnableMessageMarkdown);
Assert.Collection(inputsInteraction.Inputs,
input =>
{
Assert.Equal(BaseProvisioningContextProvider.TenantName, input.Name);
Assert.Equal("Tenant ID", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.SubscriptionIdName, input.Name);
Assert.Equal("Subscription ID", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Disabled);
Assert.True(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.ResourceGroupName, input.Name);
Assert.Equal("Resource group", input.Label);
Assert.Equal(InputType.Text, input.InputType);
Assert.False(input.Required);
},
input =>
{
Assert.Equal(BaseProvisioningContextProvider.LocationName, input.Name);
Assert.Equal("Location", input.Label);
Assert.Equal(InputType.Choice, input.InputType);
Assert.True(input.Required);
});
// Set a custom resource group name
inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName].Value = "test-new-rg";
// Trigger dynamic update of locations based on subscription and resource group.
await inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].DynamicLoading!.LoadCallback(new LoadInputContext
{
AllInputs = inputsInteraction.Inputs,
CancellationToken = CancellationToken.None,
Input = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName],
Services = new ServiceCollection().BuildServiceProvider()
});
// Trigger dynamic update of locations based on subscription.
await inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].DynamicLoading!.LoadCallback(new LoadInputContext
{
AllInputs = inputsInteraction.Inputs,
CancellationToken = CancellationToken.None,
Input = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName],
Services = new ServiceCollection().BuildServiceProvider()
});
inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Value = inputsInteraction.Inputs[BaseProvisioningContextProvider.LocationName].Options!.First(kvp => kvp.Key == "westus").Value;
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Ok(inputsInteraction.Inputs));
Assert.True(await ensureTask);
await provider.PersistProvisioningOptionsAsync(CancellationToken.None);
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context.Tenant);
Assert.Equal(Guid.Parse("87654321-4321-4321-4321-210987654321"), context.Tenant.TenantId);
Assert.Equal("testdomain.onmicrosoft.com", context.Tenant.DefaultDomain);
Assert.Equal("/subscriptions/12345678-1234-1234-1234-123456789012", context.Subscription.Id.ToString());
Assert.Equal("westus", context.Location.Name);
Assert.Equal("test-new-rg", context.ResourceGroup.Name);
}
[Fact]
public async Task PublishMode_CreateProvisioningContextAsync_ReturnsValidContext()
{
// Arrange
var options = ProvisioningTestHelpers.CreateOptions();
var environment = ProvisioningTestHelpers.CreateEnvironment();
var logger = ProvisioningTestHelpers.CreateLogger<PublishModeProvisioningContextProvider>();
var armClientProvider = ProvisioningTestHelpers.CreateArmClientProvider();
var azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new PublishModeProvisioningContextProvider(
_defaultInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish),
new NullPublishingActivityReporter());
// Act
var context = await provider.CreateProvisioningContextAsync(CancellationToken.None);
// Assert
Assert.NotNull(context);
Assert.NotNull(context.Credential);
Assert.NotNull(context.ArmClient);
Assert.NotNull(context.Subscription);
Assert.NotNull(context.ResourceGroup);
Assert.NotNull(context.Tenant);
Assert.NotNull(context.Location.DisplayName);
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 azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// 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 starts with the generated default used by the configure dialog.
var resourceGroupInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName];
Assert.StartsWith("rg-aspire-testapp-", resourceGroupInput.Value, StringComparison.Ordinal);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
[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 azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// 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);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
[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 azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// 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);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
[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 azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// Wait for the inputs interaction
var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync();
// Find the subscription input
var subscriptionInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName];
// Forced configuration lets users change the subscription, but it stays disabled until tenant selection.
Assert.NotNull(subscriptionInput.DynamicLoading);
Assert.True(subscriptionInput.Disabled, "Subscription ID input should start disabled until tenant selection");
Assert.Equal(InputType.Choice, subscriptionInput.InputType);
Assert.Equal(subscriptionId, subscriptionInput.Value);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
[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 azurePrincipalProvider = ProvisioningTestHelpers.CreateAzurePrincipalProvider();
var tokenCredentialProvider = ProvisioningTestHelpers.CreateTokenCredentialProvider();
var deploymentStateManager = ProvisioningTestHelpers.CreateUserSecretsManager();
var provider = new RunModeProvisioningContextProvider(
testInteractionService,
options,
environment,
logger,
armClientProvider,
azurePrincipalProvider,
tokenCredentialProvider,
deploymentStateManager,
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run));
// Act
var ensureTask = provider.EnsureProvisioningOptionsAsync(forcePrompt: true, CancellationToken.None);
// Wait for the inputs interaction
var inputsInteraction = await testInteractionService.Interactions.Reader.ReadAsync();
// Find the resource group input
var resourceGroupInput = inputsInteraction.Inputs[BaseProvisioningContextProvider.ResourceGroupName];
Assert.StartsWith("rg-aspire-testapp-", resourceGroupInput.Value, StringComparison.Ordinal);
// Set subscription ID to trigger resource group loading
inputsInteraction.Inputs[BaseProvisioningContextProvider.SubscriptionIdName].Value = "12345678-1234-1234-1234-123456789012";
Assert.Null(resourceGroupInput.DynamicLoading);
Assert.Equal(InputType.Text, resourceGroupInput.InputType);
inputsInteraction.CompletionTcs.SetResult(InteractionResult.Cancel<InteractionInputCollection>());
Assert.False(await ensureTask);
}
}