From 1a0d86f3b1e3234b63954cdd377ff39903132187 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang <141655842+zhiyuanliang-ms@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:55:39 +0800 Subject: [PATCH 1/2] Bug fix: The registered failture status should be used when health check fails (#714) * fix unhealthy status * update * fix test --- .../AzureAppConfigurationHealthCheck.cs | 12 ++++- .../AzureAppConfigurationProvider.cs | 6 ++- .../Unit/HealthCheckTest.cs | 49 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs index b53d71e26..4877a6ca6 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationHealthCheck.cs @@ -37,17 +37,27 @@ public async Task CheckHealthAsync(HealthCheckContext context return HealthCheckResult.Unhealthy(HealthCheckConstants.NoProviderFoundMessage); } + HealthCheckResult worstResult = HealthCheckResult.Healthy(); + foreach (IHealthCheck healthCheck in _healthChecks) { var result = await healthCheck.CheckHealthAsync(context, cancellationToken).ConfigureAwait(false); + // Keep track of the worst health status found + // HealthStatus enum is ordered: Unhealthy(0) < Degraded(1) < Healthy(2) + if (result.Status < worstResult.Status) + { + worstResult = result; + } + + // If an Unhealthy status is found, short-circuit since that's the worst possible if (result.Status == HealthStatus.Unhealthy) { return result; } } - return HealthCheckResult.Healthy(); + return worstResult; } private void FindHealthChecks(IConfigurationRoot configurationRoot, List healthChecks) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index aa0656d68..fc92d1290 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -574,15 +574,17 @@ public void ProcessPushNotification(PushNotification pushNotification, TimeSpan? public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + HealthStatus failureStatus = context.Registration?.FailureStatus ?? HealthStatus.Unhealthy; + if (!_lastSuccessfulAttempt.HasValue) { - return HealthCheckResult.Unhealthy(HealthCheckConstants.LoadNotCompletedMessage); + return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.LoadNotCompletedMessage); } if (_lastFailedAttempt.HasValue && _lastSuccessfulAttempt.Value < _lastFailedAttempt.Value) { - return HealthCheckResult.Unhealthy(HealthCheckConstants.RefreshFailedMessage); + return new HealthCheckResult(status: failureStatus, description: HealthCheckConstants.RefreshFailedMessage); } return HealthCheckResult.Healthy(); diff --git a/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs b/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs index 9dce8e826..4484def0f 100644 --- a/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs +++ b/tests/Tests.AzureAppConfiguration/Unit/HealthCheckTest.cs @@ -136,5 +136,54 @@ public async Task HealthCheckTests_RegisterAzureAppConfigurationHealthCheck() Assert.Contains(HealthCheckConstants.HealthCheckRegistrationName, result.Entries.Keys); Assert.Equal(HealthStatus.Healthy, result.Entries[HealthCheckConstants.HealthCheckRegistrationName].Status); } + + [Fact] + public async Task HealthCheckTests_ShouldRespectHealthCheckRegistration() + { + IConfigurationRefresher refresher = null; + var mockResponse = new Mock(); + var mockClient = new Mock(MockBehavior.Strict); + + mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny(), It.IsAny())) + .Returns(new MockAsyncPageable(kvCollection)) + .Throws(new RequestFailedException(503, "Request failed.")); + + var config = new ConfigurationBuilder() + .AddAzureAppConfiguration(options => + { + options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(mockClient.Object); + options.MinBackoffDuration = TimeSpan.FromSeconds(2); + options.ConfigurationSettingPageIterator = new MockConfigurationSettingPageIterator(); + options.ConfigureRefresh(refreshOptions => + { + refreshOptions.RegisterAll() + .SetRefreshInterval(TimeSpan.FromSeconds(1)); + }); + refresher = options.GetRefresher(); + }) + .Build(); + + var services = new ServiceCollection(); + services.AddSingleton(config); + services.AddLogging(); // add logging for health check service + services.AddHealthChecks() + .AddAzureAppConfiguration( + name: "TestName", + failureStatus: HealthStatus.Degraded); + var provider = services.BuildServiceProvider(); + var healthCheckService = provider.GetRequiredService(); + + var result = await healthCheckService.CheckHealthAsync(); + Assert.Equal(HealthStatus.Healthy, result.Status); + + // Wait for the refresh interval to expire + Thread.Sleep(2000); + + await refresher.TryRefreshAsync(); + result = await healthCheckService.CheckHealthAsync(); + Assert.Equal(HealthStatus.Degraded, result.Status); + Assert.Contains("TestName", result.Entries.Keys); + Assert.Equal(HealthStatus.Degraded, result.Entries["TestName"].Status); + } } } From 559badcc2f83e7329d37d2a1be842b85b6af92b3 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang <141655842+zhiyuanliang-ms@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:29:30 +0800 Subject: [PATCH 2/2] find IConfigurationRefresher (#715) --- .../AzureAppConfigurationRefresherProvider.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs index d2b310718..004a91133 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationRefresherProvider.cs @@ -63,10 +63,14 @@ private void FindRefreshers(IConfigurationRoot configurationRoot, ILoggerFactory { foreach (IConfigurationProvider provider in configurationRoot.Providers) { - if (provider is AzureAppConfigurationProvider appConfigurationProvider) + if (provider is IConfigurationRefresher configurationRefresher) { - appConfigurationProvider.LoggerFactory = loggerFactory; - refreshers.Add(appConfigurationProvider); + refreshers.Add(configurationRefresher); + + if (configurationRefresher is AzureAppConfigurationProvider azureAppConfigurationProvider) + { + azureAppConfigurationProvider.LoggerFactory = loggerFactory; + } } else if (provider is ChainedConfigurationProvider chainedProvider) {