diff --git a/src/Silverback.Core/Justifications.cs b/src/Silverback.Core/Justifications.cs
index 84c485960..b9f4ab7a8 100644
--- a/src/Silverback.Core/Justifications.cs
+++ b/src/Silverback.Core/Justifications.cs
@@ -37,5 +37,8 @@ internal static class Justifications
public const string ExecutesSyncOrAsync =
"The method executes either synchronously or asynchronously";
+
+ public const string CatchAllExceptions =
+ "All exception types require catching";
}
}
diff --git a/src/Silverback.Integration.HealthChecks/Messaging/HealthChecks/OutboxQueueHealthCheck.cs b/src/Silverback.Integration.HealthChecks/Messaging/HealthChecks/OutboxQueueHealthCheck.cs
index bd5339047..81d904cce 100644
--- a/src/Silverback.Integration.HealthChecks/Messaging/HealthChecks/OutboxQueueHealthCheck.cs
+++ b/src/Silverback.Integration.HealthChecks/Messaging/HealthChecks/OutboxQueueHealthCheck.cs
@@ -42,20 +42,28 @@ public OutboxQueueHealthCheck(IOutboundQueueHealthCheckService service)
public static int? MaxQueueLength { get; set; }
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = Justifications.CatchAllExceptions)]
public async Task CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
Check.NotNull(context, nameof(context));
- if (await _service.CheckIsHealthyAsync(MaxMessageAge).ConfigureAwait(false))
- return new HealthCheckResult(HealthStatus.Healthy);
+ try
+ {
+ if (await _service.CheckIsHealthyAsync(MaxMessageAge).ConfigureAwait(false))
+ return new HealthCheckResult(HealthStatus.Healthy);
- string errorMessage = "The outbound queue exceeded the configured limits " +
- $"(max message age: {MaxMessageAge.ToString()}, " +
- $"max queue length: {MaxQueueLength?.ToString(CultureInfo.InvariantCulture) ?? "-"}).";
+ string errorMessage = "The outbound queue exceeded the configured limits " +
+ $"(max message age: {MaxMessageAge.ToString()}, " +
+ $"max queue length: {MaxQueueLength?.ToString(CultureInfo.InvariantCulture) ?? "-"}).";
- return new HealthCheckResult(context.Registration.FailureStatus, errorMessage);
+ return new HealthCheckResult(context.Registration.FailureStatus, errorMessage);
+ }
+ catch (Exception ex)
+ {
+ return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
+ }
}
}
}
diff --git a/tests/Silverback.Integration.HealthChecks.Tests/Messaging/HealthChecks/OutboxQueueHealthCheckTests.cs b/tests/Silverback.Integration.HealthChecks.Tests/Messaging/HealthChecks/OutboxQueueHealthCheckTests.cs
new file mode 100644
index 000000000..efdcd05ef
--- /dev/null
+++ b/tests/Silverback.Integration.HealthChecks.Tests/Messaging/HealthChecks/OutboxQueueHealthCheckTests.cs
@@ -0,0 +1,119 @@
+// Copyright (c) 2020 Sergio Aquilini
+// This code is licensed under MIT license (see LICENSE file for details)
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using Silverback.Database;
+using Silverback.Messaging.HealthChecks;
+using Silverback.Messaging.Outbound.TransactionalOutbox.Repositories;
+using Xunit;
+
+namespace Silverback.Tests.Integration.HealthChecks.Messaging.HealthChecks
+{
+ public class OutboxQueueHealthCheckTests
+ {
+ [Fact]
+ public async Task CheckHealthAsync_ExceptionThrown_UnhealthyReturned()
+ {
+ var outboundQueueHealthCheckService = Substitute.For();
+ outboundQueueHealthCheckService.CheckIsHealthyAsync(Arg.Any()).ThrowsAsync(new TimeoutException());
+
+ var outboundQueueHealthCheck = new OutboxQueueHealthCheck(outboundQueueHealthCheckService);
+
+ var serviceProvider = ServiceProviderHelper.GetServiceProvider(
+ services => services
+ .AddSilverback()
+ .WithConnectionToMessageBroker(options => options
+ .AddKafka()
+ .AddOutbox())
+ .Services
+ .AddSingleton(outboundQueueHealthCheckService)
+ .AddSingleton(Substitute.For())
+ .AddHealthChecks()
+ .Add(new HealthCheckRegistration("OutboundQueue", outboundQueueHealthCheck, default, default)));
+
+ var (healthCheck, context) = GetHealthCheck(serviceProvider);
+
+ var result = await healthCheck.CheckHealthAsync(context);
+
+ result.Status.Should().Be(HealthStatus.Unhealthy);
+ }
+
+ [Fact]
+ public async Task CheckHealthAsync_ExceptionThrown_ExceptionReturned()
+ {
+ var outboundQueueHealthCheckService = Substitute.For();
+ TimeoutException exceptionToBeThrown = new TimeoutException();
+ outboundQueueHealthCheckService.CheckIsHealthyAsync(Arg.Any()).ThrowsAsync(exceptionToBeThrown);
+
+ var outboundQueueHealthCheck = new OutboxQueueHealthCheck(outboundQueueHealthCheckService);
+
+ var serviceProvider = ServiceProviderHelper.GetServiceProvider(
+ services => services
+ .AddSilverback()
+ .WithConnectionToMessageBroker(options => options
+ .AddKafka()
+ .AddOutbox())
+ .Services
+ .AddSingleton(outboundQueueHealthCheckService)
+ .AddSingleton(Substitute.For())
+ .AddHealthChecks()
+ .Add(new HealthCheckRegistration("OutboundQueue", outboundQueueHealthCheck, default, default)));
+
+ var (healthCheck, context) = GetHealthCheck(serviceProvider);
+
+ var result = await healthCheck.CheckHealthAsync(context);
+
+ result.Exception.Should().Be(exceptionToBeThrown);
+ }
+
+ [Theory]
+ [InlineData(true, HealthStatus.Healthy)]
+ [InlineData(false, HealthStatus.Unhealthy)]
+ public async Task CheckHealthAsync_HealthyCheck_ReturnsExpectedStatus(bool healthy, HealthStatus expectedStatus)
+ {
+ var outboundQueueHealthCheckService = Substitute.For();
+ outboundQueueHealthCheckService.CheckIsHealthyAsync(Arg.Any()).Returns(healthy);
+
+ var outboundQueueHealthCheck = new OutboxQueueHealthCheck(outboundQueueHealthCheckService);
+
+ var serviceProvider = ServiceProviderHelper.GetServiceProvider(
+ services => services
+ .AddSilverback()
+ .WithConnectionToMessageBroker(options => options
+ .AddKafka()
+ .AddOutbox())
+ .Services
+ .AddSingleton(outboundQueueHealthCheckService)
+ .AddSingleton(Substitute.For())
+ .AddHealthChecks()
+ .Add(new HealthCheckRegistration("OutboundQueue", outboundQueueHealthCheck, default, default)));
+
+ var (healthCheck, context) = GetHealthCheck(serviceProvider);
+
+ var result = await healthCheck.CheckHealthAsync(context);
+
+ result.Status.Should().Be(expectedStatus);
+ }
+
+ private static (IHealthCheck HealthCheck, HealthCheckContext Context) GetHealthCheck(IServiceProvider serviceProvider)
+ {
+ var healthCheckOptions = serviceProvider
+ .GetRequiredService>();
+
+ var context = new HealthCheckContext
+ {
+ Registration = healthCheckOptions.Value.Registrations.First()
+ };
+
+ return (context.Registration.Factory.Invoke(serviceProvider), context);
+ }
+ }
+}
diff --git a/tests/Silverback.Integration.HealthChecks.Tests/Silverback.Integration.HealthChecks.Tests.csproj b/tests/Silverback.Integration.HealthChecks.Tests/Silverback.Integration.HealthChecks.Tests.csproj
index fee155e8c..31bc6de0b 100644
--- a/tests/Silverback.Integration.HealthChecks.Tests/Silverback.Integration.HealthChecks.Tests.csproj
+++ b/tests/Silverback.Integration.HealthChecks.Tests/Silverback.Integration.HealthChecks.Tests.csproj
@@ -27,6 +27,7 @@
+