feat(db): Medio + Seccion (temporal tables + seed) — ADM-001 B1
V011 crea dbo.Medio y dbo.Seccion con SYSTEM_VERSIONING ON (retention 10 anios) y PAGE compression en history; siembra el permiso 'administracion:secciones:gestionar' y lo asigna a rol admin. El permiso 'administracion:medios:gestionar' ya existia desde V005. V012 siembra Medios fundacionales ELDIA y ELPLATA (MERGE idempotente). Rollbacks V011/V012 validados estructuralmente; aplicacion y reaplicacion verificadas en SIGCM2_Test y SIGCM2. Fixture de tests actualizado: EnsureV011SchemaAsync, SeedMediosCanonicalAsync, ignora Medio_History y Seccion_History en Respawner.
This commit is contained in:
@@ -36,12 +36,15 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
// Applied manually via: sqlcmd ... -i database/migrations/V010__audit_infrastructure.sql
|
||||
await EnsureV010SchemaAsync();
|
||||
|
||||
// V011 (ADM-001): ensure dbo.Medio, dbo.Seccion + temporal tables + permiso 'administracion:secciones:gestionar'.
|
||||
await EnsureV011SchemaAsync();
|
||||
|
||||
_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.
|
||||
// Permiso and RolPermiso are seeded by V005/V006 — never wipe or integration tests lose the permission catalog.
|
||||
// *_History tables: UDT-010 system-versioned — Respawn cannot DELETE them directly (engine rejects).
|
||||
// *_History tables: UDT-010/ADM-001 system-versioned — Respawn cannot DELETE them directly (engine rejects).
|
||||
TablesToIgnore =
|
||||
[
|
||||
new Respawn.Graph.Table("dbo", "Rol"),
|
||||
@@ -51,6 +54,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
new Respawn.Graph.Table("dbo", "Rol_History"),
|
||||
new Respawn.Graph.Table("dbo", "Permiso_History"),
|
||||
new Respawn.Graph.Table("dbo", "RolPermiso_History"),
|
||||
new Respawn.Graph.Table("dbo", "Medio_History"),
|
||||
new Respawn.Graph.Table("dbo", "Seccion_History"),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -64,6 +69,7 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
await SeedPermisosCanonicalAsync();
|
||||
await SeedRolPermisosCanonicalAsync();
|
||||
await SeedAdminAsync();
|
||||
await SeedMediosCanonicalAsync();
|
||||
}
|
||||
|
||||
private async Task SeedRolCanonicalAsync()
|
||||
@@ -157,7 +163,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
-- 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')
|
||||
('administracion:permisos:ver', N'Ver catalogo de permisos', N'Consultar el listado de permisos del sistema', 'administracion'),
|
||||
-- V011 (ADM-001): permiso para CRUD de Secciones
|
||||
('administracion:secciones:gestionar', N'Gestionar secciones por medio', N'Crear, editar y desactivar secciones de un medio','administracion')
|
||||
) AS s (Codigo, Nombre, Descripcion, Modulo)
|
||||
ON t.Codigo = s.Codigo
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
@@ -197,6 +205,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
('admin', 'administracion:roles:gestionar'),
|
||||
('admin', 'administracion:roles_permisos:gestionar'),
|
||||
('admin', 'administracion:permisos:ver'),
|
||||
-- V011 (ADM-001)
|
||||
('admin', 'administracion:secciones:gestionar'),
|
||||
('cajero', 'ventas:contado:crear'),
|
||||
('cajero', 'ventas:contado:modificar'),
|
||||
('cajero', 'ventas:contado:cobrar'),
|
||||
@@ -241,6 +251,148 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ADM-001 (V011): applies Medio/Seccion schema + temporal + permiso 'administracion:secciones:gestionar'
|
||||
/// idempotently to the test database. Mirrors V011__create_medio_seccion.sql.
|
||||
/// Canonical seed (ELDIA, ELPLATA) vive en SeedMediosCanonicalAsync — se reaplica tras cada Respawn.
|
||||
/// </summary>
|
||||
private async Task EnsureV011SchemaAsync()
|
||||
{
|
||||
const string createMedio = """
|
||||
IF OBJECT_ID(N'dbo.Medio', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.Medio (
|
||||
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Medio PRIMARY KEY,
|
||||
Codigo VARCHAR(30) NOT NULL,
|
||||
Nombre NVARCHAR(100) NOT NULL,
|
||||
Tipo TINYINT NOT NULL,
|
||||
PlataformaEmpresaId INT NULL,
|
||||
Activo BIT NOT NULL CONSTRAINT DF_Medio_Activo DEFAULT(1),
|
||||
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Medio_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||
FechaModificacion DATETIME2(3) NULL,
|
||||
CONSTRAINT UQ_Medio_Codigo UNIQUE (Codigo),
|
||||
CONSTRAINT CK_Medio_Tipo CHECK (Tipo BETWEEN 1 AND 4)
|
||||
);
|
||||
END
|
||||
""";
|
||||
|
||||
const string createMedioIndex = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Medio_Activo_Tipo' AND object_id = OBJECT_ID('dbo.Medio'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_Medio_Activo_Tipo
|
||||
ON dbo.Medio(Activo, Tipo)
|
||||
INCLUDE (Codigo, Nombre, PlataformaEmpresaId);
|
||||
END
|
||||
""";
|
||||
|
||||
const string createSeccion = """
|
||||
IF OBJECT_ID(N'dbo.Seccion', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.Seccion (
|
||||
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Seccion PRIMARY KEY,
|
||||
MedioId INT NOT NULL,
|
||||
Codigo VARCHAR(30) NOT NULL,
|
||||
Nombre NVARCHAR(100) NOT NULL,
|
||||
Tipo VARCHAR(20) NOT NULL,
|
||||
Activo BIT NOT NULL CONSTRAINT DF_Seccion_Activo DEFAULT(1),
|
||||
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Seccion_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||
FechaModificacion DATETIME2(3) NULL,
|
||||
CONSTRAINT FK_Seccion_Medio FOREIGN KEY (MedioId) REFERENCES dbo.Medio(Id) ON DELETE NO ACTION,
|
||||
CONSTRAINT UQ_Seccion_MedioId_Codigo UNIQUE (MedioId, Codigo),
|
||||
CONSTRAINT CK_Seccion_Tipo CHECK (Tipo IN ('clasificados','notables','suplementos'))
|
||||
);
|
||||
END
|
||||
""";
|
||||
|
||||
const string createSeccionIndex = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Seccion_MedioId_Activo' AND object_id = OBJECT_ID('dbo.Seccion'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_Seccion_MedioId_Activo
|
||||
ON dbo.Seccion(MedioId, Activo)
|
||||
INCLUDE (Codigo, Nombre, Tipo);
|
||||
END
|
||||
""";
|
||||
|
||||
const string addMedioPeriod = """
|
||||
IF COL_LENGTH('dbo.Medio', 'ValidFrom') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Medio
|
||||
ADD
|
||||
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Medio_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Medio_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||
END
|
||||
""";
|
||||
|
||||
const string setMedioVersioning = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Medio') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Medio
|
||||
SET (SYSTEM_VERSIONING = ON (
|
||||
HISTORY_TABLE = dbo.Medio_History,
|
||||
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||
));
|
||||
END
|
||||
""";
|
||||
|
||||
const string addSeccionPeriod = """
|
||||
IF COL_LENGTH('dbo.Seccion', 'ValidFrom') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Seccion
|
||||
ADD
|
||||
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Seccion_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Seccion_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||
END
|
||||
""";
|
||||
|
||||
const string setSeccionVersioning = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Seccion') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Seccion
|
||||
SET (SYSTEM_VERSIONING = ON (
|
||||
HISTORY_TABLE = dbo.Seccion_History,
|
||||
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||
));
|
||||
END
|
||||
""";
|
||||
|
||||
await _connection.ExecuteAsync(createMedio);
|
||||
await _connection.ExecuteAsync(createMedioIndex);
|
||||
await _connection.ExecuteAsync(createSeccion);
|
||||
await _connection.ExecuteAsync(createSeccionIndex);
|
||||
await _connection.ExecuteAsync(addMedioPeriod);
|
||||
await _connection.ExecuteAsync(setMedioVersioning);
|
||||
await _connection.ExecuteAsync(addSeccionPeriod);
|
||||
await _connection.ExecuteAsync(setSeccionVersioning);
|
||||
// Permiso 'administracion:secciones:gestionar' + asignación a admin se siembran
|
||||
// desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync (post-respawn).
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ADM-001 (V012): MERGE seed ELDIA + ELPLATA. Re-seeded on every Respawn reset.
|
||||
/// </summary>
|
||||
private async Task SeedMediosCanonicalAsync()
|
||||
{
|
||||
const string sql = """
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
MERGE dbo.Medio AS t
|
||||
USING (VALUES
|
||||
('ELDIA', N'El Día', 1),
|
||||
('ELPLATA', N'El Plata', 1)
|
||||
) AS s (Codigo, Nombre, Tipo)
|
||||
ON t.Codigo = s.Codigo
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
INSERT (Codigo, Nombre, Tipo, PlataformaEmpresaId, Activo)
|
||||
VALUES (s.Codigo, s.Nombre, s.Tipo, NULL, 1);
|
||||
""";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UDT-010 (V010): verifies that the audit infrastructure is present.
|
||||
/// Does NOT re-apply the migration (the ALTER DATABASE ADD FILEGROUP/FILE + partition
|
||||
|
||||
Reference in New Issue
Block a user