< Summary

Information
Class: ProjectTemplate.Web.HealthChecks.ApplicationAuditIntegrityHealthCheck
Assembly: ProjectTemplate.Web
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/HealthChecks/ApplicationAuditIntegrityHealthCheck.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 59
Coverable lines: 59
Total lines: 92
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)0%4260%
CheckHealthAsync()0%702260%

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Web/HealthChecks/ApplicationAuditIntegrityHealthCheck.cs

#LineLine coverage
 1using Microsoft.Extensions.Diagnostics.HealthChecks;
 2using Microsoft.Extensions.Options;
 3using ProjectTemplate.Infrastructure.Data.Auditing;
 4
 5namespace ProjectTemplate.Web.HealthChecks;
 6
 07public sealed class ApplicationAuditIntegrityHealthCheck(
 08    IServiceScopeFactory scopeFactory,
 09    IOptions<ApplicationAuditReconciliationOptions> options)
 10    : IHealthCheck
 11{
 012    private readonly IServiceScopeFactory _scopeFactory =
 013        scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
 014    private readonly ApplicationAuditReconciliationOptions _options =
 015        options?.Value ?? throw new ArgumentNullException(nameof(options));
 16
 17    public async Task<HealthCheckResult> CheckHealthAsync(
 18        HealthCheckContext context,
 19        CancellationToken cancellationToken = default)
 20    {
 021        cancellationToken.ThrowIfCancellationRequested();
 022        if (!_options.Enabled)
 23        {
 024            return HealthCheckResult.Healthy("Audit reconciliation is disabled.");
 25        }
 26
 027        using IServiceScope scope = _scopeFactory.CreateScope();
 028        IApplicationAuditReconciler reconciler = scope.ServiceProvider
 029            .GetRequiredService<IApplicationAuditReconciler>();
 030        ApplicationAuditReconciliationSummary summary = await reconciler
 031            .GetSummaryAsync(cancellationToken)
 032            .ConfigureAwait(false);
 33
 034        ApplicationAuditCompletionOutboxHealth? deliveryHealth = null;
 035        IApplicationAuditCompletionOutboxQuery? outboxQuery = scope.ServiceProvider
 036            .GetService<IApplicationAuditCompletionOutboxQuery>();
 037        if (outboxQuery is not null)
 38        {
 039            deliveryHealth = await outboxQuery.GetHealthAsync(cancellationToken).ConfigureAwait(false);
 040            scope.ServiceProvider
 041                .GetRequiredService<ApplicationAuditReconciliationMetrics>()
 042                .UpdateDelivery(deliveryHealth);
 43        }
 44
 045        var data = new Dictionary<string, object>
 046        {
 047            ["openFindings"] = summary.OpenFindingCount,
 048            ["errorFindings"] = summary.ErrorFindingCount,
 049            ["criticalFindings"] = summary.CriticalFindingCount,
 050            ["manifestVerificationFailures"] = summary.ManifestVerificationFailureCount,
 051            ["missingCompletions"] = summary.MissingCompletionCount,
 052            ["staleDeliveryFindings"] = summary.StaleDeliveryCount,
 053            ["deadLetterFindings"] = summary.DeadLetterCount
 054        };
 55
 056        if (summary.LastRunUtc.HasValue)
 57        {
 058            data["lastReconciliationUtc"] = summary.LastRunUtc.Value;
 59        }
 60
 061        if (deliveryHealth is not null)
 62        {
 063            data["outboxBacklog"] = deliveryHealth.BacklogCount;
 064            data["outboxRetryCount"] = deliveryHealth.TotalRetryCount;
 065            data["outboxDeadLetters"] = deliveryHealth.DeadLetterCount;
 066            if (deliveryHealth.OldestPendingAge.HasValue)
 67            {
 068                data["oldestPendingAgeSeconds"] = deliveryHealth.OldestPendingAge.Value.TotalSeconds;
 69            }
 70        }
 71
 072        bool unhealthy = summary.CriticalFindingCount > 0 ||
 073            summary.ManifestVerificationFailureCount > 0 ||
 074            summary.OpenFindingCount >= _options.HealthUnhealthyFindingCount;
 075        if (unhealthy)
 76        {
 077            return HealthCheckResult.Unhealthy(
 078                "Critical audit-integrity findings require operator review.",
 079                data: data);
 80        }
 81
 082        bool degraded = summary.OpenFindingCount >= _options.HealthWarningFindingCount ||
 083            summary.StaleDeliveryCount > 0 ||
 084            summary.DeadLetterCount > 0 ||
 085            deliveryHealth?.DeadLetterCount > 0;
 086        return degraded
 087            ? HealthCheckResult.Degraded(
 088                "Audit reconciliation or delivery findings require attention.",
 089                data: data)
 090            : HealthCheckResult.Healthy("Audit integrity and delivery state are within configured thresholds.", data);
 091    }
 92}