File: DefaultTokenCredentialProviderTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Azure.Tests\Aspire.Hosting.Azure.Tests.csproj (Aspire.Hosting.Azure.Tests)
// 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
 
using Aspire.Hosting.Azure.Provisioning;
using Aspire.Hosting.Azure.Provisioning.Internal;
using Azure.Identity;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
 
namespace Aspire.Hosting.Azure.Tests;
 
public class DefaultTokenCredentialProviderTests
{
    [Fact]
    public void Constructor_PublishMode_NoCredentialSource_UsesAzureCli()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: null);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<AzureCliCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_NoCredentialSource_UsesDefaultAzureCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: null);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<DefaultAzureCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_PublishMode_ExplicitDefaultCredentialSource_UsesAzureCli()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "Default");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<AzureCliCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_ExplicitDefaultCredentialSource_UsesDefaultAzureCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "Default");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<DefaultAzureCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_PublishMode_ExplicitNonDefaultCredentialSource_RespectsSource()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "AzurePowerShell");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<AzurePowerShellCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_ExplicitCredentialSource_RespectsSource()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "VisualStudio");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<VisualStudioCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_InvalidCredentialSource_UsesDefaultAzureCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "InvalidSource");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<DefaultAzureCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_AzureCliCredentialSource_UsesAzureCliCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "AzureCli");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<AzureCliCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_AzureDeveloperCliCredentialSource_UsesAzureDeveloperCliCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "AzureDeveloperCli");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<AzureDeveloperCliCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void Constructor_RunMode_InteractiveBrowserCredentialSource_UsesInteractiveBrowserCredential()
    {
        // Arrange
        var azureOptions = CreateAzureOptions(credentialSource: "InteractiveBrowser");
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        // Act
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            azureOptions,
            executionContext);
 
        // Assert
        Assert.IsType<InteractiveBrowserCredential>(provider.TokenCredential);
    }
 
    [Fact]
    public void TokenCredential_TenantIdChanges_RecreatesCredential()
    {
        // Arrange
        var azureOptions = new AzureProvisionerOptions
        {
            CredentialSource = "AzureCli",
            TenantId = "tenant-1"
        };
        var options = new TestOptions<AzureProvisionerOptions>(azureOptions);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            options,
            executionContext);
 
        // Act - Access credential with first tenant
        var credential1 = provider.TokenCredential;
        Assert.IsType<AzureCliCredential>(credential1);
 
        // Change tenant ID
        azureOptions.TenantId = "tenant-2";
 
        // Access credential again
        var credential2 = provider.TokenCredential;
 
        // Assert - Should get a new credential instance
        Assert.IsType<AzureCliCredential>(credential2);
        Assert.NotSame(credential1, credential2);
    }
 
    [Fact]
    public void TokenCredential_TenantIdUnchanged_ReturnsSameCredential()
    {
        // Arrange
        var azureOptions = new AzureProvisionerOptions
        {
            CredentialSource = "AzureCli",
            TenantId = "tenant-1"
        };
        var options = new TestOptions<AzureProvisionerOptions>(azureOptions);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            options,
            executionContext);
 
        // Act - Access credential multiple times without changing tenant
        var credential1 = provider.TokenCredential;
        var credential2 = provider.TokenCredential;
        var credential3 = provider.TokenCredential;
 
        // Assert - Should get the same credential instance
        Assert.Same(credential1, credential2);
        Assert.Same(credential2, credential3);
    }
 
    [Fact]
    public void TokenCredential_TenantIdSetFromNull_RecreatesCredential()
    {
        // Arrange
        var azureOptions = new AzureProvisionerOptions
        {
            CredentialSource = "AzureDeveloperCli",
            TenantId = null
        };
        var options = new TestOptions<AzureProvisionerOptions>(azureOptions);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            options,
            executionContext);
 
        // Act - Access credential with null tenant
        var credential1 = provider.TokenCredential;
        Assert.IsType<AzureDeveloperCliCredential>(credential1);
 
        // Set tenant ID
        azureOptions.TenantId = "70a036f6-8e4d-4615-bad6-149c02e7720d";
 
        // Access credential again
        var credential2 = provider.TokenCredential;
 
        // Assert - Should get a new credential instance
        Assert.IsType<AzureDeveloperCliCredential>(credential2);
        Assert.NotSame(credential1, credential2);
    }
 
    [Fact]
    public void TokenCredential_MultipleCredentialTypes_RespectsCurrentTenantId()
    {
        // Arrange
        var azureOptions = new AzureProvisionerOptions
        {
            CredentialSource = "AzurePowerShell",
            TenantId = "tenant-1"
        };
        var options = new TestOptions<AzureProvisionerOptions>(azureOptions);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Run);
 
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            options,
            executionContext);
 
        // Act - Access credential
        var credential1 = provider.TokenCredential;
        Assert.IsType<AzurePowerShellCredential>(credential1);
 
        // Change both credential source and tenant ID
        azureOptions.CredentialSource = "VisualStudio";
        azureOptions.TenantId = "tenant-2";
 
        // Access credential again
        var credential2 = provider.TokenCredential;
 
        // Assert - Should get a new credential of the new type
        Assert.IsType<VisualStudioCredential>(credential2);
        Assert.NotSame(credential1, credential2);
    }
 
    [Fact]
    public void TokenCredential_LazyInitialization_DoesNotCreateUntilAccessed()
    {
        // Arrange
        var azureOptions = new AzureProvisionerOptions
        {
            CredentialSource = "AzureCli",
            TenantId = "tenant-1"
        };
        var options = new TestOptions<AzureProvisionerOptions>(azureOptions);
        var executionContext = new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish);
 
        // Act - Create provider but don't access TokenCredential property
        var provider = new DefaultTokenCredentialProvider(
            NullLogger<DefaultTokenCredentialProvider>.Instance,
            options,
            executionContext);
 
        // Change tenant ID before first access
        azureOptions.TenantId = "tenant-2";
 
        // Now access the credential
        var credential = provider.TokenCredential;
 
        // Assert - Should create credential with the current (second) tenant ID
        Assert.IsType<AzureCliCredential>(credential);
        // The credential should have been created with tenant-2, not tenant-1
        // We can't directly verify the tenant ID in the credential, but we verified
        // it was created after the tenant ID change
    }
 
    private static IOptions<AzureProvisionerOptions> CreateAzureOptions(string? credentialSource)
    {
        var options = new AzureProvisionerOptions
        {
            // Intentionally allow CredentialSource to be null for tests that verify null handling.
            CredentialSource = credentialSource!
        };
        return Options.Create(options);
    }
 
    /// <summary>
    /// Test implementation of IOptions that allows modifying the underlying options object.
    /// </summary>
    private sealed class TestOptions<T>(T value) : IOptions<T> where T : class
    {
        public T Value => value;
    }
 
}