< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Handlers.WithdrawScopeOwnerInvitationCommandHandler
Assembly: ArturRios.Heimdall.Command
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Application/ArturRios.Heimdall.Command/Handlers/WithdrawScopeOwnerInvitationCommandHandler.cs
Line coverage
100%
Covered lines: 22
Uncovered lines: 0
Coverable lines: 22
Total lines: 70
Line coverage: 100%
Branch coverage
100%
Covered branches: 8
Total branches: 8
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%88100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Application/ArturRios.Heimdall.Command/Handlers/WithdrawScopeOwnerInvitationCommandHandler.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.Domain.Enums;
 6using ArturRios.Heimdall.Domain.Persistence;
 7using ArturRios.Heimdall.Shared.Messages;
 8using ArturRios.Heimdall.Shared.Services;
 9using ArturRios.Mediator.Command.Interfaces;
 10using ArturRios.Output;
 11using Microsoft.EntityFrameworkCore;
 12
 13namespace ArturRios.Heimdall.Command.Handlers;
 14
 15/// <summary>
 16///     Handles <see cref="WithdrawScopeOwnerInvitationCommand" /> (UC-58, FR-SC-15): an owner of the scope, or
 17///     a System Admin, ends an open invitation as withdrawn. All failures are returned as errors on the
 18///     <see cref="DataOutput{T}" /> rather than thrown.
 19/// </summary>
 20/// <remarks>
 21///     A missing or logically deleted scope is AF-58a and a caller who may not manage it AF-58b, checked
 22///     before the invitation is looked up so a non-owner learns nothing about which ids exist. The invitation
 23///     is looked up within the scope, so another scope's is not found (AF-58c), exactly like an ended, an
 24///     expired or an unknown one, or one that an answer ended between the read and the write.
 25/// </remarks>
 1226public class WithdrawScopeOwnerInvitationCommandHandler(
 1227    IAsyncReadOnlyRepository<Scope, long> scopeReader,
 1228    IAsyncReadOnlyRepository<ScopeOwnerInvitation, long> invitationReader,
 1229    IScopeOwnerInvitations invitations,
 1230    IScopeOwnershipChecker scopeOwnership)
 31    : ICommandHandlerAsync<WithdrawScopeOwnerInvitationCommand, WithdrawScopeOwnerInvitationCommandOutput>
 32{
 33    public async Task<DataOutput<WithdrawScopeOwnerInvitationCommandOutput?>> HandleAsync(
 34        WithdrawScopeOwnerInvitationCommand command, CancellationToken cancellationToken = default)
 35    {
 1236        var output = DataOutput<WithdrawScopeOwnerInvitationCommandOutput?>.New;
 37
 38        // AF-58a.
 1239        var scope = await scopeReader.Query()
 1240            .FirstOrDefaultAsync(x => x.PublicId == command.ScopeId && !x.IsDeleted, cancellationToken);
 41
 1242        if (scope is null)
 43        {
 144            return output.WithError(ScopeMessages.ScopeNotFound);
 45        }
 46
 47        // AF-58b.
 1148        if (!await scopeOwnership.ActorMayManageScopeAsync(command.ActingRole, command.ActingPersonId, scope.Id))
 49        {
 250            return output.WithError(ScopeMessages.NotScopeOwner);
 51        }
 52
 953        var now = DateTime.UtcNow;
 54
 55        // AF-58c.
 956        var invitation = await invitationReader.Query()
 957            .Where(x => x.PublicId == command.InvitationId && x.ScopeId == scope.Id)
 958            .Where(ScopeOwnerInvitation.OpenAt(now))
 959            .FirstOrDefaultAsync(cancellationToken);
 60
 961        if (invitation is null ||
 962            !await invitations.TryEndAsync(invitation, ScopeOwnerInvitationOutcomes.Withdrawn, now))
 63        {
 664            return output.WithError(ScopeInvitationMessages.InvitationNotOpen);
 65        }
 66
 67        // UC-58 main flow: no message — the endpoint answers 204 with no body.
 368        return output.WithData(new WithdrawScopeOwnerInvitationCommandOutput { Id = invitation.PublicId });
 1269    }
 70}