feat: add health check endpoints for Bot and Web
PR Checks / test-and-build (pull_request) Successful in 8m53s
PR Checks / test-and-build (pull_request) Successful in 8m53s
- Web: add /health endpoint with PostgreSQL readiness check (returns 200+JSON or 503) - Web: add /alive endpoint for liveness probe - Bot: add BotHealthCheckHostedService serving /health on port 8081 via HttpListener - Bot: expose port 8081 in Dockerfile and install wget for healthcheck - compose.yaml: add healthcheck sections for bot and web services - tests: add TDD tests for both health endpoints Bump version -> 1.16.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using GmRelay.Web.Health;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Npgsql;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace GmRelay.Bot.Tests.Web;
|
||||
|
||||
public sealed class WebHealthEndpointTests
|
||||
{
|
||||
private static WebApplication CreateTestApp(HealthStatus npgsqlStatus)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.WebHost.UseTestServer();
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
|
||||
.AddCheck("npgsql", () => new HealthCheckResult(npgsqlStatus));
|
||||
|
||||
var app = builder.Build();
|
||||
app.MapHealthChecks("/health", new HealthCheckOptions
|
||||
{
|
||||
ResponseWriter = async (context, report) =>
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
var response = new
|
||||
{
|
||||
status = report.Status == HealthStatus.Healthy ? "healthy" : "unhealthy",
|
||||
timestamp = DateTimeOffset.UtcNow.ToString("O")
|
||||
};
|
||||
await context.Response.WriteAsJsonAsync(response);
|
||||
}
|
||||
});
|
||||
app.MapHealthChecks("/alive", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Tags.Contains("live")
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthEndpoint_ShouldReturn200AndJson_WhenHealthy()
|
||||
{
|
||||
await using var app = CreateTestApp(HealthStatus.Healthy);
|
||||
await app.StartAsync();
|
||||
using var client = app.GetTestClient();
|
||||
|
||||
var response = await client.GetAsync("/health");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(content);
|
||||
Assert.Equal("healthy", doc.RootElement.GetProperty("status").GetString());
|
||||
Assert.NotNull(doc.RootElement.GetProperty("timestamp").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HealthEndpoint_ShouldReturn503_WhenDatabaseUnavailable()
|
||||
{
|
||||
await using var app = CreateTestApp(HealthStatus.Unhealthy);
|
||||
await app.StartAsync();
|
||||
using var client = app.GetTestClient();
|
||||
|
||||
var response = await client.GetAsync("/health");
|
||||
|
||||
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NpgsqlHealthCheck_ShouldReturnHealthy_WhenDatabaseIsAccessible()
|
||||
{
|
||||
var dataSource = NpgsqlDataSource.Create("Host=localhost;Port=5432;Database=gmrelay_db;Username=gmrelay;Password=fake");
|
||||
var healthCheck = new NpgsqlHealthCheck(dataSource);
|
||||
|
||||
var result = await healthCheck.CheckHealthAsync(new HealthCheckContext());
|
||||
|
||||
Assert.Equal(HealthStatus.Unhealthy, result.Status);
|
||||
Assert.NotNull(result.Exception);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user