< Summary

Information
Class: Elsa.Workflows.Runtime.Services.WorkflowRuntimeAdminService
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/WorkflowRuntimeAdminService.cs
Line coverage
21%
Covered lines: 6
Uncovered lines: 22
Coverable lines: 28
Total lines: 64
Line coverage: 21.4%
Branch coverage
0%
Covered branches: 0
Total branches: 10
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(...)100%11100%
PauseAsync()0%2040%
ResumeAsync()0%2040%
ForceDrainAsync()0%620%
GetStatus()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/WorkflowRuntimeAdminService.cs

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.Mediator.Contracts;
 3using Elsa.Workflows.Runtime.Notifications;
 4
 5namespace Elsa.Workflows.Runtime.Services;
 6
 7/// <summary>
 8/// Default implementation of <see cref="IWorkflowRuntimeAdminService"/>. Encapsulates the pause/resume/force-drain
 9/// state-machine semantics and the audit-on-effective-transition rule (SC-007) in one place so transport-specific
 10/// adapters (HTTP, MCP, CLI) stay thin.
 11/// </summary>
 312public sealed class WorkflowRuntimeAdminService(
 313    IQuiescenceSignal signal,
 314    IIngressSourceRegistry registry,
 315    IDrainOrchestrator orchestrator,
 316    INotificationSender mediator,
 317    ISystemClock clock) : IWorkflowRuntimeAdminService
 18{
 19    /// <inheritdoc />
 20    public async ValueTask<QuiescenceState> PauseAsync(string? reason, string? requestedBy, CancellationToken cancellati
 21    {
 022        var before = signal.CurrentState;
 023        var after = await signal.PauseAsync(reason, requestedBy, cancellationToken);
 24
 25        // Audit only effective transitions (SC-007).
 026        var wasPaused = (before.Reason & QuiescenceReason.AdministrativePause) != 0;
 027        var isPaused = (after.Reason & QuiescenceReason.AdministrativePause) != 0;
 028        if (!wasPaused && isPaused)
 029            await mediator.SendAsync(new RuntimePauseRequested(requestedBy, reason, after.PausedAt ?? clock.UtcNow), can
 30
 031        return after;
 032    }
 33
 34    /// <inheritdoc />
 35    public async ValueTask<QuiescenceState> ResumeAsync(string? requestedBy, CancellationToken cancellationToken)
 36    {
 037        var before = signal.CurrentState;
 038        var after = await signal.ResumeAsync(requestedBy, cancellationToken);
 39
 040        var wasPaused = (before.Reason & QuiescenceReason.AdministrativePause) != 0;
 041        var isPaused = (after.Reason & QuiescenceReason.AdministrativePause) != 0;
 042        if (wasPaused && !isPaused)
 043            await mediator.SendAsync(new RuntimeResumeRequested(requestedBy, clock.UtcNow), cancellationToken);
 44
 045        return after;
 046    }
 47
 48    /// <inheritdoc />
 49    public async ValueTask<DrainOutcome> ForceDrainAsync(string? reason, string? requestedBy, CancellationToken cancella
 50    {
 51        // Caller catches InvalidOperationException to translate to 409 etc.
 052        var outcome = await orchestrator.DrainAsync(DrainTrigger.OperatorForce, cancellationToken);
 53
 54        // Audit. Skip when the call returned a cached outcome — repeated invocations in the same generation
 55        // would otherwise emit spurious audit events and break SC-007 idempotency.
 056        if (!outcome.WasCached)
 057            await mediator.SendAsync(new RuntimeForceDrainRequested(requestedBy, reason, clock.UtcNow, outcome), cancell
 58
 059        return outcome;
 060    }
 61
 62    /// <inheritdoc />
 063    public RuntimeAdminStatus GetStatus() => new(signal.CurrentState, registry.Snapshot(), signal.ActiveExecutionCycleCo
 64}