using Dapper; using Microsoft.Data.SqlClient; using SIGCM2.Infrastructure.Persistence; namespace SIGCM2.Application.Tests.Integration; /// /// Integration tests for RolPermisoRepository against SIGCM2_Test. /// RED: written before the repository implementation exists. /// [Collection("Database")] public class RolPermisoRepositoryTests : IAsyncLifetime { private const string ConnectionString = "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; private SqlConnection _connection = null!; private RolPermisoRepository _repository = null!; public async Task InitializeAsync() { _connection = new SqlConnection(ConnectionString); await _connection.OpenAsync(); // Ensure the 18 canonical permisos exist — idempotent MERGE. // Other test classes call Respawn.ResetAsync which may have cleared Permiso. await SeedPermisosCanonicalAsync(); // Ensure canonical RolPermiso seeds are present. await SeedRolPermisosCanonicalAsync(); // Restore RolPermiso seeds for 'cajero' (4 permisos) in case prior test modified them. await RestoreCajeroPermisosAsync(); var factory = new SqlConnectionFactory(ConnectionString); _repository = new RolPermisoRepository(factory); } private async Task SeedPermisosCanonicalAsync() { const string sql = """ SET QUOTED_IDENTIFIER ON; MERGE dbo.Permiso AS t USING (VALUES ('ventas:contado:crear', N'Cargar orden contado', NULL, 'ventas'), ('ventas:contado:modificar', N'Modificar orden contado', NULL, 'ventas'), ('ventas:contado:cobrar', N'Cobrar orden contado', NULL, 'ventas'), ('ventas:contado:facturar', N'Facturar orden contado', NULL, 'ventas'), ('ventas:ctacte:crear', N'Cargar orden cuenta corriente', NULL, 'ventas'), ('ventas:ctacte:facturar', N'Facturar lote cuenta corriente', NULL, 'ventas'), ('textos:editar', N'Editar textos', NULL, 'textos'), ('textos:reclamos:ver', N'Ver reclamos de textos', NULL, 'textos'), ('pauta:azanu:ver', N'Ver AZANU en pauta', NULL, 'pauta'), ('pauta:limpiar', N'Limpieza de pauta', NULL, 'pauta'), ('pauta:recursos:fueradehora', N'Recursos fuera de hora', NULL, 'pauta'), ('productores:deuda:ver', N'Ver deuda propia de productores', NULL, 'productores'), ('productores:pendientes:crear', N'Cargar pendientes de productores', NULL, 'productores'), ('productores:deuda:bypass', N'Bypass de deuda de productores', NULL, 'productores'), ('administracion:usuarios:gestionar', N'Gestionar usuarios del sistema', N'Crear, editar y desactivar usuarios', 'administracion'), ('administracion:tarifarios:gestionar', N'Gestionar tarifarios', N'Crear y modificar tarifarios de publicidad', 'administracion'), ('administracion:medios:gestionar', N'Gestionar medios publicitarios', N'Alta y configuracion de medios', 'administracion'), ('administracion:auditoria:ver', N'Ver logs de auditoria', N'Acceso al dashboard de auditoria', 'administracion') ) AS s (Codigo, Nombre, Descripcion, Modulo) ON t.Codigo = s.Codigo WHEN NOT MATCHED BY TARGET THEN INSERT (Codigo, Nombre, Descripcion, Modulo) VALUES (s.Codigo, s.Nombre, s.Descripcion, s.Modulo); """; await _connection.ExecuteAsync(sql); } private async Task SeedRolPermisosCanonicalAsync() { const string sql = """ SET QUOTED_IDENTIFIER ON; MERGE dbo.RolPermiso AS t USING ( SELECT r.Id AS RolId, p.Id AS PermisoId FROM (VALUES ('admin', 'ventas:contado:crear'), ('admin', 'ventas:contado:modificar'), ('admin', 'ventas:contado:cobrar'), ('admin', 'ventas:contado:facturar'), ('admin', 'ventas:ctacte:crear'), ('admin', 'ventas:ctacte:facturar'), ('admin', 'textos:editar'), ('admin', 'textos:reclamos:ver'), ('admin', 'pauta:azanu:ver'), ('admin', 'pauta:limpiar'), ('admin', 'pauta:recursos:fueradehora'), ('admin', 'productores:deuda:ver'), ('admin', 'productores:pendientes:crear'), ('admin', 'productores:deuda:bypass'), ('admin', 'administracion:usuarios:gestionar'), ('admin', 'administracion:tarifarios:gestionar'), ('admin', 'administracion:medios:gestionar'), ('admin', 'administracion:auditoria:ver'), ('cajero', 'ventas:contado:crear'), ('cajero', 'ventas:contado:modificar'), ('cajero', 'ventas:contado:cobrar'), ('cajero', 'ventas:contado:facturar'), ('operador_ctacte', 'ventas:ctacte:crear'), ('operador_ctacte', 'ventas:ctacte:facturar'), ('picadora', 'textos:editar'), ('picadora', 'textos:reclamos:ver'), ('jefe_publicidad', 'textos:editar'), ('jefe_publicidad', 'textos:reclamos:ver'), ('jefe_publicidad', 'pauta:azanu:ver'), ('jefe_publicidad', 'pauta:limpiar'), ('jefe_publicidad', 'pauta:recursos:fueradehora'), ('jefe_publicidad', 'productores:deuda:ver'), ('jefe_publicidad', 'productores:deuda:bypass'), ('productor', 'productores:deuda:ver'), ('productor', 'productores:pendientes:crear'), ('diagramacion', 'pauta:azanu:ver') ) AS x (RolCodigo, PermisoCodigo) JOIN dbo.Rol r ON r.Codigo = x.RolCodigo JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo ) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId WHEN NOT MATCHED BY TARGET THEN INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId); """; await _connection.ExecuteAsync(sql); } public async Task DisposeAsync() { // Restore cajero permisos so TablesToIgnore still reflects clean seed state. await RestoreCajeroPermisosAsync(); await _connection.CloseAsync(); await _connection.DisposeAsync(); } /// /// Restores the 4 canonical cajero permisos to match V006 seed state. /// Uses MERGE so it's idempotent. /// private async Task RestoreCajeroPermisosAsync() { // Delete any extra permisos assigned to cajero by tests await _connection.ExecuteAsync(""" DELETE rp FROM dbo.RolPermiso rp JOIN dbo.Rol r ON r.Id = rp.RolId JOIN dbo.Permiso p ON p.Id = rp.PermisoId WHERE r.Codigo = 'cajero' AND p.Codigo NOT IN ( 'ventas:contado:crear', 'ventas:contado:modificar', 'ventas:contado:cobrar', 'ventas:contado:facturar' ); """); // Re-add the 4 canonical cajero permisos if missing await _connection.ExecuteAsync(""" SET QUOTED_IDENTIFIER ON; MERGE dbo.RolPermiso AS t USING ( SELECT r.Id AS RolId, p.Id AS PermisoId FROM (VALUES ('cajero', 'ventas:contado:crear'), ('cajero', 'ventas:contado:modificar'), ('cajero', 'ventas:contado:cobrar'), ('cajero', 'ventas:contado:facturar') ) AS x (RolCodigo, PermisoCodigo) JOIN dbo.Rol r ON r.Codigo = x.RolCodigo JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo ) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId WHEN NOT MATCHED BY TARGET THEN INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId); """); } // ── GetByRolCodigoAsync ────────────────────────────────────────────────── [Fact] public async Task GetByRolCodigoAsync_Admin_Returns18Permisos() { // admin has all 18 permisos assigned in V006 seed var permisos = await _repository.GetByRolCodigoAsync("admin"); Assert.Equal(18, permisos.Count); } [Fact] public async Task GetByRolCodigoAsync_Admin_ContainsAllModules() { var permisos = await _repository.GetByRolCodigoAsync("admin"); var codigos = permisos.Select(p => p.Codigo).ToHashSet(); Assert.Contains("ventas:contado:crear", codigos); Assert.Contains("administracion:auditoria:ver", codigos); Assert.Contains("pauta:limpiar", codigos); Assert.Contains("productores:deuda:bypass", codigos); } [Fact] public async Task GetByRolCodigoAsync_Cajero_Returns4Permisos() { // cajero: ventas:contado:crear, :modificar, :cobrar, :facturar var permisos = await _repository.GetByRolCodigoAsync("cajero"); Assert.Equal(4, permisos.Count); } [Fact] public async Task GetByRolCodigoAsync_Cajero_OnlyVentasContadoPermisos() { var permisos = await _repository.GetByRolCodigoAsync("cajero"); var codigos = permisos.Select(p => p.Codigo).ToHashSet(); Assert.Contains("ventas:contado:crear", codigos); Assert.Contains("ventas:contado:modificar", codigos); Assert.Contains("ventas:contado:cobrar", codigos); Assert.Contains("ventas:contado:facturar", codigos); } [Fact] public async Task GetByRolCodigoAsync_Reportes_ReturnsEmpty() { // 'reportes' rol has 0 permisos in V006 seed var permisos = await _repository.GetByRolCodigoAsync("reportes"); Assert.Empty(permisos); } [Fact] public async Task GetByRolCodigoAsync_NonExistentRol_ReturnsEmpty() { // Unknown rol código — returns empty list, not an exception var permisos = await _repository.GetByRolCodigoAsync("rol_inexistente_xyz"); Assert.Empty(permisos); } [Fact] public async Task GetByRolCodigoAsync_ReturnsFullPermisoEntities() { var permisos = await _repository.GetByRolCodigoAsync("cajero"); var primero = permisos.First(); // All entity fields must be populated Assert.True(primero.Id > 0); Assert.False(string.IsNullOrWhiteSpace(primero.Codigo)); Assert.False(string.IsNullOrWhiteSpace(primero.Nombre)); } // ── ReplaceForRolAsync ─────────────────────────────────────────────────── [Fact] public async Task ReplaceForRolAsync_ReplacesExistingSetWithNewSet() { // Get cajero's rol ID and a different permiso ID var cajeroId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Rol WHERE Codigo = 'cajero'"); var textoPermisoId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Permiso WHERE Codigo = 'textos:editar'"); // Replace cajero's 4 permisos with just 1 await _repository.ReplaceForRolAsync(cajeroId, new[] { textoPermisoId }); var permisos = await _repository.GetByRolCodigoAsync("cajero"); Assert.Single(permisos); Assert.Equal("textos:editar", permisos[0].Codigo); } [Fact] public async Task ReplaceForRolAsync_Idempotent_SameCallTwiceProducesSameResult() { var cajeroId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Rol WHERE Codigo = 'cajero'"); var permisoId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Permiso WHERE Codigo = 'ventas:contado:crear'"); await _repository.ReplaceForRolAsync(cajeroId, new[] { permisoId }); await _repository.ReplaceForRolAsync(cajeroId, new[] { permisoId }); var permisos = await _repository.GetByRolCodigoAsync("cajero"); Assert.Single(permisos); Assert.Equal("ventas:contado:crear", permisos[0].Codigo); } [Fact] public async Task ReplaceForRolAsync_WithEmptyList_DeletesAllPermisos() { var cajeroId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Rol WHERE Codigo = 'cajero'"); await _repository.ReplaceForRolAsync(cajeroId, Array.Empty()); var permisos = await _repository.GetByRolCodigoAsync("cajero"); Assert.Empty(permisos); } [Fact] public async Task ReplaceForRolAsync_PostReplace_GetReflectsNewSet() { var cajeroId = await _connection.ExecuteScalarAsync( "SELECT Id FROM dbo.Rol WHERE Codigo = 'cajero'"); // Get IDs of 2 specific permisos var rows = await _connection.QueryAsync<(int Id, string Codigo)>( "SELECT Id, Codigo FROM dbo.Permiso WHERE Codigo IN ('pauta:azanu:ver', 'pauta:limpiar')"); var ids = rows.Select(r => r.Id).ToArray(); await _repository.ReplaceForRolAsync(cajeroId, ids); var permisos = await _repository.GetByRolCodigoAsync("cajero"); var codigos = permisos.Select(p => p.Codigo).ToHashSet(); Assert.Equal(2, permisos.Count); Assert.Contains("pauta:azanu:ver", codigos); Assert.Contains("pauta:limpiar", codigos); } }