< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Handlers.PasswordRecoveryCommandHandler
Assembly: ArturRios.Heimdall.Command
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Handlers/PasswordRecoveryCommandHandler.cs
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 61
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
HandleAsync()100%22100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Handlers/PasswordRecoveryCommandHandler.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Input;
 2using ArturRios.Heimdall.Command.Output;
 3using ArturRios.Heimdall.Command.Services;
 4using ArturRios.Heimdall.Shared.Messages;
 5using ArturRios.Mediator.Command.Interfaces;
 6using ArturRios.Output;
 7using FluentValidation;
 8
 9namespace ArturRios.Heimdall.Command.Handlers;
 10
 11/// <summary>
 12///     Handles <see cref="PasswordRecoveryCommand" /> (UC-12): validates the request and queues it,
 13///     then answers. Finding the person and issuing and mailing the reset token (FR-PR-01/02) happen
 14///     afterwards, in <see cref="PasswordRecoveryProcessor" />.
 15/// </summary>
 16/// <remarks>
 17///     <para>
 18///         Every valid request returns the same success output. AF-12a — the address belongs to
 19///         nobody — is not an error flow here but the absence of work later on: the processor simply
 20///         issues no token. A person who is logically deleted or restricted, or a <c>User</c> whose
 21///         scope is, is treated the same way, for the reason UC-11 gives at length: an endpoint open
 22///         to anonymous callers must not become a directory of which addresses are registered and
 23///         which accounts still exist.
 24///     </para>
 25///     <para>
 26///         The same answer is not enough if it takes a different time to arrive. While the lookup,
 27///         the token insert and the Mailgun call ran here, a registered address answered a Mailgun
 28///         round trip later than an unknown one. So this handler does no work that depends on the
 29///         address: it validates, queues, and answers — identically, and in the same time, for every
 30///         address. A queue that is full drops the request; the answer does not change, and the
 31///         caller, who receives no email, asks again.
 32///     </para>
 33/// </remarks>
 3234public class PasswordRecoveryCommandHandler(
 3235    IValidator<PasswordRecoveryCommand> validator,
 3236    IPasswordRecoveryQueue recoveryQueue)
 37    : ICommandHandlerAsync<PasswordRecoveryCommand, PasswordRecoveryCommandOutput>
 38{
 39    public async Task<DataOutput<PasswordRecoveryCommandOutput?>> HandleAsync(PasswordRecoveryCommand command, Cancellat
 40    {
 3241        var output = DataOutput<PasswordRecoveryCommandOutput?>.New;
 42
 43        // NFR-10: validate input shape. The only rejection this endpoint ever issues, and one that
 44        // depends on the request alone.
 3245        var validation = await validator.ValidateAsync(command, cancellationToken);
 46
 3247        if (!validation.IsValid)
 48        {
 749            return output.WithErrors(validation.Errors.Select(failure => failure.ErrorMessage));
 50        }
 51
 52        // UC-12 steps 2 to 4, deferred. The result is deliberately ignored: a dropped request must
 53        // answer exactly as a queued one does.
 2954        recoveryQueue.TryEnqueue(new PasswordRecoveryRequest(command.Email, command.ScopeId));
 55
 56        // UC-12 step 5: the same answer either way.
 2957        return output
 2958            .WithData(new PasswordRecoveryCommandOutput())
 2959            .WithMessage(AuthMessages.PasswordRecoveryRequested);
 3260    }
 61}