diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditEventRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditEventRepositoryTests.cs index cf39f63..4420348 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditEventRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditEventRepositoryTests.cs @@ -13,8 +13,7 @@ namespace SIGCM2.Application.Tests.Infrastructure.Audit; [Collection("Database")] public sealed class AuditEventRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private AuditEventRepository _repo = null!; diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditJobsTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditJobsTests.cs index 270716c..75cfec0 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditJobsTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/AuditJobsTests.cs @@ -16,8 +16,7 @@ namespace SIGCM2.Application.Tests.Infrastructure.Audit; [Collection("Database")] public sealed class AuditJobsTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private SqlConnectionFactory _factory = null!; diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/SecurityEventRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/SecurityEventRepositoryTests.cs index 277183a..26a9e76 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/Audit/SecurityEventRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/Audit/SecurityEventRepositoryTests.cs @@ -11,8 +11,7 @@ namespace SIGCM2.Application.Tests.Infrastructure.Audit; [Collection("Database")] public sealed class SecurityEventRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private SecurityEventRepository _repo = null!; diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/IngresosBrutosRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/IngresosBrutosRepositoryTests.cs index 642434d..a47319b 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/IngresosBrutosRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/IngresosBrutosRepositoryTests.cs @@ -18,8 +18,7 @@ namespace SIGCM2.Application.Tests.Infrastructure; [Collection("Database")] public class IngresosBrutosRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private IIngresosBrutosRepository _repo = null!; diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/RefreshTokenRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/RefreshTokenRepositoryTests.cs index ad6b299..230ff72 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/RefreshTokenRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/RefreshTokenRepositoryTests.cs @@ -1,103 +1,44 @@ using Dapper; -using Microsoft.Data.SqlClient; -using Respawn; using SIGCM2.Domain.Entities; using SIGCM2.Infrastructure.Persistence; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Infrastructure; /// -/// Integration tests for RefreshTokenRepository against SIGCM2_Test. -/// Uses Respawn to reset the DB between test classes; the repository opens its own +/// Integration tests for RefreshTokenRepository against SIGCM2_Test_App. +/// Uses shared SqlTestFixture via xUnit collection fixture; the repository opens its own /// connections so transaction-scoped isolation would block on FK locks. /// [Collection("Database")] public class RefreshTokenRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; - - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + private readonly SqlTestFixture _db; private RefreshTokenRepository _repository = null!; private int _testUserId; + public RefreshTokenRepositoryTests(SqlTestFixture db) + { + _db = db; + } + public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); - - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - // Rol is a lookup table seeded by migration V003 — never wipe or Usuario FK breaks. - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // UDT-010: *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - // ADM-008 (V013): PuntoDeVenta is temporal; SecuenciaComprobante is NOT temporal (AD8 revisitado). - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - await _respawner.ResetAsync(_connection); - await SeedRolCanonicalAsync(); + await _db.ResetAndSeedAsync(); await SeedTestUserAsync(); - _testUserId = await _connection.QuerySingleAsync( + _testUserId = await _db.Connection.QuerySingleAsync( "SELECT Id FROM dbo.Usuario WHERE Username = 'test_rt_user'"); - var factory = new SqlConnectionFactory(ConnectionString); + var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb); _repository = new RefreshTokenRepository(factory); } - public async Task DisposeAsync() - { - await _respawner.ResetAsync(_connection); - 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); - } + public Task DisposeAsync() => Task.CompletedTask; private async Task SeedTestUserAsync() { - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" SET QUOTED_IDENTIFIER ON; IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'test_rt_user') INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/TipoDeIvaRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/TipoDeIvaRepositoryTests.cs index 75bc2f3..690473f 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/TipoDeIvaRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/TipoDeIvaRepositoryTests.cs @@ -19,8 +19,7 @@ namespace SIGCM2.Application.Tests.Infrastructure; [Collection("Database")] public class TipoDeIvaRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private ITipoDeIvaRepository _repo = null!; diff --git a/tests/SIGCM2.Application.Tests/Integration/PermisoRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Integration/PermisoRepositoryTests.cs index 9d64583..07d5f00 100644 --- a/tests/SIGCM2.Application.Tests/Integration/PermisoRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/PermisoRepositoryTests.cs @@ -11,8 +11,7 @@ namespace SIGCM2.Application.Tests.Integration; [Collection("Database")] public class PermisoRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private PermisoRepository _repository = null!; diff --git a/tests/SIGCM2.Application.Tests/Integration/RolPermisoRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Integration/RolPermisoRepositoryTests.cs index 0a90f50..6afcbd8 100644 --- a/tests/SIGCM2.Application.Tests/Integration/RolPermisoRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/RolPermisoRepositoryTests.cs @@ -11,8 +11,7 @@ namespace SIGCM2.Application.Tests.Integration; [Collection("Database")] public class RolPermisoRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private RolPermisoRepository _repository = null!; diff --git a/tests/SIGCM2.Application.Tests/Integration/RolRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Integration/RolRepositoryTests.cs index 17ec627..50c8545 100644 --- a/tests/SIGCM2.Application.Tests/Integration/RolRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/RolRepositoryTests.cs @@ -8,8 +8,7 @@ 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 const string ConnectionString = TestConnectionStrings.AppTestDb; private SqlConnection _connection = null!; private RolRepository _repository = null!; diff --git a/tests/SIGCM2.Application.Tests/Integration/UsuarioRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Integration/UsuarioRepositoryTests.cs index bb35e14..1783989 100644 --- a/tests/SIGCM2.Application.Tests/Integration/UsuarioRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/UsuarioRepositoryTests.cs @@ -1,66 +1,29 @@ -using Microsoft.Data.SqlClient; -using Respawn; +using Dapper; using SIGCM2.Infrastructure.Persistence; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Integration; [Collection("Database")] public class UsuarioRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; - - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + private readonly SqlTestFixture _db; private UsuarioRepository _repository = null!; + public UsuarioRepositoryTests(SqlTestFixture db) + { + _db = db; + } + public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); + await _db.ResetAndSeedAsync(); - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - // Rol is a lookup table seeded by migration V003 — never wipe or Usuario FK breaks. - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // UDT-010: *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - // ADM-008 (V013): PuntoDeVenta is temporal; SecuenciaComprobante is NOT temporal (AD8 revisitado). - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - // Reset DB, re-seed Rol canonical table (lookup) and admin user for each test class run. - await _respawner.ResetAsync(_connection); - await SeedRolCanonicalAsync(); - await SeedAdminAsync(); - - var factory = new SqlConnectionFactory(ConnectionString); + var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb); _repository = new UsuarioRepository(factory); } - public async Task DisposeAsync() - { - await _respawner.ResetAsync(_connection); - await _connection.CloseAsync(); - await _connection.DisposeAsync(); - } + public Task DisposeAsync() => Task.CompletedTask; // Scenario: GetByUsername returns correct entity when user exists [Fact] @@ -88,7 +51,7 @@ public class UsuarioRepositoryTests : IAsyncLifetime public async Task GetByUsernameAsync_DifferentUser_ReturnsCorrectUser_Cajero() { // Insert a second user with canonical rol 'cajero' (post-UDT-004 FK requires Rol.Codigo to exist). - await _connection.ExecuteAsync( + await _db.Connection.ExecuteAsync( "INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson) " + "VALUES ('cajero1', '$2a$12$hash2', 'Juan', 'Pérez', 'cajero', '[]')"); @@ -101,48 +64,4 @@ public class UsuarioRepositoryTests : IAsyncLifetime Assert.Equal("admin", admin.Rol); Assert.Equal("cajero", cajero.Rol); } - - 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); - } - - private async Task SeedAdminAsync() - { - await _connection.ExecuteAsync( - "SET QUOTED_IDENTIFIER ON; " + - "IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin') " + - "INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo) " + - "VALUES ('admin', '$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW', " + - "'Administrador', 'Sistema', 'admin', '[\"*\"]', 1)"); - } -} - -// Dapper extension helper for IDbConnection -file static class DapperHelper -{ - public static async Task ExecuteAsync(this SqlConnection conn, string sql) - { - using var cmd = conn.CreateCommand(); - cmd.CommandText = sql; - await cmd.ExecuteNonQueryAsync(); - } } diff --git a/tests/SIGCM2.Application.Tests/Integration/UsuarioRepository_PermisosTests.cs b/tests/SIGCM2.Application.Tests/Integration/UsuarioRepository_PermisosTests.cs index ed0186d..bf5aa56 100644 --- a/tests/SIGCM2.Application.Tests/Integration/UsuarioRepository_PermisosTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/UsuarioRepository_PermisosTests.cs @@ -1,84 +1,46 @@ using Dapper; -using Microsoft.Data.SqlClient; -using Respawn; using SIGCM2.Infrastructure.Persistence; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Integration; /// /// Integration tests for IUsuarioRepository.UpdatePermisosJsonAsync (UDT-009). -/// Uses SIGCM2_Test database directly. +/// Uses SIGCM2_Test_App database via shared SqlTestFixture. /// [Collection("Database")] public sealed class UsuarioRepository_PermisosTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; - - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + private readonly SqlTestFixture _db; private UsuarioRepository _repository = null!; + public UsuarioRepository_PermisosTests(SqlTestFixture db) + { + _db = db; + } + public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); + await _db.ResetAndSeedAsync(); - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // UDT-010: *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - // ADM-008 (V013): PuntoDeVenta is temporal; SecuenciaComprobante is NOT temporal (AD8 revisitado). - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - await _respawner.ResetAsync(_connection); - await SeedRolCanonicalAsync(); - - var factory = new SqlConnectionFactory(ConnectionString); + var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb); _repository = new UsuarioRepository(factory); // Seed a test user - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword) VALUES ('testuser', '$2a$12$hash', 'Test', 'User', 'cajero', '{"grant":[],"deny":[]}', 1, 0) """); } - public async Task DisposeAsync() - { - if (_connection is not null) - { - await _respawner.ResetAsync(_connection); - await _connection.CloseAsync(); - await _connection.DisposeAsync(); - } - } + public Task DisposeAsync() => Task.CompletedTask; // UPJ-01: UpdatePermisosJsonAsync persists PermisosJson and FechaModificacion [Fact] public async Task UpdatePermisosJsonAsync_PersistsJsonAndFechaModificacion() { // Arrange - var userId = await _connection.QuerySingleAsync( + var userId = await _db.Connection.QuerySingleAsync( "SELECT Id FROM dbo.Usuario WHERE Username = 'testuser'"); var newJson = """{"grant":["textos:editar"],"deny":[]}"""; var fechaMod = DateTime.UtcNow; @@ -87,7 +49,7 @@ public sealed class UsuarioRepository_PermisosTests : IAsyncLifetime await _repository.UpdatePermisosJsonAsync(userId, newJson, fechaMod); // Assert - var row = await _connection.QuerySingleAsync<(string PermisosJson, DateTime? FechaModificacion)>( + var row = await _db.Connection.QuerySingleAsync<(string PermisosJson, DateTime? FechaModificacion)>( "SELECT PermisosJson, FechaModificacion FROM dbo.Usuario WHERE Id = @Id", new { Id = userId }); @@ -112,7 +74,7 @@ public sealed class UsuarioRepository_PermisosTests : IAsyncLifetime public async Task UpdatePermisosJsonAsync_GetByIdReflectsChange() { // Arrange - var userId = await _connection.QuerySingleAsync( + var userId = await _db.Connection.QuerySingleAsync( "SELECT Id FROM dbo.Usuario WHERE Username = 'testuser'"); var newJson = """{"grant":["pauta:azanu:ver"],"deny":["ventas:contado:cobrar"]}"""; @@ -125,22 +87,4 @@ public sealed class UsuarioRepository_PermisosTests : IAsyncLifetime Assert.NotNull(usuario); Assert.Equal(newJson, usuario!.PermisosJson); } - - // ── helpers ─────────────────────────────────────────────────────────────── - - private async Task SeedRolCanonicalAsync() - { - await _connection.ExecuteAsync(""" - SET QUOTED_IDENTIFIER ON; - MERGE dbo.Rol AS t - USING (VALUES - ('admin', N'Administrador', N'Supervisor total'), - ('cajero', N'Cajero', N'Mostrador contado') - ) 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); - """); - } } diff --git a/tests/SIGCM2.Application.Tests/Integration/V009MigrationTests.cs b/tests/SIGCM2.Application.Tests/Integration/V009MigrationTests.cs index 530e4e2..8b5878d 100644 --- a/tests/SIGCM2.Application.Tests/Integration/V009MigrationTests.cs +++ b/tests/SIGCM2.Application.Tests/Integration/V009MigrationTests.cs @@ -1,66 +1,29 @@ using Dapper; -using Microsoft.Data.SqlClient; -using Respawn; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Integration; /// /// SUITE-B-MIGRATION-V009 — M-01 a M-07 (UDT-009) /// Validates the V009 migration SQL and SqlTestFixture.EnsureV009SchemaAsync. -/// Uses SIGCM2_Test database directly. +/// Uses SIGCM2_Test_App database via shared SqlTestFixture. /// [Collection("Database")] public sealed class V009MigrationTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; + private readonly SqlTestFixture _db; - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + public V009MigrationTests(SqlTestFixture db) + { + _db = db; + } public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); - - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // UDT-010: *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - // ADM-008 (V013): PuntoDeVenta is temporal; SecuenciaComprobante is NOT temporal (AD8 revisitado). - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - await _respawner.ResetAsync(_connection); - await SeedRolAsync(); + await _db.ResetAndSeedAsync(); } - public async Task DisposeAsync() - { - if (_connection is not null) - { - await _connection.CloseAsync(); - await _connection.DisposeAsync(); - } - } + public Task DisposeAsync() => Task.CompletedTask; // M-01: migration file exists on filesystem [Fact] @@ -110,7 +73,7 @@ public sealed class V009MigrationTests : IAsyncLifetime AND name = 'PermisosJson' """; - var definition = await _connection.QuerySingleOrDefaultAsync(sql); + var definition = await _db.Connection.QuerySingleOrDefaultAsync(sql); Assert.NotNull(definition); Assert.Contains(@"{""grant"":[]", definition); @@ -123,7 +86,7 @@ public sealed class V009MigrationTests : IAsyncLifetime { await EnsureV009SchemaAsync(); - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword) VALUES ('legacyempty', '$2a$12$hash', 'L', 'E', 'admin', '[]', 1, 0) """); @@ -131,7 +94,7 @@ public sealed class V009MigrationTests : IAsyncLifetime // Run migration again to migrate the newly inserted row await EnsureV009SchemaAsync(); - var permisosJson = await _connection.QuerySingleAsync( + var permisosJson = await _db.Connection.QuerySingleAsync( "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'legacyempty'"); Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson); @@ -143,14 +106,14 @@ public sealed class V009MigrationTests : IAsyncLifetime { await EnsureV009SchemaAsync(); - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword) VALUES ('legacywild', '$2a$12$hash', 'L', 'W', 'admin', '["*"]', 1, 0) """); await EnsureV009SchemaAsync(); - var permisosJson = await _connection.QuerySingleAsync( + var permisosJson = await _db.Connection.QuerySingleAsync( "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'legacywild'"); Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson); @@ -166,7 +129,7 @@ public sealed class V009MigrationTests : IAsyncLifetime await EnsureV009SchemaAsync(); // Temporarily drop and re-add without the DEFAULT so we can insert '' - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" IF EXISTS ( SELECT 1 FROM sys.default_constraints WHERE name = 'DF_Usuario_Permisos' @@ -175,7 +138,7 @@ public sealed class V009MigrationTests : IAsyncLifetime ALTER TABLE dbo.Usuario DROP CONSTRAINT DF_Usuario_Permisos; """); - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword) VALUES ('emptystruser', '$2a$12$hash', 'E', 'S', 'admin', '', 1, 0) """); @@ -183,7 +146,7 @@ public sealed class V009MigrationTests : IAsyncLifetime // Re-apply V009 (which restores constraint and migrates '' rows) await EnsureV009SchemaAsync(); - var permisosJson = await _connection.QuerySingleAsync( + var permisosJson = await _db.Connection.QuerySingleAsync( "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'emptystruser'"); Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson); @@ -196,7 +159,7 @@ public sealed class V009MigrationTests : IAsyncLifetime await EnsureV009SchemaAsync(); // Seed admin as TestFixture does post-V009 - await _connection.ExecuteAsync(""" + await _db.Connection.ExecuteAsync(""" IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin') INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword) VALUES ( @@ -206,7 +169,7 @@ public sealed class V009MigrationTests : IAsyncLifetime ) """); - var permisosJson = await _connection.QuerySingleAsync( + var permisosJson = await _db.Connection.QuerySingleAsync( "SELECT PermisosJson FROM dbo.Usuario WHERE Username = 'admin'"); Assert.Equal("""{"grant":[],"deny":[]}""", permisosJson); @@ -214,19 +177,6 @@ public sealed class V009MigrationTests : IAsyncLifetime // ── helpers ─────────────────────────────────────────────────────────────── - private async Task SeedRolAsync() - { - await _connection.ExecuteAsync(""" - SET QUOTED_IDENTIFIER ON; - MERGE dbo.Rol AS t - USING (VALUES ('admin', N'Administrador', N'Supervisor total')) - 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); - """); - } - /// /// Replicates V009 migration idempotently — mirrors SqlTestFixture.EnsureV009SchemaAsync. /// @@ -264,8 +214,8 @@ public sealed class V009MigrationTests : IAsyncLifetime OR LTRIM(RTRIM(PermisosJson)) = '' """; - await _connection.ExecuteAsync(dropConstraint); - await _connection.ExecuteAsync(addConstraint); - await _connection.ExecuteAsync(migrateRows); + await _db.Connection.ExecuteAsync(dropConstraint); + await _db.Connection.ExecuteAsync(addConstraint); + await _db.Connection.ExecuteAsync(migrateRows); } } diff --git a/tests/SIGCM2.Application.Tests/Medios/MedioRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Medios/MedioRepositoryTests.cs index 80c9fdf..9871e91 100644 --- a/tests/SIGCM2.Application.Tests/Medios/MedioRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Medios/MedioRepositoryTests.cs @@ -1,69 +1,35 @@ using Dapper; -using Microsoft.Data.SqlClient; -using Respawn; using SIGCM2.Domain.Entities; using SIGCM2.Infrastructure.Persistence; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Medios; /// -/// Integration tests for MedioRepository against SIGCM2_Test. +/// Integration tests for MedioRepository against SIGCM2_Test_App. /// TDD: RED written before implementation, GREEN after MedioRepository was created. /// Temporal: after UpdateAsync, dbo.Medio_History MUST have ≥1 row for that Id. /// [Collection("Database")] public class MedioRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; - - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + private readonly SqlTestFixture _db; private MedioRepository _repository = null!; + public MedioRepositoryTests(SqlTestFixture db) + { + _db = db; + } + public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); + await _db.ResetAndSeedAsync(); - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - // ADM-008 (V013): PuntoDeVenta is temporal; SecuenciaComprobante is NOT temporal (AD8 revisitado). - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - await _respawner.ResetAsync(_connection); - await SeedRolCanonicalAsync(); - - var factory = new SqlConnectionFactory(ConnectionString); + var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb); _repository = new MedioRepository(factory); } - public async Task DisposeAsync() - { - await _connection.CloseAsync(); - await _connection.DisposeAsync(); - } + public Task DisposeAsync() => Task.CompletedTask; // ── AddAsync + GetByIdAsync roundtrip ───────────────────────────────────── @@ -170,7 +136,7 @@ public class MedioRepositoryTests : IAsyncLifetime var updated = original!.WithUpdatedProfile("Historial v2", TipoMedio.Web, null, DateTime.UtcNow); await _repository.UpdateAsync(updated); - var historyCount = await _connection.ExecuteScalarAsync( + var historyCount = await _db.Connection.ExecuteScalarAsync( "SELECT COUNT(*) FROM dbo.Medio_History WHERE Id = @Id", new { Id = id }); Assert.True(historyCount >= 1, $"Expected ≥1 history row for Medio Id={id}, got {historyCount}"); @@ -241,29 +207,4 @@ public class MedioRepositoryTests : IAsyncLifetime Assert.Equal(3, result.Total); Assert.Equal(2, result.Items.Count); } - - // ── helpers ─────────────────────────────────────────────────────────────── - - 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); - } } diff --git a/tests/SIGCM2.Application.Tests/Secciones/SeccionRepositoryTests.cs b/tests/SIGCM2.Application.Tests/Secciones/SeccionRepositoryTests.cs index 541fb13..e0bde00 100644 --- a/tests/SIGCM2.Application.Tests/Secciones/SeccionRepositoryTests.cs +++ b/tests/SIGCM2.Application.Tests/Secciones/SeccionRepositoryTests.cs @@ -1,62 +1,33 @@ using Dapper; -using Microsoft.Data.SqlClient; -using Respawn; using SIGCM2.Domain.Entities; using SIGCM2.Infrastructure.Persistence; +using SIGCM2.TestSupport; namespace SIGCM2.Application.Tests.Secciones; /// -/// Integration tests for SeccionRepository against SIGCM2_Test. +/// Integration tests for SeccionRepository against SIGCM2_Test_App. /// TDD: RED written before implementation, GREEN after SeccionRepository was created. /// Temporal: after UpdateAsync, dbo.Seccion_History MUST have ≥1 row for that Id. /// [Collection("Database")] public class SeccionRepositoryTests : IAsyncLifetime { - private const string ConnectionString = - "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; - - private SqlConnection _connection = null!; - private Respawner _respawner = null!; + private readonly SqlTestFixture _db; private SeccionRepository _repository = null!; private MedioRepository _medioRepository = null!; private int _medioId; + public SeccionRepositoryTests(SqlTestFixture db) + { + _db = db; + } + public async Task InitializeAsync() { - _connection = new SqlConnection(ConnectionString); - await _connection.OpenAsync(); + await _db.ResetAndSeedAsync(); - _respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions - { - DbAdapter = DbAdapter.SqlServer, - TablesToIgnore = - [ - new Respawn.Graph.Table("dbo", "Rol"), - new Respawn.Graph.Table("dbo", "Permiso"), - new Respawn.Graph.Table("dbo", "RolPermiso"), - // *_History tables are system-versioned — engine rejects direct DELETE. - new Respawn.Graph.Table("dbo", "Usuario_History"), - new Respawn.Graph.Table("dbo", "Rol_History"), - new Respawn.Graph.Table("dbo", "Permiso_History"), - new Respawn.Graph.Table("dbo", "RolPermiso_History"), - // ADM-001 (V011): Medio + Seccion are temporal — history tables cannot be directly deleted. - new Respawn.Graph.Table("dbo", "Medio_History"), - new Respawn.Graph.Table("dbo", "Seccion_History"), - new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"), - // ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales. - new Respawn.Graph.Table("dbo", "TipoDeIva_History"), - new Respawn.Graph.Table("dbo", "IngresosBrutos_History"), - new Respawn.Graph.Table("dbo", "TipoDeIva"), - new Respawn.Graph.Table("dbo", "IngresosBrutos"), - ] - }); - - await _respawner.ResetAsync(_connection); - await SeedRolCanonicalAsync(); - - var factory = new SqlConnectionFactory(ConnectionString); + var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb); _repository = new SeccionRepository(factory); _medioRepository = new MedioRepository(factory); @@ -64,11 +35,7 @@ public class SeccionRepositoryTests : IAsyncLifetime _medioId = await _medioRepository.AddAsync(Medio.ForCreation("TESTMEDIO", "Medio de Prueba", TipoMedio.Diario, null)); } - public async Task DisposeAsync() - { - await _connection.CloseAsync(); - await _connection.DisposeAsync(); - } + public Task DisposeAsync() => Task.CompletedTask; // ── AddAsync + GetByIdAsync roundtrip ───────────────────────────────────── @@ -171,7 +138,7 @@ public class SeccionRepositoryTests : IAsyncLifetime var updated = original!.WithUpdatedProfile("Historial v2", "suplementos", DateTime.UtcNow); await _repository.UpdateAsync(updated); - var historyCount = await _connection.ExecuteScalarAsync( + var historyCount = await _db.Connection.ExecuteScalarAsync( "SELECT COUNT(*) FROM dbo.Seccion_History WHERE Id = @Id", new { Id = id }); Assert.True(historyCount >= 1, $"Expected ≥1 history row for Seccion Id={id}, got {historyCount}"); @@ -234,29 +201,4 @@ public class SeccionRepositoryTests : IAsyncLifetime Assert.Equal(3, result.Total); Assert.Equal(2, result.Items.Count); } - - // ── helpers ─────────────────────────────────────────────────────────────── - - 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); - } }