< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Controllers.GoogleUserController
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Controllers/GoogleUserController.cs
Line coverage
100%
Covered lines: 23
Uncovered lines: 0
Coverable lines: 23
Total lines: 111
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 5
Fully covered methods: 5
Total methods: 5
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
List()100%11100%
GetById()100%11100%
Delete()100%11100%
HardDelete()100%11100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Controllers/GoogleUserController.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Input;
 2using ArturRios.Heimdall.Command.Output;
 3using ArturRios.Heimdall.Domain.Enums;
 4using ArturRios.Heimdall.Query.Input;
 5using ArturRios.Heimdall.Query.Output;
 6using ArturRios.Heimdall.Shared.Messages;
 7using ArturRios.Heimdall.WebApi.Security;
 8using ArturRios.Mediator.Command;
 9using ArturRios.Mediator.Query;
 10using ArturRios.Output;
 11using ArturRios.Util.WebApi.AspNetCore;
 12using ArturRios.Util.WebApi.Security.Attributes;
 13using Microsoft.AspNetCore.Mvc;
 14
 15namespace ArturRios.Heimdall.WebApi.Controllers;
 16
 17[Route("api/scopes/{scopeId:guid}/google-users")]
 3318public class GoogleUserController(CommandMediator commandMediator, QueryMediator queryMediator) : Controller
 19{
 20    /// <summary>
 21    ///     Lists the Google Users of a scope, with pagination and optional name/email filters (UC-27,
 22    ///     FR-GO-14). A System Admin or an owner of the scope may call it; the ownership check
 23    ///     (AF-27b) is enforced by the handler from the acting caller.
 24    /// </summary>
 25    /// <remarks>
 26    ///     Unlike <see cref="GetById" /> this does carry a <c>RoleRequirement</c>: the authorization
 27    ///     matrix grants a Google User a read of themselves, never a listing, so every <c>User</c> is
 28    ///     refused here before the handler runs.
 29    /// </remarks>
 30    [HttpGet]
 31    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 32    public async Task<ActionResult<PaginatedOutput<GoogleUserOutput>>> List(
 33        Guid scopeId, [FromQuery] ListScopeGoogleUsersQuery query)
 34    {
 835        query.ScopeId = scopeId;
 836        HttpContext.ApplyActor(query);
 37
 838        var result = await queryMediator
 839            .ExecutePaginatedQueryAsync<ListScopeGoogleUsersQuery, GoogleUserOutput>(query);
 40
 841        return result.ToActionResult(statusMap: GoogleUserMessageMap.StatusCodes);
 842    }
 43
 44    /// <summary>
 45    ///     Retrieves a single Google User by its public identifier within a scope (UC-27, FR-GO-14).
 46    /// </summary>
 47    /// <remarks>
 48    ///     Open to any authenticated actor, deliberately. UC-27 names a Google User as one of its
 49    ///     three actors — they may read their own record — and a Google User's token carries the
 50    ///     <c>User</c> role (FR-GO-04), so any attribute strong enough to keep other Users out would
 51    ///     lock out the actor the use case grants. The per-actor rule (AF-27b) is data-dependent and
 52    ///     is therefore the handler's, as UC-07's by-id read is.
 53    /// </remarks>
 54    [HttpGet("{id:guid}")]
 55    public async Task<ActionResult<DataOutput<GoogleUserOutput?>>> GetById(
 56        Guid scopeId, Guid id, [FromQuery] bool includeDeleted = false)
 57    {
 1058        var query = new GetGoogleUserByIdQuery { ScopeId = scopeId, Id = id, IncludeDeleted = includeDeleted };
 1059        HttpContext.ApplyActor(query);
 60
 1061        var result = await queryMediator.ExecuteQueryAsync<GetGoogleUserByIdQuery, GoogleUserOutput>(query);
 62
 1063        return result.ToActionResult(statusMap: GoogleUserMessageMap.StatusCodes);
 1064    }
 65
 66    /// <summary>
 67    ///     Logically deletes a Google User by setting <c>IsDeleted = true</c> (UC-28, FR-GO-15). The
 68    ///     attribute keeps every <c>User</c> out — the authorization matrix withholds this from them,
 69    ///     Google or password alike — while the rule that depends on data it cannot see is the
 70    ///     handler's: whether the acting Scope Admin owns the Google User's scope (AF-28c).
 71    /// </summary>
 72    /// <remarks>
 73    ///     Unlike <see cref="GetById" /> there is no actor a role attribute would wrongly exclude
 74    ///     here, which is why this one carries the attribute the by-id read cannot.
 75    /// </remarks>
 76    [HttpDelete("{id:guid}")]
 77    [RoleRequirement((int)Roles.SystemAdmin, (int)Roles.ScopeAdmin)]
 78    public async Task<ActionResult<DataOutput<DeleteGoogleUserCommandOutput?>>> Delete(Guid scopeId, Guid id)
 79    {
 880        var command = new DeleteGoogleUserCommand { ScopeId = scopeId, Id = id };
 881        HttpContext.ApplyActor(command);
 82
 883        var result = await commandMediator
 884            .ExecuteCommandAsync<DeleteGoogleUserCommand, DeleteGoogleUserCommandOutput>(command);
 85
 886        return result.ToActionResult(statusMap: GoogleUserMessageMap.StatusCodes);
 887    }
 88
 89    /// <summary>
 90    ///     Permanently (hard) deletes a Google User, removing the record for good (UC-29, FR-GO-16).
 91    ///     Restricted to System Admins — the authorization matrix withholds this even from an owning
 92    ///     Scope Admin, who may only delete logically (UC-28).
 93    /// </summary>
 94    /// <remarks>
 95    ///     The one Google User endpoint where the attribute is the whole authorization rule: UC-29
 96    ///     names a single actor and nothing about the decision depends on data, so the handler applies
 97    ///     none and the command carries no acting person. Nothing cascades either — a Google User owns
 98    ///     no dependent row.
 99    /// </remarks>
 100    [HttpDelete("{id:guid}/hard")]
 101    [RoleRequirement((int)Roles.SystemAdmin)]
 102    public async Task<ActionResult<DataOutput<HardDeleteGoogleUserCommandOutput?>>> HardDelete(
 103        Guid scopeId, Guid id)
 104    {
 7105        var result = await commandMediator
 7106            .ExecuteCommandAsync<HardDeleteGoogleUserCommand, HardDeleteGoogleUserCommandOutput>(
 7107                new HardDeleteGoogleUserCommand { ScopeId = scopeId, Id = id });
 108
 7109        return result.ToActionResult(statusMap: GoogleUserMessageMap.StatusCodes);
 7110    }
 111}