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(); // 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;"); 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( "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( "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")); } }