< Summary

Information
Class: Anichron.API.Services.AdminUserService
Assembly: Anichron.API
File(s): /home/runner/work/anichron/anichron/src/Anichron.API/Services/AdminUserService.cs
Tag: 228_36821334185
Line coverage
100%
Covered lines: 43
Uncovered lines: 0
Coverable lines: 43
Total lines: 80
Line coverage: 100%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetAllAsync(...)100%11100%
GetByIdAsync(...)100%11100%
UpdateAsync()100%1414100%
DeleteAsync()100%44100%

File(s)

/home/runner/work/anichron/anichron/src/Anichron.API/Services/AdminUserService.cs

#LineLine coverage
 1using Anichron.Core.Data;
 2using Anichron.Core.Data.Repository;
 3using Anichron.Core.Domain;
 4
 5namespace Anichron.API.Services;
 6
 7public interface IAdminUserService
 8{
 9    Task<List<User>> GetAllAsync(CancellationToken ct);
 10    Task<User?> GetByIdAsync(Guid id, CancellationToken ct);
 11    Task<AuthResult<User>> UpdateAsync(Guid callerId, Guid targetId, bool? isAdmin, bool? isDisabled, CancellationToken 
 12    Task<AuthResult> DeleteAsync(Guid callerId, Guid targetId, CancellationToken ct);
 13}
 14
 1415public sealed class AdminUserService(
 1416    IUserRepository users,
 1417    IUnitOfWork unitOfWork,
 1418    IClock clock,
 1419    ITokenService tokenService) : IAdminUserService
 20{
 21    public Task<List<User>> GetAllAsync(CancellationToken ct)
 122        => users.GetAllAsync(ct);
 23
 24    public Task<User?> GetByIdAsync(Guid id, CancellationToken ct)
 225        => users.FindByIdWithConfigsAsync(id, ct);
 26
 27    public async Task<AuthResult<User>> UpdateAsync(Guid callerId, Guid targetId, bool? isAdmin, bool? isDisabled, Cance
 828    {
 829        if (callerId == targetId)
 130            return AuthResult.Fail<User>(AuthError.CannotModifySelf);
 31
 732        var user = await users.FindByIdWithConfigsAsync(targetId, ct);
 733        if (user is null)
 134            return AuthResult.Fail<User>(AuthError.UserNotFound);
 35
 636        if (!isAdmin.HasValue && !isDisabled.HasValue)
 137            return AuthResult.Ok(user);
 38
 539        if (isAdmin.HasValue)
 140            user.IsAdmin = isAdmin.Value;
 41
 542        var shouldRevokeSessions = isDisabled == true && !user.IsDisabled;
 543        if (isDisabled.HasValue)
 444            user.IsDisabled = isDisabled.Value;
 45
 46        // Both writes, or neither: revocation goes through ExecuteUpdateAsync, which bypasses the
 47        // change tracker and commits immediately, so untransacted a failing save would leave the
 48        // sessions revoked and the user mutation lost.
 549        await unitOfWork.ExecuteInTransactionAsync(async () =>
 550        {
 551            if (shouldRevokeSessions)
 552                await tokenService.MarkAllSessionsRevokedAsync(targetId, clock.GetCurrentInstant(), ct);
 553
 554            await unitOfWork.SaveChangesAsync(ct);
 555        }, ct);
 56
 457        return AuthResult.Ok(user);
 758    }
 59
 60    public async Task<AuthResult> DeleteAsync(Guid callerId, Guid targetId, CancellationToken ct)
 361    {
 362        if (callerId == targetId)
 163            return AuthResult.Fail(AuthError.CannotModifySelf);
 64
 265        var user = await users.FindByIdAsync(targetId, ct);
 266        if (user is null)
 167            return AuthResult.Fail(AuthError.UserNotFound);
 68
 69        // Same revoke-then-save pairing as UpdateAsync, transacted for the same reason — here the
 70        // loss would be a user whose sessions are all revoked but whose row was never deleted.
 171        users.Remove(user);
 172        await unitOfWork.ExecuteInTransactionAsync(async () =>
 173        {
 174            await tokenService.MarkAllSessionsRevokedAsync(targetId, clock.GetCurrentInstant(), ct);
 175            await unitOfWork.SaveChangesAsync(ct);
 176        }, ct);
 77
 178        return AuthResult.Ok();
 379    }
 80}