Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<PackageVersion Include="Testcontainers" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.ClickHouse" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.Kafka" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.LocalStack" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.Milvus" Version="$(TestcontainersVersion)" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="$(TestcontainersVersion)" />
<PackageVersion Include="TestContainers.MongoDb" Version="$(TestcontainersVersion)" />
Expand Down
1 change: 1 addition & 0 deletions src/HealthChecks.Aws.Sns/SnsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace HealthChecks.Aws.Sns;
/// </summary>
public class SnsOptions
{
public string? ServiceURL { get; set; }
public AWSCredentials? Credentials { get; set; }

public RegionEndpoint? RegionEndpoint { get; set; }
Expand Down
31 changes: 20 additions & 11 deletions src/HealthChecks.Aws.Sns/SnsTopicAndSubscriptionHealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <inheritdoc />
Expand All @@ -20,7 +21,7 @@ public async Task<HealthCheckResult> 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.");
Expand Down Expand Up @@ -53,14 +54,22 @@ public async Task<HealthCheckResult> 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);
}
}
176 changes: 176 additions & 0 deletions test/HealthChecks.Aws.Sns.Tests/Functional/SnsHealthCheckTests.cs
Original file line number Diff line number Diff line change
@@ -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<LocalStackContainerFixture>
{
[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);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<PackageReference Include="Testcontainers.LocalStack" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\HealthChecks.Aws.Sns\HealthChecks.Aws.Sns.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>? subscriptions = null) { }
}
public class SnsTopicAndSubscriptionHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
Expand Down
45 changes: 45 additions & 0 deletions test/HealthChecks.Aws.Sns.Tests/LocalStackContainerFixture.cs
Original file line number Diff line number Diff line change
@@ -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<LocalStackContainer> CreateContainerAsync()
{
var container = new LocalStackBuilder()
.WithImage($"{Registry}/{Image}:{Tag}")
.Build();

await container.StartAsync();

return container;
}
}