< Summary

Information
Class: Elsa.Workflows.Runtime.HealthChecks.ElsaWorkflowPersistenceHealthCheck
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaWorkflowPersistenceHealthCheck.cs
Line coverage
94%
Covered lines: 64
Uncovered lines: 4
Coverable lines: 68
Total lines: 131
Line coverage: 94.1%
Branch coverage
91%
Covered branches: 22
Total branches: 24
Branch coverage: 91.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CheckHealthAsync()66.66%6685.71%
<CheckHealthAsync()100%11100%
<CheckHealthAsync()100%11100%
<CheckHealthAsync()100%11100%
<CheckHealthAsync()100%11100%
AddProbeAsync()100%22100%
CreateResult()100%44100%
CreateData()100%88100%
ProbeAsync()100%2277.77%
get_StoreName()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/HealthChecks/ElsaWorkflowPersistenceHealthCheck.cs

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.Workflows.Management;
 3using Elsa.Workflows.Management.Filters;
 4using Elsa.Workflows.Runtime.Filters;
 5using Elsa.Workflows.Runtime.Options;
 6using Microsoft.Extensions.DependencyInjection;
 7using Microsoft.Extensions.Diagnostics.HealthChecks;
 8using Microsoft.Extensions.Logging;
 9using Microsoft.Extensions.Options;
 10
 11namespace Elsa.Workflows.Runtime.HealthChecks;
 12
 13/// <summary>
 14/// Performs small read-only probes against the workflow management and runtime stores.
 15/// </summary>
 716public class ElsaWorkflowPersistenceHealthCheck(
 717    IServiceProvider serviceProvider,
 718    IOptions<ElsaReadinessHealthCheckOptions> options,
 719    ILogger<ElsaWorkflowPersistenceHealthCheck> logger) : IHealthCheck
 20{
 21    private const string ProbeId = "00000000-0000-0000-0000-000000000000";
 22
 23    /// <inheritdoc />
 24    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToke
 25    {
 26        // These probes verify store reachability only; returned entities and counts are intentionally ignored.
 627        var probeResults = new List<ProbeResult>();
 28
 629        if (!await AddProbeAsync("workflow-definitions", serviceProvider.GetService<IWorkflowDefinitionStore>(), async (
 630            {
 431                await store.FindAsync(new WorkflowDefinitionFilter { Id = ProbeId }, ct);
 1032            }))
 033            return CreateResult();
 34
 635        if (!await AddProbeAsync("workflow-instances", serviceProvider.GetService<IWorkflowInstanceStore>(), async (stor
 636            {
 437                await store.CountAsync(new WorkflowInstanceFilter { Id = ProbeId }, ct);
 1038            }))
 039            return CreateResult();
 40
 641        if (!await AddProbeAsync("triggers", serviceProvider.GetService<ITriggerStore>(), async (store, ct) =>
 642            {
 543                await store.FindAsync(new TriggerFilter { Id = ProbeId }, ct);
 944            }))
 145            return CreateResult();
 46
 547        if (!await AddProbeAsync("bookmark-queue", serviceProvider.GetService<IBookmarkQueueStore>(), async (store, ct) 
 548            {
 449                await store.FindAsync(new BookmarkQueueFilter { Id = ProbeId }, ct);
 950            }))
 551            return CreateResult();
 52
 53        return CreateResult();
 54
 55        async Task<bool> AddProbeAsync<TStore>(string storeName, TStore? store, Func<TStore, CancellationToken, Task> pr
 56        {
 2357            var result = await ProbeAsync(storeName, store, probe);
 2358            probeResults.Add(result);
 2359            return result.Exception == null || options.Value.ContinuePersistenceProbesAfterFailure;
 2360        }
 61
 62        HealthCheckResult CreateResult()
 63        {
 4664            var attemptedProbes = probeResults.Where(x => !x.Skipped).Select(x => x.StoreName).ToList();
 4465            var successfulProbes = probeResults.Where(x => !x.Skipped && x.Exception == null).Select(x => x.StoreName).T
 3566            var skippedProbes = probeResults.Where(x => x.Skipped).Select(x => x.StoreName).ToList();
 3167            var failedProbes = probeResults.Where(x => x.Exception != null).Select(x => x.StoreName).ToList();
 2868            var failedProbe = probeResults.FirstOrDefault(x => x.Exception != null);
 669            if (failedProbe != null)
 70            {
 271                var data = CreateData();
 272                data["failedStore"] = failedProbe.StoreName;
 273                data["failedProbe"] = failedProbe.StoreName;
 74
 275                logger.LogWarning(failedProbe.Exception, "Elsa workflow store {StoreName} is not reachable.", failedProb
 276                return HealthCheckResult.Unhealthy($"Elsa workflow store '{failedProbe.StoreName}' is not reachable.", d
 77            }
 78
 479            var healthyData = CreateData();
 480            return attemptedProbes.Count == 0
 481                ? HealthCheckResult.Degraded("No Elsa workflow persistence stores are registered.", data: healthyData)
 482                : HealthCheckResult.Healthy("Elsa workflow stores are reachable.", healthyData);
 83
 84            Dictionary<string, object> CreateData()
 85            {
 686                var data = new Dictionary<string, object>
 687                {
 688                    ["category"] = "persistence"
 689                };
 90
 691                if (successfulProbes.Count > 0)
 592                    data["successfulProbes"] = string.Join(",", successfulProbes);
 93
 694                if (attemptedProbes.Count > 0)
 595                    data["attemptedProbes"] = string.Join(",", attemptedProbes);
 96
 697                if (failedProbes.Count > 0)
 298                    data["failedProbes"] = string.Join(",", failedProbes);
 99
 6100                if (skippedProbes.Count > 0)
 2101                    data["skippedProbes"] = string.Join(",", skippedProbes);
 102
 6103                return data;
 104            }
 105        }
 106
 107        async Task<ProbeResult> ProbeAsync<TStore>(string storeName, TStore? store, Func<TStore, CancellationToken, Task
 108        {
 23109            if (store == null)
 110            {
 6111                return new ProbeResult(storeName, true, null);
 112            }
 113
 114            try
 115            {
 17116                await probe(store, cancellationToken);
 15117                return new ProbeResult(storeName, false, null);
 118            }
 0119            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 120            {
 0121                throw;
 122            }
 2123            catch (Exception e) when (!e.IsFatal())
 124            {
 2125                return new ProbeResult(storeName, false, e);
 126            }
 23127        }
 6128    }
 129
 227130    private sealed record ProbeResult(string StoreName, bool Skipped, Exception? Exception);
 131}