< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Handlers.CreateScopePermissionCommandHandler
Assembly: ArturRios.Heimdall.Command
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Handlers/CreateScopePermissionCommandHandler.cs
Line coverage
97%
Covered lines: 38
Uncovered lines: 1
Coverable lines: 39
Total lines: 88
Line coverage: 97.4%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage
100%
Covered methods: 2
Fully covered methods: 1
Total methods: 2
Method coverage: 100%
Full method coverage: 50%

Metrics

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

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Handlers/CreateScopePermissionCommandHandler.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;
 11using Scope = ArturRios.Heimdall.Domain.Entities.Scope;
 12
 13namespace ArturRios.Heimdall.Command.Handlers;
 14
 15/// <summary>
 16///     Handles <see cref="CreateScopePermissionCommand" /> (UC-31, FR-SP-01/02): validates input
 17///     (AF-31d), verifies the target scope exists and is active (AF-31a), enforces the acting role's
 18///     rule — a Scope Admin must own the scope (AF-31e), a System Admin bypasses it — then creates the
 19///     scope-permission record. A scope permission is a scope-child resource with no separate owner,
 20///     so the scope-ownership check is the only authorization.
 21/// </summary>
 3322public class CreateScopePermissionCommandHandler(
 3323    IValidator<CreateScopePermissionCommand> validator,
 3324    IAsyncReadOnlyRepository<Scope, long> scopeReader,
 3325    IAsyncRepository<ScopePermission, long> permissionWriter,
 3326    IScopeOwnershipChecker scopeOwnership)
 27    : ICommandHandlerAsync<CreateScopePermissionCommand, CreateScopePermissionCommandOutput>
 28{
 29    public async Task<DataOutput<CreateScopePermissionCommandOutput?>> HandleAsync(
 30        CreateScopePermissionCommand command, CancellationToken cancellationToken = default)
 31    {
 3332        var output = DataOutput<CreateScopePermissionCommandOutput?>.New;
 33
 34        // AF-31d: validate input shape.
 3335        var validation = await validator.ValidateAsync(command);
 36
 3337        if (!validation.IsValid)
 38        {
 639            return output.WithErrors(validation.Errors.Select(failure => failure.ErrorMessage));
 40        }
 41
 42        // AF-31a: the target scope must exist and not be logically deleted.
 3043        var scope = await scopeReader.Query()
 3044            .FirstOrDefaultAsync(x => x.PublicId == command.ScopeId && !x.IsDeleted);
 45
 3046        if (scope is null)
 47        {
 448            return output.WithError(ScopePermissionMessages.ScopeNotFound);
 49        }
 50
 51        // AF-31e: a Scope Admin actor may only act on a scope they own; a System Admin bypasses the
 52        // check inside the checker.
 2653        if (!await scopeOwnership.ActorMayManageScopeAsync(
 2654                command.ActingRole, command.ActingPersonId, scope.Id))
 55        {
 256            return output.WithError(ScopePermissionMessages.NotScopeOwner);
 57        }
 58
 59        // FR-SP-01/02: create the permission in the scope, active.
 2460        var permission = new ScopePermission
 2461        {
 2462            Name = command.Name,
 2463            Description = command.Description,
 2464            IncludeAsJwtClaim = command.IncludeAsJwtClaim,
 2465            IsDeleted = false,
 2466            ScopeId = scope.Id
 2467        };
 68
 2469        var creation = await permissionWriter.CreateAsync(permission);
 70
 2471        if (!creation.Success)
 72        {
 073            return output.WithErrors(creation.Errors);
 74        }
 75
 2476        return output
 2477            .WithData(new CreateScopePermissionCommandOutput
 2478            {
 2479                Id = permission.PublicId,
 2480                Name = permission.Name,
 2481                Description = permission.Description,
 2482                IncludeAsJwtClaim = permission.IncludeAsJwtClaim,
 2483                ScopeId = scope.PublicId,
 2484                CreatedAt = permission.CreatedAt
 2485            })
 2486            .WithMessage(ScopePermissionMessages.ScopePermissionCreatedSuccessfully);
 3387    }
 88}