< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Query.Handlers.GetScopePermissionByIdQueryHandler
Assembly: ArturRios.Heimdall.Query
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Query/Handlers/GetScopePermissionByIdQueryHandler.cs
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 70
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
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%44100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Query/Handlers/GetScopePermissionByIdQueryHandler.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Interfaces;
 2using ArturRios.Heimdall.Domain.Entities;
 3using ArturRios.Heimdall.Query.Input;
 4using ArturRios.Heimdall.Query.Output;
 5using ArturRios.Heimdall.Shared.Messages;
 6using ArturRios.Heimdall.Shared.Services;
 7using ArturRios.Mediator.Query.Interfaces;
 8using ArturRios.Output;
 9using Microsoft.EntityFrameworkCore;
 10
 11namespace ArturRios.Heimdall.Query.Handlers;
 12
 13/// <summary>
 14///     Handles <see cref="GetScopePermissionByIdQuery" /> (UC-32, FR-SP-04): retrieves a scope
 15///     permission by its <c>PublicId</c> within the addressed scope, excluding logically deleted
 16///     permissions unless explicitly requested (FR-SP-09), then applies the use case's visibility
 17///     rule — a System Admin sees any permission, anyone else must own the scope. A miss is AF-32a
 18///     (<c>ScopePermissionNotFound</c>); an actor who does not own the scope is AF-32e
 19///     (<c>NotScopeOwner</c>). Both are returned as errors rather than thrown. The permission's
 20///     internal scope key is needed for the ownership check, so the entity is loaded (not projected)
 21///     before authorization — mirroring the UC-34 delete handler.
 22/// </summary>
 1623public class GetScopePermissionByIdQueryHandler(
 1624    IAsyncReadOnlyRepository<ScopePermission, long> permissionReader,
 1625    IScopeOwnershipChecker scopeOwnership)
 26    : IQueryHandlerAsync<GetScopePermissionByIdQuery, ScopePermissionOutput>
 27{
 28    public async Task<DataOutput<ScopePermissionOutput?>> HandleAsync(GetScopePermissionByIdQuery query, CancellationTok
 29    {
 1630        var output = DataOutput<ScopePermissionOutput?>.New;
 31
 32        // AF-32a: no such permission under this scope (or it is logically deleted and was not
 33        // explicitly requested). The route's scopeId qualifies the lookup, so a permission that
 34        // lives in another scope is not the resource this path addresses. Checked before
 35        // authorization, so AF-32a and AF-32e both stay observable — a GUID nobody holds cannot be
 36        // told apart from one the caller may not see.
 1637        var permission = await permissionReader.Query()
 1638            .Include(x => x.Scope)
 1639            .FirstOrDefaultAsync(x => x.PublicId == query.Id &&
 1640                                      x.Scope.PublicId == query.ScopeId &&
 1641                                      (query.IncludeDeleted || !x.IsDeleted));
 42
 1643        if (permission is null)
 44        {
 745            return output.WithError(ScopePermissionMessages.ScopePermissionNotFound);
 46        }
 47
 48        // AF-32e: a System Admin sees every permission; anyone else must own the scope. A scope
 49        // permission has no owner of its own, so owning the scope is the only authorization.
 950        if (!await scopeOwnership.ActorMayManageScopeAsync(
 951                query.ActingRole, query.ActingPersonId, permission.ScopeId))
 52        {
 253            return output.WithError(ScopePermissionMessages.NotScopeOwner);
 54        }
 55
 756        return output
 757            .WithData(new ScopePermissionOutput
 758            {
 759                Id = permission.PublicId,
 760                Name = permission.Name,
 761                Description = permission.Description,
 762                IncludeAsJwtClaim = permission.IncludeAsJwtClaim,
 763                ScopeId = permission.Scope.PublicId,
 764                IsDeleted = permission.IsDeleted,
 765                CreatedAt = permission.CreatedAt,
 766                UpdatedAt = permission.UpdatedAt
 767            })
 768            .WithMessage(ScopePermissionMessages.ScopePermissionRetrievedSuccessfully);
 1669    }
 70}