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);
- }
}