File: AspireKeyVaultExtensionsTests.cs
Web Access
Project: src\tests\Aspire.Azure.Security.KeyVault.Tests\Aspire.Azure.Security.KeyVault.Tests.csproj (Aspire.Azure.Security.KeyVault.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using System.Text;
using Azure.Core;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
 
namespace Aspire.Azure.Security.KeyVault.Tests;
 
public class AspireKeyVaultExtensionsTests
{
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void VaultUriCanBeSetInCode(bool useKeyed)
    {
        var vaultUri = new Uri(ConformanceTests.VaultUri);
 
        var builder = Host.CreateEmptyApplicationBuilder(null);
        builder.Configuration.AddInMemoryCollection([
            new KeyValuePair<string, string?>("ConnectionStrings:secrets", "https://unused.vault.azure.net/")
        ]);
 
        if (useKeyed)
        {
            builder.AddKeyedAzureKeyVaultClient("secrets", settings => settings.VaultUri = vaultUri);
        }
        else
        {
            builder.AddAzureKeyVaultClient("secrets", settings => settings.VaultUri = vaultUri);
        }
 
        using var host = builder.Build();
        var client = useKeyed ?
            host.Services.GetRequiredKeyedService<SecretClient>("secrets") :
            host.Services.GetRequiredService<SecretClient>();
 
        Assert.Equal(vaultUri, client.VaultUri);
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void ConnectionNameWinsOverConfigSection(bool useKeyed)
    {
        var builder = Host.CreateEmptyApplicationBuilder(null);
 
        var key = useKeyed ? "secrets" : null;
        builder.Configuration.AddInMemoryCollection([
            new KeyValuePair<string, string?>(ConformanceTests.CreateConfigKey("Aspire:Azure:Security:KeyVault", key, "VaultUri"), "unused"),
            new KeyValuePair<string, string?>("ConnectionStrings:secrets", ConformanceTests.VaultUri)
        ]);
 
        if (useKeyed)
        {
            builder.AddKeyedAzureKeyVaultClient("secrets");
        }
        else
        {
            builder.AddAzureKeyVaultClient("secrets");
        }
 
        using var host = builder.Build();
        var client = useKeyed ?
            host.Services.GetRequiredKeyedService<SecretClient>("secrets") :
            host.Services.GetRequiredService<SecretClient>();
 
        Assert.Equal(new Uri(ConformanceTests.VaultUri), client.VaultUri);
    }
 
    [Fact]
    public void AddsKeyVaultSecretsToConfig()
    {
        var builder = Host.CreateEmptyApplicationBuilder(null);
        builder.Configuration.AddInMemoryCollection([
            new KeyValuePair<string, string?>("ConnectionStrings:secrets", ConformanceTests.VaultUri)
        ]);
 
        builder.Configuration.AddAzureKeyVaultSecrets("secrets", configureClientOptions: o =>
        {
            o.Transport = new MockTransport(
                CreateResponse("""
                    {
                        "value": [
                            {
                                "id": "https://aspiretests.vault.azure.net/secrets/super-secret-1",
                                "attributes": {
                                    "enabled": true,
                                    "created": 1697066435,
                                    "updated": 1697066435,
                                    "recoveryLevel": "Recoverable+Purgeable",
                                    "recoverableDays": 90
                                }
                            },
                            {
                                "id": "https://aspiretests.vault.azure.net/secrets/super-secret-2",
                                "attributes": {
                                    "enabled": true,
                                    "created": 1692910062,
                                    "updated": 1692910062,
                                    "recoveryLevel": "Recoverable+Purgeable",
                                    "recoverableDays": 90
                                }
                            }
                        ],
                        "nextLink": null
                    }
                    """{
                        "value": [
                            {
                                "id": "https://aspiretests.vault.azure.net/secrets/super-secret-1",
                                "attributes": {
                                    "enabled": true,
                                    "created": 1697066435,
                                    "updated": 1697066435,
                                    "recoveryLevel": "Recoverable+Purgeable",
                                    "recoverableDays": 90
                                }
                            },
                            {
                                "id": "https://aspiretests.vault.azure.net/secrets/super-secret-2",
                                "attributes": {
                                    "enabled": true,
                                    "created": 1692910062,
                                    "updated": 1692910062,
                                    "recoveryLevel": "Recoverable+Purgeable",
                                    "recoverableDays": 90
                                }
                            }
                        ],
                        "nextLink": null
                    }
                    """),
                CreateResponse("""
                    {
                        "value": "Secret 1 Value",
                        "id": "https://aspiretests.vault.azure.net/secrets/super-secret-1/1a78b8580ee548c3be0d146aab3817e2",
                        "attributes": {
                            "enabled": true,
                            "created": 1697066435,
                            "updated": 1697066435,
                            "recoveryLevel": "Recoverable+Purgeable",
                            "recoverableDays": 90
                        }
                    }
                    """{
                        "value": "Secret 1 Value",
                        "id": "https://aspiretests.vault.azure.net/secrets/super-secret-1/1a78b8580ee548c3be0d146aab3817e2",
                        "attributes": {
                            "enabled": true,
                            "created": 1697066435,
                            "updated": 1697066435,
                            "recoveryLevel": "Recoverable+Purgeable",
                            "recoverableDays": 90
                        }
                    }
                    """),
                CreateResponse("""
                    {
                        "value": "Secret 2 Value",
                        "id": "https://aspiretests.vault.azure.net/secrets/super-secret-2/9b8cc4d1ba7941a9b62c3168a17c039f",
                        "attributes": {
                            "enabled": true,
                            "created": 1692910062,
                            "updated": 1692910062,
                            "recoveryLevel": "Recoverable+Purgeable",
                            "recoverableDays": 90
                        }
                    }
                    """{
                        "value": "Secret 2 Value",
                        "id": "https://aspiretests.vault.azure.net/secrets/super-secret-2/9b8cc4d1ba7941a9b62c3168a17c039f",
                        "attributes": {
                            "enabled": true,
                            "created": 1692910062,
                            "updated": 1692910062,
                            "recoveryLevel": "Recoverable+Purgeable",
                            "recoverableDays": 90
                        }
                    }
                    """));
        });
 
        Assert.Equal("Secret 1 Value", builder.Configuration["super-secret-1"]);
        Assert.Equal("Secret 2 Value", builder.Configuration["super-secret-2"]);
    }
 
    private static MockResponse CreateResponse(string content)
    {
        var buffer = Encoding.UTF8.GetBytes(content);
        var response = new MockResponse(200)
        {
            ClientRequestId = Guid.NewGuid().ToString(),
            ContentStream = new MemoryStream(buffer),
        };
 
        // Add headers matching current response headers from Key Vault.
        response.AddHeader(new HttpHeader("Cache-Control", "no-cache"));
        response.AddHeader(new HttpHeader("Content-Length", buffer.Length.ToString(CultureInfo.InvariantCulture)));
        response.AddHeader(new HttpHeader("Content-Type", "application/json; charset=utf-8"));
        response.AddHeader(new HttpHeader("Date", DateTimeOffset.UtcNow.ToString("r", CultureInfo.InvariantCulture)));
        response.AddHeader(new HttpHeader("Expires", "-1"));
        response.AddHeader(new HttpHeader("Pragma", "no-cache"));
        response.AddHeader(new HttpHeader("Server", "Microsoft-IIS/10.0"));
        response.AddHeader(new HttpHeader("Strict-Transport-Security", "max-age=31536000;includeSubDomains"));
        response.AddHeader(new HttpHeader("X-AspNet-Version", "4.0.30319"));
        response.AddHeader(new HttpHeader("X-Content-Type-Options", "nosniff"));
        response.AddHeader(new HttpHeader("x-ms-keyvault-network-info", "addr=122.117.106.78;act_addr_fam=InterNetwork;"));
        response.AddHeader(new HttpHeader("x-ms-keyvault-region", "westus"));
        response.AddHeader(new HttpHeader("x-ms-keyvault-service-version", "1.1.0.875"));
        response.AddHeader(new HttpHeader("x-ms-request-id", response.ClientRequestId));
        response.AddHeader(new HttpHeader("X-Powered-By", "ASP.NET"));
 
        return response;
    }
 
    [Fact]
    public void CanAddMultipleKeyedServices()
    {
        var builder = Host.CreateEmptyApplicationBuilder(null);
        builder.Configuration.AddInMemoryCollection([
            new KeyValuePair<string, string?>("ConnectionStrings:secrets1", ConformanceTests.VaultUri),
            new KeyValuePair<string, string?>("ConnectionStrings:secrets2", "https://aspiretests2.vault.azure.net/"),
            new KeyValuePair<string, string?>("ConnectionStrings:secrets3", "https://aspiretests3.vault.azure.net/")
        ]);
 
        builder.AddAzureKeyVaultClient("secrets1");
        builder.AddKeyedAzureKeyVaultClient("secrets2");
        builder.AddKeyedAzureKeyVaultClient("secrets3");
 
        using var host = builder.Build();
 
        // Unkeyed services don't work with keyed services. See https://github.com/dotnet/aspire/issues/3890
        //var client1 = host.Services.GetRequiredService<SecretClient>();
        var client2 = host.Services.GetRequiredKeyedService<SecretClient>("secrets2");
        var client3 = host.Services.GetRequiredKeyedService<SecretClient>("secrets3");
 
        //Assert.NotSame(client1, client2);
        //Assert.NotSame(client1, client3);
        Assert.NotSame(client2, client3);
 
        //Assert.Equal(new Uri(ConformanceTests.VaultUri), client1.VaultUri);
        Assert.Equal(new Uri("https://aspiretests2.vault.azure.net/"), client2.VaultUri);
        Assert.Equal(new Uri("https://aspiretests3.vault.azure.net/"), client3.VaultUri);
    }
}