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.Sqs/README.md b/src/HealthChecks.Aws.Sqs/README.md
index e0b87da02f..7d80ab5e30 100644
--- a/src/HealthChecks.Aws.Sqs/README.md
+++ b/src/HealthChecks.Aws.Sqs/README.md
@@ -13,7 +13,7 @@ With all of the following examples, you can additionally add the following param
### Basic
-### Check existence of a queue and load credentials from the application's default configuration
+### Check the existence of a queue and load credentials from the application's default configuration
```csharp
public void ConfigureServices(IServiceCollection services)
@@ -27,7 +27,7 @@ public void ConfigureServices(IServiceCollection services)
}
```
-### Check existence of a queue and directly pass credentials
+### Check the existence of a queue and directly pass credentials
```csharp
public void ConfigureServices(IServiceCollection services)
@@ -42,7 +42,7 @@ public void ConfigureServices(IServiceCollection services)
}
```
-### Check existence of a queue and specify region endpoint
+### Check the existence of a queue and specify region endpoint
```csharp
public void ConfigureServices(IServiceCollection services)
@@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services)
}
```
-### Check existence of a queue and specify credentials with region endpoint
+### Check the existence of a queue and specify credentials with region endpoint
```csharp
public void ConfigureServices(IServiceCollection services)
@@ -72,3 +72,18 @@ public void ConfigureServices(IServiceCollection services)
});
}
```
+
+### Check the existence of a queue and specify service URL
+
+```csharp
+public void ConfigureServices(IServiceCollection services)
+{
+ services
+ .AddHealthChecks()
+ .AddSqs(options =>
+ {
+ options.AddQueue("queueName");
+ options.ServiceURL = "http://localhost:4566";
+ });
+}
+```
diff --git a/src/HealthChecks.Aws.Sqs/SqsHealthCheck.cs b/src/HealthChecks.Aws.Sqs/SqsHealthCheck.cs
index 59889524e5..c960b626e6 100644
--- a/src/HealthChecks.Aws.Sqs/SqsHealthCheck.cs
+++ b/src/HealthChecks.Aws.Sqs/SqsHealthCheck.cs
@@ -5,11 +5,11 @@ namespace HealthChecks.Aws.Sqs;
public class SqsHealthCheck : IHealthCheck
{
- private readonly SqsOptions _sqsOptions;
+ private readonly SqsOptions _options;
public SqsHealthCheck(SqsOptions sqsOptions)
{
- _sqsOptions = Guard.ThrowIfNull(sqsOptions);
+ _options = Guard.ThrowIfNull(sqsOptions);
}
///
@@ -18,29 +18,36 @@ public async Task CheckHealthAsync(HealthCheckContext context
try
{
using var client = CreateSqsClient();
- foreach (var queueName in _sqsOptions.Queues)
+
+ foreach (string queueName in _options.Queues)
{
- _ = await client.GetQueueUrlAsync(queueName).ConfigureAwait(false);
+ await client.GetQueueUrlAsync(queueName, cancellationToken).ConfigureAwait(false);
}
return HealthCheckResult.Healthy();
}
- catch (Exception ex)
+ catch (Exception e)
{
- return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
+ return new HealthCheckResult(context.Registration.FailureStatus, exception: e);
}
}
- private IAmazonSQS CreateSqsClient()
+ private AmazonSQSClient CreateSqsClient()
{
- var credentialsProvided = _sqsOptions.Credentials is not null;
- var regionProvided = _sqsOptions.RegionEndpoint is not null;
- return (credentialsProvided, regionProvided) switch
+ var config = new AmazonSQSConfig();
+
+ if (_options.ServiceURL is not null)
{
- (false, false) => new AmazonSQSClient(),
- (false, true) => new AmazonSQSClient(_sqsOptions.RegionEndpoint),
- (true, false) => new AmazonSQSClient(_sqsOptions.Credentials),
- (true, true) => new AmazonSQSClient(_sqsOptions.Credentials, _sqsOptions.RegionEndpoint)
- };
+ config.ServiceURL = _options.ServiceURL;
+ }
+
+ if (_options.RegionEndpoint is not null)
+ {
+ config.RegionEndpoint = _options.RegionEndpoint;
+ }
+
+ return _options.Credentials is not null
+ ? new AmazonSQSClient(_options.Credentials, config)
+ : new AmazonSQSClient(config);
}
}
diff --git a/src/HealthChecks.Aws.Sqs/SqsOptions.cs b/src/HealthChecks.Aws.Sqs/SqsOptions.cs
index b2549ad381..33b01df3cd 100644
--- a/src/HealthChecks.Aws.Sqs/SqsOptions.cs
+++ b/src/HealthChecks.Aws.Sqs/SqsOptions.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Amazon;
using Amazon.Runtime;
@@ -8,11 +9,14 @@ namespace HealthChecks.Aws.Sqs;
///
public class SqsOptions
{
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public string? ServiceURL { get; set; }
+
public AWSCredentials? Credentials { get; set; }
public RegionEndpoint? RegionEndpoint { get; set; }
- internal HashSet Queues { get; } = new HashSet();
+ internal HashSet Queues { get; } = [];
///
/// Add an AWS SQS queue to be checked.
diff --git a/test/HealthChecks.Aws.Sqs.Tests/Functional/SqsHealthCheckTests.cs b/test/HealthChecks.Aws.Sqs.Tests/Functional/SqsHealthCheckTests.cs
new file mode 100644
index 0000000000..e602ed0e29
--- /dev/null
+++ b/test/HealthChecks.Aws.Sqs.Tests/Functional/SqsHealthCheckTests.cs
@@ -0,0 +1,175 @@
+using System.Net;
+using Amazon.Runtime;
+
+namespace HealthChecks.Aws.Sqs.Tests.Functional;
+
+public class aws_sqs_healthcheck_should(LocalStackContainerFixture localStackFixture) : IClassFixture
+{
+ [Fact]
+ public async Task be_healthy_if_aws_sqs_queue_is_available()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSqs(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddQueue("healthchecks");
+ },
+ tags: ["sqs"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sqs")
+ });
+ });
+
+ 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_sqs_multiple_queues_are_available()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSqs(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddQueue("healthchecks");
+ options.AddQueue("healthchecks");
+ },
+ tags: ["sqs"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sqs")
+ });
+ });
+
+ 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_sqs_is_unavailable()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSqs(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = "invalid";
+
+ options.AddQueue("healthchecks");
+ },
+ tags: ["sqs"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sqs")
+ });
+ });
+
+ 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_sqs_credentials_not_provided()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSqs(
+ options =>
+ {
+ options.ServiceURL = connectionString;
+
+ options.AddQueue("healthchecks");
+ },
+ tags: ["sqs"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sqs")
+ });
+ });
+
+ 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_sqs_queue_does_not_exist()
+ {
+ string connectionString = localStackFixture.GetConnectionString();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddSqs(
+ options =>
+ {
+ options.Credentials = new BasicAWSCredentials("test", "test");
+ options.ServiceURL = connectionString;
+
+ options.AddQueue("invalid");
+ },
+ tags: ["sqs"]);
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("sqs")
+ });
+ });
+
+ 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.Sqs.Tests/HealthChecks.Aws.Sqs.Tests.csproj b/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.Tests.csproj
index 92e6e96ddd..c16df9aec1 100644
--- a/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.Tests.csproj
+++ b/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.Tests.csproj
@@ -4,4 +4,8 @@
+
+
+
+
diff --git a/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.approved.txt b/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.approved.txt
index 6b3f47151d..5b99739720 100644
--- a/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.approved.txt
+++ b/test/HealthChecks.Aws.Sqs.Tests/HealthChecks.Aws.Sqs.approved.txt
@@ -10,6 +10,7 @@ namespace HealthChecks.Aws.Sqs
public SqsOptions() { }
public Amazon.Runtime.AWSCredentials? Credentials { get; set; }
public Amazon.RegionEndpoint? RegionEndpoint { get; set; }
+ public string? ServiceURL { get; set; }
public HealthChecks.Aws.Sqs.SqsOptions AddQueue(string queueName) { }
}
}
diff --git a/test/HealthChecks.Aws.Sqs.Tests/LocalStackContainerFixture.cs b/test/HealthChecks.Aws.Sqs.Tests/LocalStackContainerFixture.cs
new file mode 100644
index 0000000000..adcd6d67c0
--- /dev/null
+++ b/test/HealthChecks.Aws.Sqs.Tests/LocalStackContainerFixture.cs
@@ -0,0 +1,44 @@
+using Testcontainers.LocalStack;
+
+namespace HealthChecks.Aws.Sqs.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", "sqs", "create-queue", "--queue-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;
+ }
+}