Skip to content

Commit

Permalink
[Extensions] Fix ASP.NET Core integration testing (#48426)
Browse files Browse the repository at this point in the history
* [Extensions] Fix ASP.NET Core integration testing

The focus of these changes is to fix an assumption
in the logic when creating clients from configuration.
Currently, if the configuration section has a non-null
value, it is assumed to be a connection string.
This is not the case for ASP.NET Core test integration
due to a bug in the test host which causes the Value
property to return an empty string rather than the
null returned by the actual configuration system.
  • Loading branch information
jsquire authored Feb 27, 2025
1 parent f072158 commit 6fcabfc
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 9 deletions.
2 changes: 2 additions & 0 deletions sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bugs Fixed

- Fixed an issue when creating clients from configuration using the ASP.NET Core integration testing library, `Microsoft.AspNetCore.Mvc.Testing`. Due to a difference in how an section value is represented, logic was interpreting a setting with a child object as an empty connection string value. The child object is now properly parsed and applied for client construction. ([#48368](https://github.com/Azure/azure-sdk-for-net/issues/48368))

### Other Changes

## 1.10.0 (2025-02-11)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ public static object CreateClient(
{
List<object> arguments = new List<object>();
// Handle single values as connection strings
if (configuration is IConfigurationSection section && section.Value != null)
if (configuration is IConfigurationSection section && (!string.IsNullOrEmpty(section.Value)))
{
var connectionString = section.Value;
configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
.AddInMemoryCollection(
[
new KeyValuePair<string, string>(ConnectionStringParameterName, connectionString)
})
])
.Build();
}
foreach (var constructor in clientType.GetConstructors().OrderByDescending(c => c.GetParameters().Length))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="8.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
using Microsoft.Extensions.Configuration;
using NUnit.Framework;

#if NET8_0_OR_GREATER
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
#endif

namespace Azure.Core.Extensions.Tests
{
public class ClientFactoryTests
Expand Down Expand Up @@ -626,6 +633,37 @@ public void CanUseAzureSasCredential()
Assert.AreEqual("key", client.AzureSasCredential.Signature);
}

#if NET8_0_OR_GREATER
[Test]
public async Task AllowsAspNetCoreIntegrationTestHostConfiguration()
{
var expectedKeyVaultUriValue = "https://fake.vault.azure.net/";

// When configuration is set using the test host, the behavior of IConfigurationSection
// changes and the Value property is not null for a complex object. Instead it returns an
// empty string, which we don't want to treat as a connection string.
//
// This is a bug in the configuration system, but there's no commitment for when it will
// be fixed. See: https://github.com/dotnet/aspnetcore/issues/37680
//
var factory = new WebApplicationFactory<AspNetHost>()
.WithWebHostBuilder(builder =>
{
builder.UseContentRoot("aspnet-host");

builder.UseConfiguration(
GetConfiguration(
new KeyValuePair<string, string>("KeyVault:VaultUri", expectedKeyVaultUriValue)));
});

var client = factory.CreateClient();
var response = await client.GetAsync("/keyvault");
var keyVaultUriValue = await response.Content.ReadAsStringAsync();

Assert.AreEqual(expectedKeyVaultUriValue, keyVaultUriValue);
}
#endif

private IConfiguration GetConfiguration(params KeyValuePair<string, string>[] items)
{
return new ConfigurationBuilder().AddInMemoryCollection(items).Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,50 @@
<PropertyGroup>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<None Include="aspnet-host\AspNetHostRoot.sln" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="8.0.1" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="System.Net.WebSockets.Client" />
<PackageReference Include="System.ValueTuple" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" />
<PackageReference Include="System.Net.WebSockets.Client" />
<PackageReference Include="System.ValueTuple" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(AzureCoreTestFramework)" />
<ProjectReference Include="..\src\Microsoft.Extensions.Azure.csproj" />
</ItemGroup>

<!-- Include ASPNET content for testing compatibility with the integration framework on net8+ only -->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />

<!--
Override the ASPNET Core testing package locally until the 8.x bump in the engineering system.
Tracked by: #48425
-->
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" VersionOverride="8.0.13" />
</ItemGroup>

<!-- Don't include the generated entry point for net8+ because one is explicitly defined in the ASPNET content -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>

<!-- Include the solution and content root markers for the ASPNET Core application -->
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
<Copy SourceFiles="aspnet-host\AspNetHostRoot.sln" DestinationFolder="$(OutDir)" />
</Target>

<ItemGroup>
<None Update="aspnet-host\appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#if NET8_0_OR_GREATER

using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;

namespace Azure.Core.Extensions.Tests;

public class AspNetHost
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Environment.ContentRootPath = "aspnet-host";

builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddSecretClient(builder.Configuration.GetSection("KeyVault"));
});

var app = builder.Build();
var secretClient = app.Services.GetRequiredService<SecretClient>();

app.MapGet("/keyvault", () => secretClient.VaultUri.AbsoluteUri);
app.Run();
}
}
#endif
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"KeyVault": {
"VaultUri": "<vault_uri>"
}
}

0 comments on commit 6fcabfc

Please sign in to comment.