diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2c2207765a..827e471330 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -105,6 +105,7 @@
+
diff --git a/src/HealthChecks.Aws.Sns/SnsOptions.cs b/src/HealthChecks.Aws.Sns/SnsOptions.cs
index 793988566b..22767672a0 100644
--- a/src/HealthChecks.Aws.Sns/SnsOptions.cs
+++ b/src/HealthChecks.Aws.Sns/SnsOptions.cs
@@ -8,6 +8,7 @@ namespace HealthChecks.Aws.Sns;
///
public class SnsOptions
{
+ public string? ServiceURL { get; set; }
public AWSCredentials? Credentials { get; set; }
public RegionEndpoint? RegionEndpoint { get; set; }
diff --git a/src/HealthChecks.Aws.Sns/SnsTopicAndSubscriptionHealthCheck.cs b/src/HealthChecks.Aws.Sns/SnsTopicAndSubscriptionHealthCheck.cs
index 8b60fa9a1e..945c20527e 100644
--- a/src/HealthChecks.Aws.Sns/SnsTopicAndSubscriptionHealthCheck.cs
+++ b/src/HealthChecks.Aws.Sns/SnsTopicAndSubscriptionHealthCheck.cs
@@ -6,11 +6,12 @@ namespace HealthChecks.Aws.Sns;
public class SnsTopicAndSubscriptionHealthCheck : IHealthCheck
{
- private readonly SnsOptions _snsOptions;
+ private readonly SnsOptions _options;
+
public SnsTopicAndSubscriptionHealthCheck(SnsOptions snsOptions)
{
- _snsOptions = Guard.ThrowIfNull(snsOptions);
+ _options = Guard.ThrowIfNull(snsOptions);
}
///
@@ -20,7 +21,7 @@ public async Task CheckHealthAsync(HealthCheckContext context
{
using var client = CreateSnsClient();
- foreach (var (topicName, subscriptions) in _snsOptions.TopicsAndSubscriptions.Select(x => (x.Key, x.Value)))
+ foreach (var (topicName, subscriptions) in _options.TopicsAndSubscriptions.Select(x => (x.Key, x.Value)))
{
var topic = await client.FindTopicAsync(topicName).ConfigureAwait(false)
?? throw new NotFoundException($"Topic {topicName} does not exist.");
@@ -53,14 +54,22 @@ public async Task CheckHealthAsync(HealthCheckContext context
private AmazonSimpleNotificationServiceClient CreateSnsClient()
{
- bool credentialsProvided = _snsOptions.Credentials is not null;
- bool regionProvided = _snsOptions.RegionEndpoint is not null;
- return (credentialsProvided, regionProvided) switch
+ bool credentialsProvided = _options.Credentials is not null;
+ bool regionProvided = _options.RegionEndpoint is not null;
+
+ var config = new AmazonSimpleNotificationServiceConfig();
+
+ if (_options.ServiceURL is not null)
{
- (false, false) => new AmazonSimpleNotificationServiceClient(),
- (false, true) => new AmazonSimpleNotificationServiceClient(_snsOptions.RegionEndpoint),
- (true, false) => new AmazonSimpleNotificationServiceClient(_snsOptions.Credentials),
- (true, true) => new AmazonSimpleNotificationServiceClient(_snsOptions.Credentials, _snsOptions.RegionEndpoint)
- };
+ config.ServiceURL = _options.ServiceURL;
+ }
+ if (_options.RegionEndpoint is not null)
+ {
+ config.RegionEndpoint = _options.RegionEndpoint;
+ }
+
+ return _options.Credentials is not null
+ ? new AmazonSimpleNotificationServiceClient(_options.Credentials, config)
+ : new AmazonSimpleNotificationServiceClient(config);
}
}
diff --git a/test/HealthChecks.Aws.Sns.Tests/Functional/SnsHealthCheckTests.cs b/test/HealthChecks.Aws.Sns.Tests/Functional/SnsHealthCheckTests.cs
new file mode 100644
index 0000000000..884c49fb56
--- /dev/null
+++ b/test/HealthChecks.Aws.Sns.Tests/Functional/SnsHealthCheckTests.cs
@@ -0,0 +1,176 @@
+using System.Net;
+using Amazon.Runtime;
+using HealthChecks.Aws.Sns.Tests;
+
+namespace HealthChecks.Aws.Sqs.Tests.Functional;
+
+public class aws_sqs_healthcheck_should(LocalStackContainerFixture localStackFixture) : IClassFixture
+{
+ [Fact]
+ public async Task be_healthy_if_aws_sns_topic_is_available()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSnsTopicsAndSubscriptions(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddTopicAndSubscriptions("healthchecks");
+ },
+ tags: ["sns"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sns")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ }
+
+ [Fact]
+ public async Task be_healthy_if_aws_sns_multiple_queues_are_available()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSnsTopicsAndSubscriptions(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddTopicAndSubscriptions("healthchecks");
+ options.AddTopicAndSubscriptions("healthchecks");
+ },
+ tags: ["sns"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sns")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ }
+
+ [Fact]
+ public async Task be_unhealthy_if_aws_sns_is_unavailable()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSnsTopicsAndSubscriptions(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = "invalid";
+
+ options.AddTopicAndSubscriptions("healthchecks");
+ },
+ tags: ["sns"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sns")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
+ }
+
+ [Fact]
+ public async Task be_unhealthy_if_aws_sns_credentials_not_provided()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSnsTopicsAndSubscriptions(
+ options =>
+ {
+ options.ServiceURL = connectionString;
+
+ options.AddTopicAndSubscriptions("healthchecks");
+ },
+ tags: ["sns"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sns")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
+ }
+
+ [Fact]
+ public async Task be_unhealthy_if_aws_sns_topic_does_not_exist()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSnsTopicsAndSubscriptions(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddTopicAndSubscriptions("invalid");
+ },
+ tags: ["sns"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sns")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
+ }
+}
diff --git a/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.Tests.csproj b/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.Tests.csproj
index 3f66b2f33d..aeb5865e0e 100644
--- a/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.Tests.csproj
+++ b/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.Tests.csproj
@@ -1,5 +1,8 @@
+
+
+
diff --git a/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.approved.txt b/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.approved.txt
index f2b8e04fd7..67841ab7c5 100644
--- a/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.approved.txt
+++ b/test/HealthChecks.Aws.Sns.Tests/HealthChecks.Aws.Sns.approved.txt
@@ -5,6 +5,7 @@ namespace HealthChecks.Aws.Sns
public SnsOptions() { }
public Amazon.Runtime.AWSCredentials? Credentials { get; set; }
public Amazon.RegionEndpoint? RegionEndpoint { get; set; }
+ public string? ServiceURL { get; set; }
public HealthChecks.Aws.Sns.SnsOptions AddTopicAndSubscriptions(string topicName, System.Collections.Generic.IEnumerable? subscriptions = null) { }
}
public class SnsTopicAndSubscriptionHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
diff --git a/test/HealthChecks.Aws.Sns.Tests/LocalStackContainerFixture.cs b/test/HealthChecks.Aws.Sns.Tests/LocalStackContainerFixture.cs
new file mode 100644
index 0000000000..ffc261b652
--- /dev/null
+++ b/test/HealthChecks.Aws.Sns.Tests/LocalStackContainerFixture.cs
@@ -0,0 +1,45 @@
+using Testcontainers.LocalStack;
+
+
+namespace HealthChecks.Aws.Sns.Tests;
+
+public class LocalStackContainerFixture : IAsyncLifetime
+{
+ private const string Registry = "docker.io";
+
+ private const string Image = "localstack/localstack";
+
+ private const string Tag = "4.7.0";
+
+ public LocalStackContainer? Container { get; private set; }
+
+ public string GetConnectionString()
+ {
+ if (Container is null)
+ {
+ throw new InvalidOperationException("The test container was not initialized.");
+ }
+
+ return Container.GetConnectionString();
+ }
+
+ public async Task InitializeAsync()
+ {
+ Container = await CreateContainerAsync();
+
+ await Container.ExecAsync(["awslocal", "sns", "create-topic", "--topic-name", "healthchecks"]);
+ }
+
+ public Task DisposeAsync() => Container?.DisposeAsync().AsTask() ?? Task.CompletedTask;
+
+ private static async Task CreateContainerAsync()
+ {
+ var container = new LocalStackBuilder()
+ .WithImage($"{Registry}/{Image}:{Tag}")
+ .Build();
+
+ await container.StartAsync();
+
+ return container;
+ }
+}