QrRapido/Services/HealthChecks/ExternalServicesHealthCheck.cs
2025-07-28 18:22:47 -03:00

321 lines
14 KiB
C#

using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Diagnostics;
using System.Text.Json;
namespace QRRapidoApp.Services.HealthChecks
{
public class ExternalServicesHealthCheck : IHealthCheck
{
private readonly IConfiguration _configuration;
private readonly ILogger<ExternalServicesHealthCheck> _logger;
private readonly HttpClient _httpClient;
private readonly int _timeoutSeconds;
private readonly bool _testStripeConnection;
private readonly bool _testGoogleAuth;
private readonly bool _testMicrosoftAuth;
public ExternalServicesHealthCheck(
IConfiguration configuration,
ILogger<ExternalServicesHealthCheck> logger,
IHttpClientFactory httpClientFactory)
{
_configuration = configuration;
_logger = logger;
_httpClient = httpClientFactory.CreateClient();
_timeoutSeconds = configuration.GetValue<int>("HealthChecks:ExternalServices:TimeoutSeconds", 10);
_testStripeConnection = configuration.GetValue<bool>("HealthChecks:ExternalServices:TestStripeConnection", true);
_testGoogleAuth = configuration.GetValue<bool>("HealthChecks:ExternalServices:TestGoogleAuth", false);
_testMicrosoftAuth = configuration.GetValue<bool>("HealthChecks:ExternalServices:TestMicrosoftAuth", false);
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var stopwatch = Stopwatch.StartNew();
var data = new Dictionary<string, object>();
var services = new List<object>();
var issues = new List<string>();
var warnings = new List<string>();
try
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutSeconds));
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
// Test Stripe connection
if (_testStripeConnection)
{
var stripeResult = await TestStripeConnectionAsync(combinedCts.Token);
services.Add(stripeResult);
if (stripeResult.GetProperty("status").GetString() == "error")
{
issues.Add($"Stripe: {stripeResult.GetProperty("error").GetString()}");
}
else if (stripeResult.GetProperty("status").GetString() == "warning")
{
warnings.Add($"Stripe: slow response ({stripeResult.GetProperty("latencyMs").GetInt32()}ms)");
}
}
// Test Google OAuth endpoints
if (_testGoogleAuth)
{
var googleResult = await TestGoogleAuthAsync(combinedCts.Token);
services.Add(googleResult);
if (googleResult.GetProperty("status").GetString() == "error")
{
warnings.Add($"Google Auth: {googleResult.GetProperty("error").GetString()}");
}
}
// Test Microsoft OAuth endpoints
if (_testMicrosoftAuth)
{
var microsoftResult = await TestMicrosoftAuthAsync(combinedCts.Token);
services.Add(microsoftResult);
if (microsoftResult.GetProperty("status").GetString() == "error")
{
warnings.Add($"Microsoft Auth: {microsoftResult.GetProperty("error").GetString()}");
}
}
stopwatch.Stop();
data["services"] = services;
data["totalCheckTimeMs"] = stopwatch.ElapsedMilliseconds;
data["status"] = DetermineOverallStatus(issues.Count, warnings.Count);
// Return appropriate health status
if (issues.Any())
{
return HealthCheckResult.Unhealthy($"External service issues: {string.Join(", ", issues)}", data: data);
}
if (warnings.Any())
{
return HealthCheckResult.Degraded($"External service warnings: {string.Join(", ", warnings)}", data: data);
}
return HealthCheckResult.Healthy($"All external services healthy ({services.Count} services checked)", data: data);
}
catch (OperationCanceledException)
{
return HealthCheckResult.Unhealthy($"External services health check timed out after {_timeoutSeconds} seconds", data: data);
}
catch (Exception ex)
{
_logger.LogError(ex, "External services health check failed");
return HealthCheckResult.Unhealthy($"External services health check failed: {ex.Message}", data: data);
}
}
private async Task<JsonElement> TestStripeConnectionAsync(CancellationToken cancellationToken)
{
var serviceStopwatch = Stopwatch.StartNew();
try
{
var secretKey = _configuration["Stripe:SecretKey"];
if (string.IsNullOrEmpty(secretKey) || secretKey.StartsWith("sk_test_xxxxx"))
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "stripe",
status = "warning",
error = "Stripe not configured",
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
// Test Stripe API connectivity by hitting a simple endpoint
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.stripe.com/v1/account");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", secretKey);
var response = await _httpClient.SendAsync(request, cancellationToken);
serviceStopwatch.Stop();
var latencyMs = serviceStopwatch.ElapsedMilliseconds;
if (response.IsSuccessStatusCode)
{
var status = latencyMs > 2000 ? "warning" : "ok";
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "stripe",
status = status,
statusCode = (int)response.StatusCode,
latencyMs = latencyMs,
lastChecked = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
})).RootElement;
}
else
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "stripe",
status = "error",
statusCode = (int)response.StatusCode,
error = $"HTTP {response.StatusCode}",
latencyMs = latencyMs
})).RootElement;
}
}
catch (Exception ex)
{
serviceStopwatch.Stop();
_logger.LogWarning(ex, "Failed to test Stripe connection");
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "stripe",
status = "error",
error = ex.Message,
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
}
private async Task<JsonElement> TestGoogleAuthAsync(CancellationToken cancellationToken)
{
var serviceStopwatch = Stopwatch.StartNew();
try
{
var clientId = _configuration["Authentication:Google:ClientId"];
if (string.IsNullOrEmpty(clientId) || clientId == "your-google-client-id")
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "google_auth",
status = "warning",
error = "Google Auth not configured",
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
// Test Google's OAuth discovery document
var response = await _httpClient.GetAsync("https://accounts.google.com/.well-known/openid_configuration", cancellationToken);
serviceStopwatch.Stop();
var latencyMs = serviceStopwatch.ElapsedMilliseconds;
if (response.IsSuccessStatusCode)
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "google_auth",
status = "ok",
statusCode = (int)response.StatusCode,
latencyMs = latencyMs,
lastChecked = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
})).RootElement;
}
else
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "google_auth",
status = "error",
statusCode = (int)response.StatusCode,
error = $"HTTP {response.StatusCode}",
latencyMs = latencyMs
})).RootElement;
}
}
catch (Exception ex)
{
serviceStopwatch.Stop();
_logger.LogWarning(ex, "Failed to test Google Auth connection");
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "google_auth",
status = "error",
error = ex.Message,
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
}
private async Task<JsonElement> TestMicrosoftAuthAsync(CancellationToken cancellationToken)
{
var serviceStopwatch = Stopwatch.StartNew();
try
{
var clientId = _configuration["Authentication:Microsoft:ClientId"];
if (string.IsNullOrEmpty(clientId) || clientId == "your-microsoft-client-id")
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "microsoft_auth",
status = "warning",
error = "Microsoft Auth not configured",
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
// Test Microsoft's OAuth discovery document
var response = await _httpClient.GetAsync("https://login.microsoftonline.com/common/v2.0/.well-known/openid_configuration", cancellationToken);
serviceStopwatch.Stop();
var latencyMs = serviceStopwatch.ElapsedMilliseconds;
if (response.IsSuccessStatusCode)
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "microsoft_auth",
status = "ok",
statusCode = (int)response.StatusCode,
latencyMs = latencyMs,
lastChecked = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
})).RootElement;
}
else
{
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "microsoft_auth",
status = "error",
statusCode = (int)response.StatusCode,
error = $"HTTP {response.StatusCode}",
latencyMs = latencyMs
})).RootElement;
}
}
catch (Exception ex)
{
serviceStopwatch.Stop();
_logger.LogWarning(ex, "Failed to test Microsoft Auth connection");
return JsonDocument.Parse(JsonSerializer.Serialize(new
{
service = "microsoft_auth",
status = "error",
error = ex.Message,
latencyMs = serviceStopwatch.ElapsedMilliseconds
})).RootElement;
}
}
private string DetermineOverallStatus(int issueCount, int warningCount)
{
if (issueCount > 0)
{
return "error";
}
if (warningCount > 0)
{
return "warning";
}
return "ok";
}
}
}