2026-04-15 21:25:09 -03:00
|
|
|
using SIGCM2.Application.Common;
|
|
|
|
|
|
|
|
|
|
namespace SIGCM2.Application.Tests.Common;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SUITE-B-RESOLVER — R-01 a R-09 (UDT-009)
|
|
|
|
|
/// Unit tests for PermisoResolver.Resolve static helper.
|
|
|
|
|
/// Pure unit: no DB, no mocks.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class PermisoResolverTests
|
|
|
|
|
{
|
|
|
|
|
// R-01: Override vacío → effective = solo rol sin cambios
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_EmptyOverride_ReturnsRolPermisosUnchanged()
|
|
|
|
|
{
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], PermisosOverride.Empty);
|
|
|
|
|
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Equal(2, result.Count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-02: Grant nuevo permiso → se agrega al set
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_GrantNewPermiso_AddsToEffective()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: ["C"], Deny: []);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Contains("C", result);
|
|
|
|
|
Assert.Equal(3, result.Count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-03: Deny permiso del rol → se quita del set
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_DenyRolPermiso_RemovesFromEffective()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: [], Deny: ["A"]);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.DoesNotContain("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
2026-04-18 20:56:23 -03:00
|
|
|
Assert.Single(result);
|
2026-04-15 21:25:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-04: Grant duplicado (ya en rol) → idempotente, no duplicados
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_GrantDuplicated_Idempotent()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: ["B"], Deny: []);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Equal(2, result.Count); // no duplicates
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-05: Deny código inexistente en rol → no-op
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_DenyNonExistentCode_NoOp()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: [], Deny: ["X"]);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Equal(2, result.Count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-06: Grant + Deny combinados
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_GrantAndDeny_Combined()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: ["C"], Deny: ["A"]);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.DoesNotContain("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Contains("C", result);
|
|
|
|
|
Assert.Equal(2, result.Count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-07: PermisosOverride.Empty literal → mismo que rol
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_EmptyLiteral_ReturnsRolPermisosOnly()
|
|
|
|
|
{
|
|
|
|
|
var result = PermisoResolver.Resolve(["A", "B"], PermisosOverride.Empty);
|
|
|
|
|
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
Assert.Contains("B", result);
|
|
|
|
|
Assert.Equal(2, result.Count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-08: Rol vacío + grant → effective = solo el grant
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_EmptyRol_WithGrant_ReturnsGrant()
|
|
|
|
|
{
|
|
|
|
|
var overrides = new PermisosOverride(Grant: ["C"], Deny: []);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve([], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.Single(result);
|
|
|
|
|
Assert.Contains("C", result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// R-09: Rol vacío + sin overrides → effective vacío
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_EmptyRol_EmptyOverrides_ReturnsEmpty()
|
|
|
|
|
{
|
|
|
|
|
var result = PermisoResolver.Resolve([], PermisosOverride.Empty);
|
|
|
|
|
|
|
|
|
|
Assert.Empty(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extra: Deny gana sobre grant explícito (defense en runtime — validator lo bloquea antes)
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Resolve_DenyWinsOver_ExplicitGrant()
|
|
|
|
|
{
|
|
|
|
|
// Mismo código en grant y deny → deny gana (algoritmo: grant primero, deny al final)
|
|
|
|
|
var overrides = new PermisosOverride(Grant: ["C"], Deny: ["C"]);
|
|
|
|
|
|
|
|
|
|
var result = PermisoResolver.Resolve(["A"], overrides);
|
|
|
|
|
|
|
|
|
|
Assert.DoesNotContain("C", result);
|
|
|
|
|
Assert.Contains("A", result);
|
|
|
|
|
}
|
|
|
|
|
}
|