2026-04-15 12:31:29 -03:00
using Dapper ;
using Microsoft.Data.SqlClient ;
using SIGCM2.Domain.Entities ;
using SIGCM2.Infrastructure.Persistence ;
namespace SIGCM2.Application.Tests.Integration ;
[Collection("Database")]
public class RolRepositoryTests : IAsyncLifetime
{
private const string ConnectionString =
"Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;" ;
private SqlConnection _connection = null ! ;
private RolRepository _repository = null ! ;
public async Task InitializeAsync ( )
{
_connection = new SqlConnection ( ConnectionString ) ;
await _connection . OpenAsync ( ) ;
2026-04-16 13:22:56 -03:00
// Clean RefreshToken first (FK to Usuario), then Usuario (FK to Rol), then custom Rol codes.
// Residual RefreshTokens from prior test suites would violate FK_RefreshToken_Usuario otherwise.
await _connection . ExecuteAsync ( "DELETE FROM dbo.RefreshToken;" ) ;
2026-04-15 12:31:29 -03:00
await _connection . ExecuteAsync ( "DELETE FROM dbo.Usuario;" ) ;
await _connection . ExecuteAsync ( "DELETE FROM dbo.Rol WHERE Codigo NOT IN ('admin','cajero','operador_ctacte','picadora','jefe_publicidad','productor','diagramacion','reportes');" ) ;
// Ensure canonical Rol seeds exist (idempotent — previous test classes may have wiped them via Respawn).
await SeedRolCanonicalAsync ( ) ;
// Reset any mutations applied to canonical seeds during prior tests.
await _connection . ExecuteAsync ( "UPDATE dbo.Rol SET Activo = 1, FechaModificacion = NULL WHERE Codigo IN ('admin','cajero','operador_ctacte','picadora','jefe_publicidad','productor','diagramacion','reportes');" ) ;
// Seed admin usuario (needed by HasActiveUsuariosAsync test expecting admin active).
await _connection . ExecuteAsync (
"INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) " +
"VALUES ('admin', '$2a$12$hash', 'Administrador', 'Sistema', 'admin', '[\"*\"]', 1);" ) ;
var factory = new SqlConnectionFactory ( ConnectionString ) ;
_repository = new RolRepository ( factory ) ;
}
public async Task DisposeAsync ( )
{
await _connection . CloseAsync ( ) ;
await _connection . DisposeAsync ( ) ;
}
private async Task SeedRolCanonicalAsync ( )
{
const string sql = "" "
SET QUOTED_IDENTIFIER ON ;
MERGE dbo . Rol AS t
USING ( VALUES
( ' admin ' , N ' Administrador ' , N ' Supervisor total ' ) ,
( ' cajero ' , N ' Cajero ' , N ' Mostrador contado ' ) ,
( ' operador_ctacte ' , N ' Operador Cta Cte ' , N ' Cuenta corriente ' ) ,
( ' picadora ' , N ' Picadora / Correctora ' , N ' Edición de textos ' ) ,
( ' jefe_publicidad ' , N ' Jefe de Publicidad ' , N ' Supervisión de pauta ' ) ,
( ' productor ' , N ' Productor ' , N ' Carga restringida ' ) ,
( ' diagramacion ' , N ' Diagramación / Taller ' , N ' Solo lectura pauta ' ) ,
( ' reportes ' , N ' Reportes ' , N ' Solo lectura reportes ' )
) AS s ( Codigo , Nombre , Descripcion )
ON t . Codigo = s . Codigo
WHEN NOT MATCHED BY TARGET THEN
INSERT ( Codigo , Nombre , Descripcion , Activo )
VALUES ( s . Codigo , s . Nombre , s . Descripcion , 1 ) ;
"" ";
await _connection . ExecuteAsync ( sql ) ;
}
// ── ListAsync ────────────────────────────────────────────────────────────
[Fact]
public async Task ListAsync_ReturnsAllCanonicalSeeds ( )
{
var list = await _repository . ListAsync ( ) ;
var codes = list . Select ( r = > r . Codigo ) . ToHashSet ( ) ;
Assert . Contains ( "admin" , codes ) ;
Assert . Contains ( "cajero" , codes ) ;
Assert . Contains ( "operador_ctacte" , codes ) ;
Assert . Contains ( "picadora" , codes ) ;
Assert . Contains ( "jefe_publicidad" , codes ) ;
Assert . Contains ( "productor" , codes ) ;
Assert . Contains ( "diagramacion" , codes ) ;
Assert . Contains ( "reportes" , codes ) ;
Assert . True ( list . Count > = 8 ) ;
}
[Fact]
public async Task ListAsync_IncludesInactiveRoles ( )
{
// Triangulation: list must include deactivated rows too.
await _connection . ExecuteAsync ( "INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('listtest_inactive', N'Inactivo de test', 0);" ) ;
var list = await _repository . ListAsync ( ) ;
var inactive = list . Single ( r = > r . Codigo = = "listtest_inactive" ) ;
Assert . False ( inactive . Activo ) ;
}
// ── GetByCodigoAsync ────────────────────────────────────────────────────
[Fact]
public async Task GetByCodigoAsync_ExistingCodigo_ReturnsRol ( )
{
var rol = await _repository . GetByCodigoAsync ( "cajero" ) ;
Assert . NotNull ( rol ) ;
Assert . Equal ( "cajero" , rol ! . Codigo ) ;
Assert . Equal ( "Cajero" , rol . Nombre ) ;
Assert . True ( rol . Activo ) ;
}
[Fact]
public async Task GetByCodigoAsync_NonExistentCodigo_ReturnsNull ( )
{
var rol = await _repository . GetByCodigoAsync ( "no_existe" ) ;
Assert . Null ( rol ) ;
}
// ── ExistsActiveByCodigoAsync ───────────────────────────────────────────
[Fact]
public async Task ExistsActiveByCodigoAsync_ActiveCodigo_ReturnsTrue ( )
{
Assert . True ( await _repository . ExistsActiveByCodigoAsync ( "admin" ) ) ;
}
[Fact]
public async Task ExistsActiveByCodigoAsync_InactiveCodigo_ReturnsFalse ( )
{
await _connection . ExecuteAsync ( "INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('exists_inactive', N'Test inactivo', 0);" ) ;
Assert . False ( await _repository . ExistsActiveByCodigoAsync ( "exists_inactive" ) ) ;
}
[Fact]
public async Task ExistsActiveByCodigoAsync_MissingCodigo_ReturnsFalse ( )
{
Assert . False ( await _repository . ExistsActiveByCodigoAsync ( "missing_codigo_xyz" ) ) ;
}
// ── AddAsync ────────────────────────────────────────────────────────────
[Fact]
public async Task AddAsync_NewRol_PersistsAndReturnsId ( )
{
var rol = Rol . ForCreation ( "addtest_new" , "Add Test" , "Rol de prueba add" ) ;
var newId = await _repository . AddAsync ( rol ) ;
Assert . True ( newId > 0 ) ;
var persisted = await _connection . QuerySingleAsync < ( string Codigo , string Nombre , string? Descripcion , bool Activo ) > (
"SELECT Codigo, Nombre, Descripcion, Activo FROM dbo.Rol WHERE Id = @Id" ,
new { Id = newId } ) ;
Assert . Equal ( "addtest_new" , persisted . Codigo ) ;
Assert . Equal ( "Add Test" , persisted . Nombre ) ;
Assert . Equal ( "Rol de prueba add" , persisted . Descripcion ) ;
Assert . True ( persisted . Activo ) ;
}
[Fact]
public async Task AddAsync_WithNullDescripcion_PersistsNull ( )
{
var rol = Rol . ForCreation ( "addtest_nulldesc" , "Null Desc" , null ) ;
var newId = await _repository . AddAsync ( rol ) ;
var desc = await _connection . ExecuteScalarAsync < string? > (
"SELECT Descripcion FROM dbo.Rol WHERE Id = @Id" , new { Id = newId } ) ;
Assert . Null ( desc ) ;
}
// ── UpdateAsync ─────────────────────────────────────────────────────────
[Fact]
public async Task UpdateAsync_ExistingCodigo_UpdatesMutableFieldsAndSetsFechaModificacion ( )
{
await _connection . ExecuteAsync (
"INSERT INTO dbo.Rol (Codigo, Nombre, Descripcion, Activo) VALUES ('updtest_one', N'Nombre Viejo', N'Desc vieja', 1);" ) ;
var updated = await _repository . UpdateAsync ( "updtest_one" , "Nombre Nuevo" , "Desc nueva" , activo : true ) ;
Assert . True ( updated ) ;
var row = await _connection . QuerySingleAsync < ( string Nombre , string? Descripcion , bool Activo , DateTime ? FechaModificacion ) > (
"SELECT Nombre, Descripcion, Activo, FechaModificacion FROM dbo.Rol WHERE Codigo = 'updtest_one'" ) ;
Assert . Equal ( "Nombre Nuevo" , row . Nombre ) ;
Assert . Equal ( "Desc nueva" , row . Descripcion ) ;
Assert . True ( row . Activo ) ;
Assert . NotNull ( row . FechaModificacion ) ;
}
[Fact]
public async Task UpdateAsync_NonExistentCodigo_ReturnsFalse ( )
{
var updated = await _repository . UpdateAsync ( "updtest_missing" , "X" , null , true ) ;
Assert . False ( updated ) ;
}
[Fact]
public async Task UpdateAsync_DoesNotChangeCodigo ( )
{
await _connection . ExecuteAsync (
"INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES ('updtest_codigo', N'Test Codigo', 1);" ) ;
await _repository . UpdateAsync ( "updtest_codigo" , "Nombre Cambiado" , null , true ) ;
var stillExists = await _connection . ExecuteScalarAsync < int > (
"SELECT COUNT(*) FROM dbo.Rol WHERE Codigo = 'updtest_codigo';" ) ;
Assert . Equal ( 1 , stillExists ) ;
}
// ── HasActiveUsuariosAsync ──────────────────────────────────────────────
[Fact]
public async Task HasActiveUsuariosAsync_WithActiveUsuario_ReturnsTrue ( )
{
// 'admin' Usuario is seeded active and references Rol.admin.
Assert . True ( await _repository . HasActiveUsuariosAsync ( "admin" ) ) ;
}
[Fact]
public async Task HasActiveUsuariosAsync_NoUsuariosReferencing_ReturnsFalse ( )
{
// 'reportes' seed has no Usuario referencing it in a clean test DB.
Assert . False ( await _repository . HasActiveUsuariosAsync ( "reportes" ) ) ;
}
[Fact]
public async Task HasActiveUsuariosAsync_OnlyInactiveUsuarioReferencing_ReturnsFalse ( )
{
// Insert an INACTIVE usuario referencing 'cajero'.
await _connection . ExecuteAsync (
"INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) " +
"VALUES ('inactivo1', '$2a$12$hash', 'Test', 'Inactivo', 'cajero', '[]', 0);" ) ;
Assert . False ( await _repository . HasActiveUsuariosAsync ( "cajero" ) ) ;
}
}