diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6754c03..5804944 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,6 +15,7 @@
+
@@ -44,6 +45,7 @@
+
diff --git a/HealthChecks.sln b/HealthChecks.sln
index 4bd8157..31ce847 100644
--- a/HealthChecks.sln
+++ b/HealthChecks.sln
@@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Tests.Architecture", "tests\NetEvolve.HealthChecks.Tests.Architecture\NetEvolve.HealthChecks.Tests.Architecture.csproj", "{17BCA132-1FBB-46C1-B6A1-60F64969383D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.AWS.SNS", "src\NetEvolve.HealthChecks.AWS.SNS\NetEvolve.HealthChecks.AWS.SNS.csproj", "{546D5904-1811-457F-8C48-3F78D7F0C803}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -315,6 +317,18 @@ Global
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x64.Build.0 = Release|Any CPU
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.ActiveCfg = Release|Any CPU
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.Build.0 = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.Build.0 = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.Build.0 = Debug|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.Build.0 = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.ActiveCfg = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.Build.0 = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.ActiveCfg = Release|Any CPU
+ {546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -340,6 +354,7 @@ Global
{66406BE8-0281-4C95-B90B-20CAE4516A16} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
{2B089420-E791-44E7-B471-F6F527B33E1C} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
{17BCA132-1FBB-46C1-B6A1-60F64969383D} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
+ {546D5904-1811-457F-8C48-3F78D7F0C803} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28B4CC2B-39E8-49C0-9687-78121BD83A53}
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs b/src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs
new file mode 100644
index 0000000..9515cf8
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs
@@ -0,0 +1,6 @@
+namespace NetEvolve.HealthChecks.AWS.SNS;
+
+public enum CreationMode
+{
+ BasicAuthentication = 0,
+}
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/DependencyInjectionExtensions.cs b/src/NetEvolve.HealthChecks.AWS.SNS/DependencyInjectionExtensions.cs
new file mode 100644
index 0000000..a0ccdc7
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/DependencyInjectionExtensions.cs
@@ -0,0 +1,66 @@
+namespace NetEvolve.HealthChecks.AWS.SNS;
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using NetEvolve.Arguments;
+using NetEvolve.HealthChecks.Abstractions;
+
+///
+/// Extensions methods for with custom Health Checks.
+///
+public static class DependencyInjectionExtensions
+{
+ private static readonly string[] _defaultTags = ["aws", "sns", "message-queue"];
+
+ ///
+ /// Add a health check for AWS SimpleNotificationService (SNS).
+ ///
+ /// The .
+ /// The name of the .
+ /// An optional action to configure.
+ /// A list of additional tags that can be used to filter sets of health checks. Optional.
+ /// The is .
+ /// The is .
+ /// The is or whitespace.
+ /// The is already in use.
+ /// The is .
+ public static IHealthChecksBuilder AddSimpleNotificationService(
+ [NotNull] this IHealthChecksBuilder builder,
+ [NotNull] string name,
+ Action? options = null,
+ params string[] tags
+ )
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ Argument.ThrowIfNullOrEmpty(name);
+ ArgumentNullException.ThrowIfNull(tags);
+
+ if (!builder.IsServiceTypeRegistered())
+ {
+ _ = builder
+ .Services.AddSingleton()
+ .AddSingleton()
+ .ConfigureOptions();
+ }
+
+ if (builder.IsNameAlreadyUsed(name))
+ {
+ throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null);
+ }
+
+ if (options is not null)
+ {
+ _ = builder.Services.Configure(name, options);
+ }
+
+ return builder.AddCheck(
+ name,
+ HealthStatus.Unhealthy,
+ _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase)
+ );
+ }
+
+ private sealed partial class SimpleNotificationServiceCheckMarker { }
+}
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/NetEvolve.HealthChecks.AWS.SNS.csproj b/src/NetEvolve.HealthChecks.AWS.SNS/NetEvolve.HealthChecks.AWS.SNS.csproj
new file mode 100644
index 0000000..44b417d
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/NetEvolve.HealthChecks.AWS.SNS.csproj
@@ -0,0 +1,19 @@
+
+
+
+ $(_ProjectTargetFrameworks)
+
+ Contains HealthChecks for AWS Simple Notification Service (SNS).
+ $(PackageTags);aws;sns;
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceConfigure.cs b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceConfigure.cs
new file mode 100644
index 0000000..8544d8b
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceConfigure.cs
@@ -0,0 +1,15 @@
+namespace NetEvolve.HealthChecks.AWS.SNS;
+
+using Microsoft.Extensions.Options;
+
+internal sealed class SimpleNotificationServiceConfigure
+ : IConfigureNamedOptions,
+ IValidateOptions
+{
+ public void Configure(string? name, SimpleNotificationServiceOptions options) { }
+
+ public void Configure(SimpleNotificationServiceOptions options) { }
+
+ public ValidateOptionsResult Validate(string? name, SimpleNotificationServiceOptions options) =>
+ ValidateOptionsResult.Success;
+}
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceHealthCheck.cs b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceHealthCheck.cs
new file mode 100644
index 0000000..0628087
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceHealthCheck.cs
@@ -0,0 +1,69 @@
+namespace NetEvolve.HealthChecks.AWS.SNS;
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amazon;
+using Amazon.SimpleNotificationService;
+using Amazon.SimpleNotificationService.Model;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Options;
+using NetEvolve.Extensions.Tasks;
+using NetEvolve.HealthChecks.Abstractions;
+
+internal sealed class SimpleNotificationServiceHealthCheck
+ : ConfigurableHealthCheckBase
+{
+ public SimpleNotificationServiceHealthCheck(
+ IOptionsMonitor optionsMonitor
+ )
+ : base(optionsMonitor) { }
+
+ protected override async ValueTask ExecuteHealthCheckAsync(
+ string name,
+ HealthStatus failureStatus,
+ SimpleNotificationServiceOptions options,
+ CancellationToken cancellationToken
+ )
+ {
+ using var client = CreateClient(options);
+
+ var (isValid, topic) = await client
+ .GetSubscriptionAttributesAsync(
+ new GetSubscriptionAttributesRequest { SubscriptionArn = options.TopicName },
+ cancellationToken
+ )
+ .WithTimeoutAsync(options.Timeout, cancellationToken)
+ .ConfigureAwait(false);
+
+ return HealthCheckState(isValid && topic is not null, name);
+ }
+
+ private static AmazonSimpleNotificationServiceClient CreateClient(
+ SimpleNotificationServiceOptions options
+ )
+ {
+ var hasCredentials = options.GetCredentials() is not null;
+ var hasEndpoint = options.GetRegionEndpoint() is not null;
+
+ var config = new AmazonSimpleNotificationServiceConfig
+ {
+ ServiceURL = options.ServiceUrl,
+ RegionEndpoint = RegionEndpoint.USEast1,
+ };
+
+ return (hasCredentials, hasEndpoint) switch
+ {
+ (true, true) => new AmazonSimpleNotificationServiceClient(
+ options.GetCredentials(),
+ options.GetRegionEndpoint()
+ ),
+ (true, false) => new AmazonSimpleNotificationServiceClient(
+ options.GetCredentials(),
+ config
+ ),
+ (false, true) => new AmazonSimpleNotificationServiceClient(options.GetRegionEndpoint()),
+ _ => throw new InvalidOperationException("Invalid ClientCreationMode."),
+ };
+ }
+}
diff --git a/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
new file mode 100644
index 0000000..d643540
--- /dev/null
+++ b/src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
@@ -0,0 +1,33 @@
+namespace NetEvolve.HealthChecks.AWS.SNS;
+
+using System;
+using Amazon;
+using Amazon.Runtime;
+
+public sealed class SimpleNotificationServiceOptions
+{
+ public string? AccessKey { get; set; }
+
+ public CreationMode CreationMode { get; set; }
+
+ public string? SecretKey { get; set; }
+
+#pragma warning disable CA1056 // URI-like properties should not be strings
+ public string? ServiceUrl { get; set; }
+#pragma warning restore CA1056 // URI-like properties should not be strings
+
+ public string? TopicName { get; set; }
+
+ public int Timeout { get; set; } = 100;
+
+ internal AWSCredentials? GetCredentials()
+ {
+ return CreationMode switch
+ {
+ CreationMode.BasicAuthentication => new BasicAWSCredentials(AccessKey, SecretKey),
+ _ => null,
+ };
+ }
+
+ internal RegionEndpoint? GetRegionEndpoint() => CreationMode == CreationMode ? null : null;
+}
diff --git a/tests/NetEvolve.HealthChecks.Tests.Architecture/HealthCheckArchitecture.cs b/tests/NetEvolve.HealthChecks.Tests.Architecture/HealthCheckArchitecture.cs
index 8646c7f..e8e5ad7 100644
--- a/tests/NetEvolve.HealthChecks.Tests.Architecture/HealthCheckArchitecture.cs
+++ b/tests/NetEvolve.HealthChecks.Tests.Architecture/HealthCheckArchitecture.cs
@@ -19,10 +19,15 @@ private static Architecture LoadArchitecture()
{
System.Reflection.Assembly[] assemblies =
[
+ // Apache
typeof(Apache.Kafka.KafkaCheck).Assembly,
+ // AWS
+ typeof(AWS.SNS.SimpleNotificationServiceHealthCheck).Assembly,
+ // Azure
typeof(Azure.Blobs.BlobContainerAvailableHealthCheck).Assembly,
typeof(Azure.Queues.QueueClientAvailableHealthCheck).Assembly,
typeof(Azure.Tables.TableClientAvailableHealthCheck).Assembly,
+ // others
typeof(ClickHouse.ClickHouseCheck).Assembly,
typeof(Dapr.DaprHealthCheck).Assembly,
typeof(MySql.MySqlCheck).Assembly,
diff --git a/tests/NetEvolve.HealthChecks.Tests.Architecture/NetEvolve.HealthChecks.Tests.Architecture.csproj b/tests/NetEvolve.HealthChecks.Tests.Architecture/NetEvolve.HealthChecks.Tests.Architecture.csproj
index eade4cb..b861184 100644
--- a/tests/NetEvolve.HealthChecks.Tests.Architecture/NetEvolve.HealthChecks.Tests.Architecture.csproj
+++ b/tests/NetEvolve.HealthChecks.Tests.Architecture/NetEvolve.HealthChecks.Tests.Architecture.csproj
@@ -23,6 +23,7 @@
+
diff --git a/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/LocalStackInstance.cs b/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/LocalStackInstance.cs
new file mode 100644
index 0000000..f1cde20
--- /dev/null
+++ b/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/LocalStackInstance.cs
@@ -0,0 +1,24 @@
+namespace NetEvolve.HealthChecks.Tests.Integration.AWS;
+
+using System.Threading.Tasks;
+using Testcontainers.LocalStack;
+using TestContainer = Testcontainers.LocalStack.LocalStackContainer;
+
+public sealed class LocalStackInstance : IAsyncLifetime
+{
+ /// Access Key
+ ///
+ internal const string AccessKey = "LSIAQAAAAAAVNCBMPNSG";
+ internal const string SecretKey = "test";
+
+ private readonly TestContainer _container = new LocalStackBuilder()
+ .WithEnvironment("AWS_ACCESS_KEY_ID", AccessKey)
+ .WithEnvironment("AWS_SECRET_ACCESS_KEY", SecretKey)
+ .Build();
+
+ internal string ConnectionString => _container.GetConnectionString();
+
+ public async Task DisposeAsync() => await _container.DisposeAsync().ConfigureAwait(false);
+
+ public async Task InitializeAsync() => await _container.StartAsync().ConfigureAwait(false);
+}
diff --git a/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/SNS/SimpleNotificationServiceHealthCheckTests.cs b/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/SNS/SimpleNotificationServiceHealthCheckTests.cs
new file mode 100644
index 0000000..e80c7bd
--- /dev/null
+++ b/tests/NetEvolve.HealthChecks.Tests.Integration/AWS/SNS/SimpleNotificationServiceHealthCheckTests.cs
@@ -0,0 +1,31 @@
+namespace NetEvolve.HealthChecks.Tests.Integration.AWS.SNS;
+
+using NetEvolve.HealthChecks.AWS.SNS;
+using NodaTime;
+
+public class SimpleNotificationServiceHealthCheckTests
+ : HealthCheckTestBase,
+ IClassFixture
+{
+ private readonly LocalStackInstance _instance;
+
+ public SimpleNotificationServiceHealthCheckTests(LocalStackInstance instance)
+ {
+ _instance = instance;
+ }
+
+ [Fact]
+ public async Task AddSimpleNotificationService_UseOptionsCreate_ShouldReturnHealthy() =>
+ await RunAndVerify(healthChecks =>
+ {
+ _ = healthChecks.AddSimpleNotificationService(
+ "TestContainerHealthy",
+ options =>
+ {
+ options.AccessKey = LocalStackInstance.AccessKey;
+ options.SecretKey = LocalStackInstance.SecretKey;
+ options.ServiceUrl = _instance.ConnectionString;
+ }
+ );
+ });
+}
diff --git a/tests/NetEvolve.HealthChecks.Tests.Integration/NetEvolve.HealthChecks.Tests.Integration.csproj b/tests/NetEvolve.HealthChecks.Tests.Integration/NetEvolve.HealthChecks.Tests.Integration.csproj
index d22a7ed..37d5552 100644
--- a/tests/NetEvolve.HealthChecks.Tests.Integration/NetEvolve.HealthChecks.Tests.Integration.csproj
+++ b/tests/NetEvolve.HealthChecks.Tests.Integration/NetEvolve.HealthChecks.Tests.Integration.csproj
@@ -19,6 +19,7 @@
+
@@ -36,6 +37,7 @@
+
diff --git a/tests/NetEvolve.HealthChecks.Tests.Integration/_snapshots/SimpleNotificationServiceHealthCheckTests.AddSimpleNotificationService_UseOptionsCreate_ShouldReturnHealthy.verified.txt b/tests/NetEvolve.HealthChecks.Tests.Integration/_snapshots/SimpleNotificationServiceHealthCheckTests.AddSimpleNotificationService_UseOptionsCreate_ShouldReturnHealthy.verified.txt
new file mode 100644
index 0000000..6ce74a9
--- /dev/null
+++ b/tests/NetEvolve.HealthChecks.Tests.Integration/_snapshots/SimpleNotificationServiceHealthCheckTests.AddSimpleNotificationService_UseOptionsCreate_ShouldReturnHealthy.verified.txt
@@ -0,0 +1,25 @@
+{
+ results: [
+ {
+ description: TestContainerHealthy: Unexpected error.,
+ exception: {
+ innerExceptions: [
+ {
+ message: Exception of type 'Amazon.Runtime.Internal.HttpErrorResponseException' was thrown.,
+ type: HttpErrorResponseException
+ }
+ ],
+ message: The security token included in the request is invalid.,
+ type: Amazon.SimpleNotificationService.AmazonSimpleNotificationServiceException
+ },
+ name: TestContainerHealthy,
+ status: Unhealthy,
+ tags: [
+ aws,
+ sns,
+ message-queue
+ ]
+ }
+ ],
+ status: Unhealthy
+}
\ No newline at end of file