< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Handlers.SetScopeEmailLinksCommandHandler
Assembly: ArturRios.Heimdall.Command
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Handlers/SetScopeEmailLinksCommandHandler.cs
Line coverage
97%
Covered lines: 41
Uncovered lines: 1
Coverable lines: 42
Total lines: 87
Line coverage: 97.6%
Branch coverage
90%
Covered branches: 9
Total branches: 10
Branch coverage: 90%
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%
HandleAsync()87.5%8897.14%
Normalised(...)100%22100%

File(s)

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

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Interfaces;
 2using ArturRios.Heimdall.Command.Input;
 3using ArturRios.Heimdall.Command.Output;
 4using ArturRios.Heimdall.Domain.Entities;
 5using ArturRios.Heimdall.Shared.Messages;
 6using ArturRios.Heimdall.Shared.Services;
 7using ArturRios.Mediator.Command.Interfaces;
 8using ArturRios.Output;
 9using FluentValidation;
 10using Microsoft.EntityFrameworkCore;
 11
 12namespace ArturRios.Heimdall.Command.Handlers;
 13
 14/// <summary>
 15///     Handles <see cref="SetScopeEmailLinksCommand" />: the same order of checks as
 16///     <see cref="SetSelfRegistrationCommandHandler" /> — the links are usable, the scope is live, a
 17///     Scope Admin actor owns it — then writes <c>EmailVerificationUrl</c> and <c>PasswordResetUrl</c>
 18///     and returns the scope. Nothing else on the scope is touched.
 19/// </summary>
 1520public class SetScopeEmailLinksCommandHandler(
 1521    IValidator<SetScopeEmailLinksCommand> validator,
 1522    IAsyncReadOnlyRepository<Scope, long> scopeReader,
 1523    IAsyncRepository<Scope, long> scopeWriter,
 1524    IScopeOwnershipChecker scopeOwnership)
 25    : ICommandHandlerAsync<SetScopeEmailLinksCommand, SetScopeEmailLinksCommandOutput>
 26{
 27    public async Task<DataOutput<SetScopeEmailLinksCommandOutput?>> HandleAsync(
 28        SetScopeEmailLinksCommand command, CancellationToken cancellationToken = default)
 29    {
 1530        var output = DataOutput<SetScopeEmailLinksCommandOutput?>.New;
 31
 1532        var validation = await validator.ValidateAsync(command, cancellationToken);
 33
 1534        if (!validation.IsValid)
 35        {
 436            return output.WithErrors(validation.Errors.Select(failure => failure.ErrorMessage));
 37        }
 38
 1339        var scope = await scopeReader.Query()
 1340            .FirstOrDefaultAsync(x => x.PublicId == command.Id && !x.IsDeleted, cancellationToken);
 41
 1342        if (scope is null)
 43        {
 344            return output.WithError(ScopeMessages.ScopeNotFound);
 45        }
 46
 1047        if (!await scopeOwnership.ActorMayManageScopeAsync(command.ActingRole, command.ActingPersonId, scope.Id))
 48        {
 149            return output.WithError(ScopeMessages.NotScopeOwner);
 50        }
 51
 952        var ownerIds = await scopeReader.Query()
 953            .Where(x => x.PublicId == command.Id)
 954            .SelectMany(x => x.Owners.Select(owner => owner.Person.PublicId))
 955            .ToListAsync(cancellationToken);
 56
 957        scope.EmailVerificationUrl = Normalised(command.EmailVerificationUrl);
 958        scope.PasswordResetUrl = Normalised(command.PasswordResetUrl);
 959        scope.UpdatedAt = DateTime.UtcNow;
 60
 961        var update = await scopeWriter.UpdateAsync(scope);
 62
 963        if (!update.Success)
 64        {
 065            return output.WithErrors(update.Errors);
 66        }
 67
 968        return output
 969            .WithData(new SetScopeEmailLinksCommandOutput
 970            {
 971                Id = scope.PublicId,
 972                Name = scope.Name,
 973                Description = scope.Description,
 974                GoogleSignInEnabled = scope.GoogleSignInEnabled,
 975                SelfRegistrationEnabled = scope.SelfRegistrationEnabled,
 976                EmailVerificationUrl = scope.EmailVerificationUrl,
 977                PasswordResetUrl = scope.PasswordResetUrl,
 978                OwnerIds = ownerIds,
 979                CreatedAt = scope.CreatedAt,
 980                UpdatedAt = scope.UpdatedAt
 981            })
 982            .WithMessage(ScopeMessages.EmailLinksUpdatedSuccessfully);
 1583    }
 84
 85    // An empty or blank link is stored as "no link", so the senders have one way to read it.
 1886    private static string? Normalised(string? url) => string.IsNullOrWhiteSpace(url) ? null : url.Trim();
 87}