< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Email.PasswordRecoveryDispatcher
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Email/PasswordRecoveryDispatcher.cs
Line coverage
94%
Covered lines: 16
Uncovered lines: 1
Coverable lines: 17
Total lines: 58
Line coverage: 94.1%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage
100%
Covered methods: 3
Fully covered methods: 2
Total methods: 3
Method coverage: 100%
Full method coverage: 66.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteAsync()50%2283.33%
ProcessAsync()100%11100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Email/PasswordRecoveryDispatcher.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Services;
 2
 3namespace ArturRios.Heimdall.WebApi.Email;
 4
 5/// <summary>
 6///     Works through <see cref="PasswordRecoveryQueue" />: for each request, a fresh DI scope and one
 7///     <see cref="PasswordRecoveryProcessor" /> run — UC-12's lookup, token and email, after the caller
 8///     has already been answered.
 9/// </summary>
 10/// <remarks>
 11///     <para>
 12///         A scope per request, not one for the service's lifetime: the database context and the
 13///         Mailgun client are scoped, and each request's work should neither see the last one's
 14///         tracked entities nor depend on an HTTP request's scope that ended when it was answered.
 15///         That is the difference from firing the send off from the handler, which would have used a
 16///         client the request scope disposes underneath it.
 17///     </para>
 18///     <para>
 19///         One request at a time. Volume is bounded by the per-address rate limit, and a failure —
 20///         a database blip, Mailgun refusing — is logged and the next request is taken, as the
 21///         scheduled passes do: one person's email must not stop the next person's.
 22///     </para>
 23/// </remarks>
 90124public sealed class PasswordRecoveryDispatcher(
 90125    PasswordRecoveryQueue queue,
 90126    IServiceScopeFactory scopeFactory,
 90127    ILogger<PasswordRecoveryDispatcher> logger) : BackgroundService
 28{
 29    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 30    {
 31        try
 32        {
 186433            await foreach (var request in queue.Reader.ReadAllAsync(stoppingToken))
 34            {
 3135                await ProcessAsync(request);
 36            }
 037        }
 90138        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
 39        {
 40            // Shutdown. Whatever is still queued is dropped; see PasswordRecoveryQueue for why that
 41            // is acceptable.
 90142        }
 90143    }
 44
 45    private async Task ProcessAsync(PasswordRecoveryRequest request)
 46    {
 47        try
 48        {
 3149            await using var scope = scopeFactory.CreateAsyncScope();
 50
 3151            await scope.ServiceProvider.GetRequiredService<PasswordRecoveryProcessor>().ProcessAsync(request);
 3052        }
 153        catch (Exception exception)
 54        {
 155            logger.LogError(exception, "A password recovery request could not be processed");
 156        }
 3157    }
 58}