< Summary

Information
Class: Chronicis.Api.Controllers.HealthController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/HealthController.cs
Line coverage
100%
Covered lines: 32
Uncovered lines: 0
Coverable lines: 32
Total lines: 156
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetHealth()100%11100%
GetApiVersion()100%11100%
GetApiVersion(...)100%44100%
MaskConnectionString(...)100%66100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/HealthController.cs

#LineLine coverage
 1using System.Reflection;
 2using Chronicis.Api.Services;
 3using Chronicis.Shared.DTOs;
 4using Microsoft.AspNetCore.Mvc;
 5using Microsoft.Data.SqlClient;
 6
 7namespace Chronicis.Api.Controllers;
 8
 9/// <summary>
 10/// API endpoints for health checks.
 11/// These endpoints do NOT require authentication.
 12/// </summary>
 13[ApiController]
 14[Route("health")]
 15public class HealthController : ControllerBase
 16{
 17    private readonly ILogger<HealthController> _logger;
 18    private readonly IConfiguration _configuration;
 19    private readonly ISystemHealthService _systemHealthService;
 20    private readonly IHealthReadinessService _healthReadinessService;
 21
 122    public HealthController(
 123        ILogger<HealthController> logger,
 124        IConfiguration configuration,
 125        ISystemHealthService systemHealthService,
 126        IHealthReadinessService healthReadinessService)
 27    {
 128        _logger = logger;
 129        _configuration = configuration;
 130        _systemHealthService = systemHealthService;
 131        _healthReadinessService = healthReadinessService;
 132    }
 33
 34    /// <summary>
 35    /// GET /api/health - Basic health check endpoint.
 36    /// Returns 200 OK if the API is running.
 37    /// </summary>
 38    [HttpGet]
 39    public IActionResult GetHealth()
 40    {
 141        _logger.LogTraceSanitized("Health Endpoint Called");
 142        return Ok(new
 143        {
 144            status = "healthy",
 145            timestamp = DateTime.UtcNow,
 146            version = GetApiVersion()
 147        });
 48    }
 49
 50    /// <summary>
 51    /// GET /api/health/ready - Readiness check including database connectivity.
 52    /// Returns 200 OK if the API and database are ready.
 53    /// </summary>
 54    [HttpGet("ready")]
 55    public async Task<IActionResult> GetReadiness()
 56    {
 57        try
 58        {
 59            var readiness = await _healthReadinessService.GetReadinessAsync();
 60            if (!readiness.IsHealthy)
 61            {
 62                _logger.LogWarningSanitized("Health check failed: Cannot connect to database");
 63                return StatusCode(503, new
 64                {
 65                    status = "unhealthy",
 66                    timestamp = DateTime.UtcNow,
 67                    checks = new
 68                    {
 69                        database = readiness.DatabaseStatus
 70                    }
 71                });
 72            }
 73
 74            // Get connection string info for diagnostics (mask password)
 75            var connStr = _configuration.GetConnectionString("ChronicisDb") ?? "";
 76            var maskedConnStr = MaskConnectionString(connStr);
 77
 78
 79            _logger.LogTraceSanitized("Readiness endpoint succeeded");
 80
 81            return Ok(new
 82            {
 83                status = "healthy",
 84                timestamp = DateTime.UtcNow,
 85                version = GetApiVersion(),
 86                checks = new
 87                {
 88                    database = readiness.DatabaseStatus,
 89                    connectionInfo = maskedConnStr
 90                }
 91            });
 92        }
 93        catch (Exception ex)
 94        {
 95            _logger.LogErrorSanitized(ex, "Health check failed with exception");
 96            return StatusCode(503, new
 97            {
 98                status = "unhealthy",
 99                timestamp = DateTime.UtcNow,
 100                error = ex.Message
 101            });
 102        }
 103    }
 104
 105    /// <summary>
 106    /// GET /api/health/status - Comprehensive system health status.
 107    /// Returns the health status of all system dependencies.
 108    /// </summary>
 109    [HttpGet("status")]
 110    public async Task<ActionResult<SystemHealthStatusDto>> GetSystemStatus()
 111    {
 112        _logger.LogTraceSanitized("System health status endpoint called");
 113
 114        var systemHealth = await _systemHealthService.GetSystemHealthAsync();
 115        systemHealth.ApiVersion = GetApiVersion();
 116
 117        // Return appropriate HTTP status code based on overall health
 118        var statusCode = systemHealth.OverallStatus switch
 119        {
 120            HealthStatus.Healthy => 200,
 121            HealthStatus.Degraded => 200, // Still operational
 122            HealthStatus.Unhealthy => 503,
 123            _ => 200
 124        };
 125
 126        return StatusCode(statusCode, systemHealth);
 127    }
 128
 2129    internal static string GetApiVersion() => GetApiVersion(Assembly.GetExecutingAssembly());
 130
 131    internal static string GetApiVersion(Assembly assembly) =>
 3132        assembly
 3133            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()
 3134            ?.InformationalVersion ?? "0.0.0";
 135
 136    private static string MaskConnectionString(string connectionString)
 137    {
 5138        if (string.IsNullOrEmpty(connectionString))
 2139            return "(empty)";
 140
 141        try
 142        {
 3143            var builder = new SqlConnectionStringBuilder(connectionString);
 2144            var hasPassword = !string.IsNullOrEmpty(builder.Password);
 2145            var hasUserId = !string.IsNullOrEmpty(builder.UserID);
 146
 2147            return $"Server={builder.DataSource}; Database={builder.InitialCatalog}; " +
 2148                   $"User={(!hasUserId ? "(none)" : "****")}; Password={(!hasPassword ? "(none)" : "****")}; " +
 2149                   $"MARS={builder.MultipleActiveResultSets}; Encrypt={builder.Encrypt}";
 150        }
 1151        catch
 152        {
 1153            return "(invalid connection string format)";
 154        }
 3155    }
 156}