fix: address review feedback for health check endpoints
PR Checks / test-and-build (pull_request) Successful in 8m34s

- Install wget in Web Dockerfile for compose healthcheck
- Ensure HttpListener response is always closed in BotHealthCheckHostedService
- Use ephemeral port in Bot health check test to avoid port conflicts
- Rename NpgsqlHealthCheck test to reflect actual behavior

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 11:16:58 +03:00
parent 3bea327043
commit 105b3c59d7
4 changed files with 21 additions and 7 deletions
@@ -72,10 +72,10 @@ public sealed class BotHealthCheckHostedService : IHostedService
private async Task HandleRequestAsync(HttpListenerContext context) private async Task HandleRequestAsync(HttpListenerContext context)
{ {
var response = context.Response;
try try
{ {
var request = context.Request; var request = context.Request;
var response = context.Response;
if (request.Url?.AbsolutePath == "/health") if (request.Url?.AbsolutePath == "/health")
{ {
@@ -88,12 +88,14 @@ public sealed class BotHealthCheckHostedService : IHostedService
{ {
response.StatusCode = (int)HttpStatusCode.NotFound; response.StatusCode = (int)HttpStatusCode.NotFound;
} }
response.Close();
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error handling health check request"); _logger.LogError(ex, "Error handling health check request");
} }
finally
{
response.Close();
}
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@ RUN dotnet publish "GmRelay.Web.csproj" -c Release -o /app/publish /p:UseAppHost
# Stage 2: Runtime # Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble AS final FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble AS final
WORKDIR /app WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends libgssapi-krb5-2 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y --no-install-recommends libgssapi-krb5-2 wget && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish . COPY --from=build /app/publish .
RUN mkdir -p /app/dataprotection-keys && chown -R $APP_UID:$APP_UID /app/dataprotection-keys RUN mkdir -p /app/dataprotection-keys && chown -R $APP_UID:$APP_UID /app/dataprotection-keys
ENV ASPNETCORE_URLS=http://+:8080 ENV ASPNETCORE_URLS=http://+:8080
@@ -1,4 +1,5 @@
using System.Net; using System.Net;
using System.Net.Sockets;
using GmRelay.Bot.Infrastructure.Health; using GmRelay.Bot.Infrastructure.Health;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@@ -8,13 +9,15 @@ namespace GmRelay.Bot.Tests.Infrastructure.Health;
public sealed class BotHealthCheckHostedServiceTests : IDisposable public sealed class BotHealthCheckHostedServiceTests : IDisposable
{ {
private readonly BotHealthCheckHostedService _service; private readonly BotHealthCheckHostedService _service;
private readonly int _port;
public BotHealthCheckHostedServiceTests() public BotHealthCheckHostedServiceTests()
{ {
_port = GetAvailablePort();
var config = new ConfigurationBuilder() var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> .AddInMemoryCollection(new Dictionary<string, string?>
{ {
["HealthCheck:Prefix"] = "http://localhost:8081/" ["HealthCheck:Prefix"] = $"http://localhost:{_port}/"
}) })
.Build(); .Build();
@@ -35,8 +38,17 @@ public sealed class BotHealthCheckHostedServiceTests : IDisposable
using var client = new HttpClient(); using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(5); client.Timeout = TimeSpan.FromSeconds(5);
var response = await client.GetAsync("http://localhost:8081/health"); var response = await client.GetAsync($"http://localhost:{_port}/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
} }
private static int GetAvailablePort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
} }
@@ -73,7 +73,7 @@ public sealed class WebHealthEndpointTests
} }
[Fact] [Fact]
public async Task NpgsqlHealthCheck_ShouldReturnHealthy_WhenDatabaseIsAccessible() public async Task NpgsqlHealthCheck_ShouldReturnUnhealthy_WhenDatabaseIsInaccessible()
{ {
var dataSource = NpgsqlDataSource.Create("Host=localhost;Port=5432;Database=gmrelay_db;Username=gmrelay;Password=fake"); var dataSource = NpgsqlDataSource.Create("Host=localhost;Port=5432;Database=gmrelay_db;Username=gmrelay;Password=fake");
var healthCheck = new NpgsqlHealthCheck(dataSource); var healthCheck = new NpgsqlHealthCheck(dataSource);