File: ConformanceTests.cs
Web Access
Project: src\tests\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj (Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Components.ConformanceTests;
using Azure.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using Xunit;
 
namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests;
 
public class ConformanceTests : ConformanceTests<IConfigurationRefresherProvider, AzureAppConfigurationSettings>
{
    public const string Endpoint = "https://aspiretests.azconfig.io/";
 
    protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton;
 
    protected override string ActivitySourceName => "Microsoft.Extensions.Configuration.AzureAppConfiguration";
 
    protected override string[] RequiredLogCategories => new string[] { "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh" };
 
    protected override bool SupportsKeyedRegistrations => false;
 
    protected override bool IsComponentBuiltBeforeHost => true;
 
    protected override string ValidJsonConfig => """
        {
          "Aspire": {
            "Azure": {
              "AppConfiguration": {
                "Endpoint": "http://YOUR_URI",
                "Optional": true,
                "DisableTracing": false
              }
            }
          }
        }
        """;
 
    protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null)
        => configuration.AddInMemoryCollection(new KeyValuePair<string, string?>[]
        {
            new(CreateConfigKey("Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration", null, "Endpoint"), Endpoint)
        });
 
    protected override void RegisterComponent(HostApplicationBuilder builder, Action<AzureAppConfigurationSettings>? configure = null, string? key = null)
    {
        builder.AddAzureAppConfiguration(
            "appconfig",
            settings =>
            {
                configure?.Invoke(settings);
                settings.Credential = new EmptyTokenCredential();
                settings.Optional = true;
            },
            options =>
            {
                // AzureAppConfigurationOptions.MinBackoffDuration is internal, use reflection to set it to 1 second to facilitate testing.
                var minBackoffDurationProperty = options.GetType().GetProperty("MinBackoffDuration", BindingFlags.Instance | BindingFlags.NonPublic);
                minBackoffDurationProperty?.SetValue(options, TimeSpan.FromSeconds(1));
 
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("sentinel")
                        .SetRefreshInterval(TimeSpan.FromSeconds(1));
                });
                options.ConfigureStartupOptions(startupOptions =>
                {
                    startupOptions.Timeout = TimeSpan.FromSeconds(1);
                });
                options.ConfigureClientOptions(clientOptions => clientOptions.Retry.MaxRetries = 0);
            });
    }
 
    protected override (string json, string error)[] InvalidJsonToErrorMessage => new[]
        {
            ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "YOUR_URI"}}}}}}""", "Value does not match format \"uri\""),
            ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}}}""", "Value is \"string\" but should be \"boolean\""),
            ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "http://YOUR_URI", "DisableTracing": "true"}}}}}}""", "Value is \"string\" but should be \"boolean\""),
        };
 
    protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled)
        => options.DisableHealthChecks = !enabled;
 
    protected override void SetMetrics(AzureAppConfigurationSettings options, bool enabled)
        => throw new NotImplementedException();
 
    protected override void SetTracing(AzureAppConfigurationSettings options, bool enabled)
        => options.DisableTracing = !enabled;
 
    protected override void TriggerActivity(IConfigurationRefresherProvider service)
    {
        // wait for 1 seconds to ensure the refresh interval is exceeded
        Task.Delay(1000).ConfigureAwait(false).GetAwaiter().GetResult();
        service.Refreshers.First().RefreshAsync().ConfigureAwait(false).GetAwaiter().GetResult();
    }
 
    public ConformanceTests(ITestOutputHelper? output = null) : base(output)
    {
    }
 
    [Fact]
    public void TracingEnablesTheRightActivitySource()
        => RemoteInvokeWithLogging(() => new ConformanceTests().ActivitySourceTest(key: null), Output);
 
    internal sealed class EmptyTokenCredential : TokenCredential
    {
        public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
        {
            return new AccessToken(string.Empty, DateTimeOffset.MaxValue);
        }
 
        public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
        {
            return new ValueTask<AccessToken>(new AccessToken(string.Empty, DateTimeOffset.MaxValue));
        }
    }
}