2026-04-13 21:36:09 -03:00
|
|
|
using Dapper;
|
|
|
|
|
using Microsoft.Data.SqlClient;
|
|
|
|
|
using Respawn;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace SIGCM2.TestSupport;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manages a real SQL Server test database.
|
|
|
|
|
/// Resets state between test runs using Respawn.
|
|
|
|
|
/// Seeds the admin user after each reset.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class SqlTestFixture : IAsyncLifetime
|
|
|
|
|
{
|
|
|
|
|
private readonly string _connectionString;
|
|
|
|
|
private SqlConnection _connection = null!;
|
|
|
|
|
private Respawner _respawner = null!;
|
|
|
|
|
|
|
|
|
|
public SqlTestFixture(string connectionString)
|
|
|
|
|
{
|
|
|
|
|
_connectionString = connectionString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task InitializeAsync()
|
|
|
|
|
{
|
|
|
|
|
_connection = new SqlConnection(_connectionString);
|
|
|
|
|
await _connection.OpenAsync();
|
|
|
|
|
|
|
|
|
|
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
|
|
|
|
{
|
2026-04-15 12:31:29 -03:00
|
|
|
DbAdapter = DbAdapter.SqlServer,
|
|
|
|
|
// Rol is a lookup table seeded by migration V003 — never wipe or Usuario FK breaks.
|
2026-04-15 15:26:19 -03:00
|
|
|
// Permiso and RolPermiso are seeded by V005/V006 — never wipe or integration tests lose the permission catalog.
|
|
|
|
|
TablesToIgnore =
|
|
|
|
|
[
|
|
|
|
|
new Respawn.Graph.Table("dbo", "Rol"),
|
|
|
|
|
new Respawn.Graph.Table("dbo", "Permiso"),
|
|
|
|
|
new Respawn.Graph.Table("dbo", "RolPermiso"),
|
|
|
|
|
]
|
2026-04-13 21:36:09 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await ResetAndSeedAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task ResetAndSeedAsync()
|
|
|
|
|
{
|
|
|
|
|
await _respawner.ResetAsync(_connection);
|
2026-04-15 12:31:29 -03:00
|
|
|
await SeedRolCanonicalAsync();
|
2026-04-15 15:39:25 -03:00
|
|
|
await SeedPermisosCanonicalAsync();
|
|
|
|
|
await SeedRolPermisosCanonicalAsync();
|
2026-04-13 21:36:09 -03:00
|
|
|
await SeedAdminAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 12:31:29 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 21:36:09 -03:00
|
|
|
public async Task DisposeAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_connection is not null)
|
|
|
|
|
{
|
|
|
|
|
await _connection.CloseAsync();
|
|
|
|
|
await _connection.DisposeAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 15:39:25 -03:00
|
|
|
private async Task SeedPermisosCanonicalAsync()
|
|
|
|
|
{
|
|
|
|
|
const string sql = """
|
|
|
|
|
SET QUOTED_IDENTIFIER ON;
|
|
|
|
|
MERGE dbo.Permiso AS t
|
|
|
|
|
USING (VALUES
|
|
|
|
|
('ventas:contado:crear', N'Cargar orden contado', NULL, 'ventas'),
|
|
|
|
|
('ventas:contado:modificar', N'Modificar orden contado', NULL, 'ventas'),
|
|
|
|
|
('ventas:contado:cobrar', N'Cobrar orden contado', NULL, 'ventas'),
|
|
|
|
|
('ventas:contado:facturar', N'Facturar orden contado', NULL, 'ventas'),
|
|
|
|
|
('ventas:ctacte:crear', N'Cargar orden cuenta corriente', NULL, 'ventas'),
|
|
|
|
|
('ventas:ctacte:facturar', N'Facturar lote cuenta corriente', NULL, 'ventas'),
|
|
|
|
|
('textos:editar', N'Editar textos', NULL, 'textos'),
|
|
|
|
|
('textos:reclamos:ver', N'Ver reclamos de textos', NULL, 'textos'),
|
|
|
|
|
('pauta:azanu:ver', N'Ver AZANU en pauta', NULL, 'pauta'),
|
|
|
|
|
('pauta:limpiar', N'Limpieza de pauta', NULL, 'pauta'),
|
|
|
|
|
('pauta:recursos:fueradehora', N'Recursos fuera de hora', NULL, 'pauta'),
|
|
|
|
|
('productores:deuda:ver', N'Ver deuda propia de productores', NULL, 'productores'),
|
|
|
|
|
('productores:pendientes:crear', N'Cargar pendientes de productores', NULL, 'productores'),
|
|
|
|
|
('productores:deuda:bypass', N'Bypass de deuda de productores', NULL, 'productores'),
|
2026-04-15 16:34:32 -03:00
|
|
|
('administracion:usuarios:gestionar', N'Gestionar usuarios del sistema', N'Crear, editar y desactivar usuarios', 'administracion'),
|
|
|
|
|
('administracion:tarifarios:gestionar', N'Gestionar tarifarios', N'Crear y modificar tarifarios de publicidad', 'administracion'),
|
|
|
|
|
('administracion:medios:gestionar', N'Gestionar medios publicitarios', N'Alta y configuracion de medios', 'administracion'),
|
|
|
|
|
('administracion:auditoria:ver', N'Ver logs de auditoria', N'Acceso al dashboard de auditoria', 'administracion'),
|
|
|
|
|
-- V007 (UDT-006): permisos administrativos RBAC
|
|
|
|
|
('administracion:roles:gestionar', N'Gestionar roles del sistema', N'Crear, editar y desactivar roles RBAC', 'administracion'),
|
|
|
|
|
('administracion:roles_permisos:gestionar', N'Gestionar asignacion de permisos', N'Asignar y revocar permisos por rol', 'administracion'),
|
|
|
|
|
('administracion:permisos:ver', N'Ver catalogo de permisos', N'Consultar el listado de permisos del sistema', 'administracion')
|
2026-04-15 15:39:25 -03:00
|
|
|
) AS s (Codigo, Nombre, Descripcion, Modulo)
|
|
|
|
|
ON t.Codigo = s.Codigo
|
|
|
|
|
WHEN NOT MATCHED BY TARGET THEN
|
|
|
|
|
INSERT (Codigo, Nombre, Descripcion, Modulo)
|
|
|
|
|
VALUES (s.Codigo, s.Nombre, s.Descripcion, s.Modulo);
|
|
|
|
|
""";
|
|
|
|
|
await _connection.ExecuteAsync(sql);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SeedRolPermisosCanonicalAsync()
|
|
|
|
|
{
|
|
|
|
|
const string sql = """
|
|
|
|
|
SET QUOTED_IDENTIFIER ON;
|
|
|
|
|
MERGE dbo.RolPermiso AS t
|
|
|
|
|
USING (
|
|
|
|
|
SELECT r.Id AS RolId, p.Id AS PermisoId
|
|
|
|
|
FROM (VALUES
|
|
|
|
|
('admin', 'ventas:contado:crear'),
|
|
|
|
|
('admin', 'ventas:contado:modificar'),
|
|
|
|
|
('admin', 'ventas:contado:cobrar'),
|
|
|
|
|
('admin', 'ventas:contado:facturar'),
|
|
|
|
|
('admin', 'ventas:ctacte:crear'),
|
|
|
|
|
('admin', 'ventas:ctacte:facturar'),
|
|
|
|
|
('admin', 'textos:editar'),
|
|
|
|
|
('admin', 'textos:reclamos:ver'),
|
|
|
|
|
('admin', 'pauta:azanu:ver'),
|
|
|
|
|
('admin', 'pauta:limpiar'),
|
|
|
|
|
('admin', 'pauta:recursos:fueradehora'),
|
|
|
|
|
('admin', 'productores:deuda:ver'),
|
|
|
|
|
('admin', 'productores:pendientes:crear'),
|
|
|
|
|
('admin', 'productores:deuda:bypass'),
|
|
|
|
|
('admin', 'administracion:usuarios:gestionar'),
|
|
|
|
|
('admin', 'administracion:tarifarios:gestionar'),
|
|
|
|
|
('admin', 'administracion:medios:gestionar'),
|
|
|
|
|
('admin', 'administracion:auditoria:ver'),
|
2026-04-15 16:34:32 -03:00
|
|
|
-- V007 (UDT-006): permisos administrativos RBAC para admin
|
|
|
|
|
('admin', 'administracion:roles:gestionar'),
|
|
|
|
|
('admin', 'administracion:roles_permisos:gestionar'),
|
|
|
|
|
('admin', 'administracion:permisos:ver'),
|
2026-04-15 15:39:25 -03:00
|
|
|
('cajero', 'ventas:contado:crear'),
|
|
|
|
|
('cajero', 'ventas:contado:modificar'),
|
|
|
|
|
('cajero', 'ventas:contado:cobrar'),
|
|
|
|
|
('cajero', 'ventas:contado:facturar'),
|
|
|
|
|
('operador_ctacte', 'ventas:ctacte:crear'),
|
|
|
|
|
('operador_ctacte', 'ventas:ctacte:facturar'),
|
|
|
|
|
('picadora', 'textos:editar'),
|
|
|
|
|
('picadora', 'textos:reclamos:ver'),
|
|
|
|
|
('jefe_publicidad', 'textos:editar'),
|
|
|
|
|
('jefe_publicidad', 'textos:reclamos:ver'),
|
|
|
|
|
('jefe_publicidad', 'pauta:azanu:ver'),
|
|
|
|
|
('jefe_publicidad', 'pauta:limpiar'),
|
|
|
|
|
('jefe_publicidad', 'pauta:recursos:fueradehora'),
|
|
|
|
|
('jefe_publicidad', 'productores:deuda:ver'),
|
|
|
|
|
('jefe_publicidad', 'productores:deuda:bypass'),
|
|
|
|
|
('productor', 'productores:deuda:ver'),
|
|
|
|
|
('productor', 'productores:pendientes:crear'),
|
|
|
|
|
('diagramacion', 'pauta:azanu:ver')
|
|
|
|
|
) AS x (RolCodigo, PermisoCodigo)
|
|
|
|
|
JOIN dbo.Rol r ON r.Codigo = x.RolCodigo
|
|
|
|
|
JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo
|
|
|
|
|
) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId
|
|
|
|
|
WHEN NOT MATCHED BY TARGET THEN
|
|
|
|
|
INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId);
|
|
|
|
|
""";
|
|
|
|
|
await _connection.ExecuteAsync(sql);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 21:36:09 -03:00
|
|
|
private async Task SeedAdminAsync()
|
|
|
|
|
{
|
|
|
|
|
const string sql = """
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
""";
|
|
|
|
|
await _connection.ExecuteAsync(sql);
|
|
|
|
|
}
|
|
|
|
|
}
|