refactor(tests): Application.Tests elimina Respawner inline; usa SqlTestFixture compartido

6 clases que instanciaban Respawner directamente migran a recibir SqlTestFixture
vía ICollectionFixture. 8 clases restantes solo actualizan ConnectionString a
TestConnectionStrings.AppTestDb. Cada clase ahora es responsable únicamente de
sus seeds específicos; la limpieza de la base queda centralizada en el fixture.
This commit is contained in:
2026-04-18 21:44:36 -03:00
parent 03a695feb9
commit e0b9cba948
14 changed files with 95 additions and 466 deletions

View File

@@ -1,66 +1,29 @@
using Dapper;
using Microsoft.Data.SqlClient;
using Respawn;
using SIGCM2.TestSupport;
namespace SIGCM2.Application.Tests.Integration;
/// <summary>
/// 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.
/// </summary>
[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<string>(sql);
var definition = await _db.Connection.QuerySingleOrDefaultAsync<string>(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<string>(
var permisosJson = await _db.Connection.QuerySingleAsync<string>(
"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<string>(
var permisosJson = await _db.Connection.QuerySingleAsync<string>(
"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<string>(
var permisosJson = await _db.Connection.QuerySingleAsync<string>(
"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<string>(
var permisosJson = await _db.Connection.QuerySingleAsync<string>(
"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);
""");
}
/// <summary>
/// Replicates V009 migration idempotently — mirrors SqlTestFixture.EnsureV009SchemaAsync.
/// </summary>
@@ -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);
}
}