feat(application): LoginCommandHandler usa PermisoResolver para permisos efectivos [UDT-009]
This commit is contained in:
@@ -150,6 +150,135 @@ public class LoginCommandHandlerTests
|
||||
Assert.Contains("ventas:contado:cobrar", result.Usuario.Permisos);
|
||||
}
|
||||
|
||||
// ── UDT-009: PermisoResolver integration in LoginCommandHandler ─────────────
|
||||
|
||||
// L-01: Admin sin overrides → permisos = exactamente los del rol
|
||||
[Fact]
|
||||
public async Task Handle_AdminNoOverrides_PermisosEqualRolPermisos()
|
||||
{
|
||||
// Arrange
|
||||
var usuario = new Usuario(1, "admin", "$2a$12$hash", "Admin", "Sys", null, "admin",
|
||||
"""{"grant":[],"deny":[]}""", true);
|
||||
_repository.GetByUsernameAsync("admin").Returns(usuario);
|
||||
_hasher.Verify("pass", "$2a$12$hash").Returns(true);
|
||||
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||
|
||||
var adminPermisos = Enumerable.Range(1, 21)
|
||||
.Select(i => MakePermiso(i, $"perm:mod{i}:accion{i}"))
|
||||
.ToList();
|
||||
_rolPermisoRepo.GetByRolCodigoAsync("admin", Arg.Any<CancellationToken>())
|
||||
.Returns(adminPermisos.AsReadOnly() as IReadOnlyList<Permiso>);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(new LoginCommand("admin", "pass"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(21, result.Usuario.Permisos.Length);
|
||||
}
|
||||
|
||||
// L-02: Cajero + grant nuevo permiso → result contiene permiso del grant
|
||||
[Fact]
|
||||
public async Task Handle_CajeroWithGrant_PermisosContainGrantedPermiso()
|
||||
{
|
||||
// Arrange
|
||||
var usuario = new Usuario(2, "cajero1", "$2a$12$hash", "C", "A", null, "cajero",
|
||||
"""{"grant":["textos:editar"],"deny":[]}""", true);
|
||||
_repository.GetByUsernameAsync("cajero1").Returns(usuario);
|
||||
_hasher.Verify("pass", "$2a$12$hash").Returns(true);
|
||||
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||
|
||||
var cajeroPermisos = new List<Permiso>
|
||||
{
|
||||
MakePermiso(10, "ventas:contado:crear"),
|
||||
MakePermiso(11, "ventas:contado:modificar"),
|
||||
MakePermiso(12, "ventas:contado:cobrar"),
|
||||
MakePermiso(13, "ventas:contado:facturar"),
|
||||
};
|
||||
_rolPermisoRepo.GetByRolCodigoAsync("cajero", Arg.Any<CancellationToken>())
|
||||
.Returns(cajeroPermisos.AsReadOnly() as IReadOnlyList<Permiso>);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(new LoginCommand("cajero1", "pass"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result.Usuario.Permisos.Length);
|
||||
Assert.Contains("textos:editar", result.Usuario.Permisos);
|
||||
Assert.Contains("ventas:contado:crear", result.Usuario.Permisos);
|
||||
}
|
||||
|
||||
// L-03: Cajero + deny uno del rol → result NO contiene el permiso denegado
|
||||
[Fact]
|
||||
public async Task Handle_CajeroWithDeny_PermisosExcludeDeniedPermiso()
|
||||
{
|
||||
// Arrange
|
||||
var usuario = new Usuario(3, "cajero2", "$2a$12$hash", "C", "B", null, "cajero",
|
||||
"""{"grant":[],"deny":["ventas:contado:cobrar"]}""", true);
|
||||
_repository.GetByUsernameAsync("cajero2").Returns(usuario);
|
||||
_hasher.Verify("pass", "$2a$12$hash").Returns(true);
|
||||
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||
|
||||
var cajeroPermisos = new List<Permiso>
|
||||
{
|
||||
MakePermiso(10, "ventas:contado:crear"),
|
||||
MakePermiso(11, "ventas:contado:modificar"),
|
||||
MakePermiso(12, "ventas:contado:cobrar"),
|
||||
MakePermiso(13, "ventas:contado:facturar"),
|
||||
};
|
||||
_rolPermisoRepo.GetByRolCodigoAsync("cajero", Arg.Any<CancellationToken>())
|
||||
.Returns(cajeroPermisos.AsReadOnly() as IReadOnlyList<Permiso>);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(new LoginCommand("cajero2", "pass"));
|
||||
|
||||
// Assert — ventas:contado:cobrar was denied
|
||||
Assert.Equal(3, result.Usuario.Permisos.Length);
|
||||
Assert.DoesNotContain("ventas:contado:cobrar", result.Usuario.Permisos);
|
||||
Assert.Contains("ventas:contado:crear", result.Usuario.Permisos);
|
||||
}
|
||||
|
||||
// L-04: DTO always returns Permisos as string[] — not grant/deny shape
|
||||
[Fact]
|
||||
public async Task Handle_AlwaysReturnsPermisosAsStringArray_NotGrantDenyShape()
|
||||
{
|
||||
var usuario = new Usuario(1, "admin", "$2a$12$hash", "A", "B", null, "admin",
|
||||
"""{"grant":["extra:perm"],"deny":[]}""", true);
|
||||
_repository.GetByUsernameAsync("admin").Returns(usuario);
|
||||
_hasher.Verify("pass", "$2a$12$hash").Returns(true);
|
||||
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||
_rolPermisoRepo.GetByRolCodigoAsync("admin", Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Permiso>().AsReadOnly() as IReadOnlyList<Permiso>);
|
||||
|
||||
var result = await _handler.Handle(new LoginCommand("admin", "pass"));
|
||||
|
||||
// Must be string[] — no grant/deny wrapping
|
||||
Assert.IsType<string[]>(result.Usuario.Permisos);
|
||||
}
|
||||
|
||||
// L-05: Legacy PermisosJson "[]" → treated as Empty → permisos = only rol
|
||||
[Fact]
|
||||
public async Task Handle_LegacyPermisosJson_EmptyArray_TreatedAsEmpty()
|
||||
{
|
||||
var usuario = new Usuario(1, "cajero1", "$2a$12$hash", "C", "A", null, "cajero",
|
||||
"[]", true);
|
||||
_repository.GetByUsernameAsync("cajero1").Returns(usuario);
|
||||
_hasher.Verify("pass", "$2a$12$hash").Returns(true);
|
||||
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||
|
||||
var cajeroPermisos = new List<Permiso>
|
||||
{
|
||||
MakePermiso(10, "ventas:contado:crear"),
|
||||
MakePermiso(11, "ventas:contado:cobrar"),
|
||||
};
|
||||
_rolPermisoRepo.GetByRolCodigoAsync("cajero", Arg.Any<CancellationToken>())
|
||||
.Returns(cajeroPermisos.AsReadOnly() as IReadOnlyList<Permiso>);
|
||||
|
||||
var result = await _handler.Handle(new LoginCommand("cajero1", "pass"));
|
||||
|
||||
Assert.Equal(2, result.Usuario.Permisos.Length);
|
||||
Assert.Contains("ventas:contado:crear", result.Usuario.Permisos);
|
||||
Assert.Contains("ventas:contado:cobrar", result.Usuario.Permisos);
|
||||
}
|
||||
|
||||
// Helper: construir Permiso via ForRead para tests
|
||||
private static Permiso MakePermiso(int id, string codigo) =>
|
||||
Permiso.ForRead(id, codigo, codigo, null, codigo.Split(':')[0], true, DateTime.UtcNow);
|
||||
|
||||
Reference in New Issue
Block a user