Rebase de CAT-001 sobre main (post #29) requiere:
- EnsureV016SchemaAsync en SqlTestFixture
- Rubro_History en TablesToIgnore central (el commit original b1be4a5 se skipeo por ser obsoleto post consolidacion)
- catalogo:rubros:gestionar en seed canonical de Permiso + RolPermiso admin
- RubroRepositoryTests refactorizado al patron [Collection] + SqlTestFixture
- RubrosControllerTests apunta a TestConnectionStrings.ApiTestDb
- Counts de permisos admin actualizados 24 -> 25 en 5 tests
Verify: App 819/819 + Api 251/251 + vitest 349/349 verde post-rebase.
DATETIME2(3) + cursor roundtrip via O format perdía sub-ms de
DateTime.UtcNow causando ~37% flake rate. Timestamp fijo con sub-ms=0
elimina la ambigüedad.
Fixes residual flake del issue #29.
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.
Registra la colección "Database" con SqlTestFixture como fixture compartido
para Application.Tests (elimina el ctor-con-string inline en cada test class).
Agrega Using global a ambos proyectos para evitar usings por archivo.
Fix all test compilation errors caused by T400.10/T400.20/T400.30:
- Handler constructors: add TimeProvider.System as last argument
- Domain mutator calls: add DateTime.UtcNow as explicit 'now' argument
- AuditLogger/SecurityEventLogger Build() helpers: add TimeProvider.System
- JwtService test constructors: add TimeProvider.System
Cat2 coverage already present in TimeProviderArgentinaExtensionsTests.cs:
FakeTimeProvider proves GetArgentinaToday() returns ART civil date, not UTC.
Eliminar SecuenciaComprobanteTests, ReservarNumeroCommandHandlerTests,
GetProximoNumeroQueryHandlerTests y 7 tests de integración en
PuntosDeVentaControllerTests (reserva/proximo/concurrencia/secuencialidad).
SqlTestFixture ahora limpia SecuenciaComprobante+SP si existen (drops idempotentes)
y solo crea PuntoDeVenta + temporal table.
Seis ajustes post-verify detectados durante la corrida full de tests:
1. PuntoDeVentaRepository: UQ_PuntoDeVenta_Medio_AFIP (no _MedioId_NumeroAFIP)
— el catch de unique violation no disparaba → 500 en race duplicado.
2. Application.DependencyInjection: registro de 8 handlers PuntosDeVenta
— sin esto, dispatcher arrojaba "No service registered" → 500.
3. ReservarNumeroCommandHandler: backoff ampliado a 5 retries
[25, 75, 200, 500, 1200]ms para soportar 50 threads concurrentes.
4. SecuenciaComprobante: SYSTEM_VERSIONING = OFF (AD8 revisitado).
Under UPDATE concurrente sobre misma fila, el engine arroja
"transaction time earlier than period start time" — limitación
conocida de Temporal Tables con alta contención de UPDATEs.
Decisión: secuencia es operacional, no configuración → sin history.
V013 y SqlTestFixture actualizados para ser idempotentes.
5. SqlTestFixture: EnsureV013SchemaAsync idempotente + PuntoDeVenta_History
en TablesToIgnore + permiso administracion:puntos_de_venta:gestionar
en seed canónico + asignación a rol admin.
6. Tests: conteos 22→23 permisos (V013 agrega uno); repository fixtures
ignoran PuntoDeVenta_History; test UpdatePdv_WhenPdvInactive eliminado
(over-specified — spec no bloquea update en PdV inactivo, solo en Medio
padre inactivo; alineado con frontend que permite editar PdV inactivo).
Resultado: 190/190 Api.Tests y tests específicos ADM-008 verdes
(Domain 13, Application 42, Api 21 = 76 tests nuevos). El único failure
residual (AuditEventRepositoryTests.QueryAsync_Limit_EmitsCursor) es
pre-existente y no relacionado a ADM-008.
Covers: verify report CRITICAL (UQ name mismatch) + WARNINGs descubiertos
durante la ejecución (DI registro, temporal tables concurrency, permiso
fixture, counts de tests pre-existentes).
- IMedioRepository, ISeccionRepository interfaces
- MediosQuery, SeccionesQuery common records
- TipoSeccion static AllowedTipos helper
- Medios: 6 use cases (Create/Update/Deactivate/Reactivate/List/GetById) with validators, handlers and DTOs
- Secciones: 6 use cases mirroring Medios; Create validates MedioId active via IMedioRepository
- 52 unit tests (xUnit + NSubstitute) all green; audit LogAsync asserted per mutating handler
- DI registrations for all 12 handlers and validators auto-scanned via AddValidatorsFromAssemblyContaining
Agrega Quartz.Extensions.Hosting 3.13.1 al catálogo central.
SIGCM2.Infrastructure/Audit/Jobs/:
- AuditPartitionManagerJob — mensual (cron '0 0 2 1 * ?', UTC). Extiende
pf_AuditEvent_Monthly y pf_SecurityEvent_Monthly con SPLIT RANGE para el
mes+2 (mantiene +1 de buffer). Idempotente: verifica existencia antes.
- AuditRetentionEnforcerJob — anual (cron '0 0 3 1 1 ?', UTC). DELETE rows
> 10 años en AuditEvent y > 5 años en SecurityEvent. Temporal history se
purga solo vía HISTORY_RETENTION_PERIOD del engine.
- AuditIntegrityCheckJob — semanal domingos (cron '0 0 1 ? * SUN', UTC).
Valida SYSTEM_VERSIONING=ON + partitions próximos 3 meses. Emite
SecurityEvent 'system.integrity_alert' failure via ISecurityEventLogger
cuando detecta inconsistencias.
AuditMaintenanceRegistration.cs:
- services.AddAuditMaintenance(configuration) wraps AddQuartz + AddQuartzHostedService
con los 3 triggers crónicos.
Program.cs:
- builder.Services.AddAuditMaintenance(configuration) wired ONLY en entornos
productivos — skipeado en 'Testing' para que los integration tests no
disparen los triggers cron durante el ciclo de vida del TestWebAppFactory.
Row-based DELETE en RetentionEnforcerJob es la opción conservadora para la
primera generación — cuando los volúmenes lo justifiquen (>200M filas), se
upgradea a SWITCH OUT + DROP para partition-level drop. Documentado en
comentario de la clase.
Tests (Strict TDD, integration):
- AuditJobsTests (3): PartitionManager crea target boundary + idempotencia,
RetentionEnforcer purga > threshold (10y audit, 5y security), IntegrityCheck
all-OK no emite alert.
Suite: 381/381 Application.Tests + 147/147 Api.Tests = 528/528 passing.
Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-6 #REQ-SEC-5, design, tasks#B11}
7 command handlers del módulo Usuarios ahora auditan via IAuditLogger:
| Handler | Action |
|-----------------------------------------|-------------------------|
| CreateUsuarioCommandHandler | usuario.create |
| UpdateUsuarioCommandHandler | usuario.update |
| DeactivateUsuarioCommandHandler | usuario.deactivate |
| ReactivateUsuarioCommandHandler | usuario.reactivate |
| ChangeMyPasswordCommandHandler | usuario.password_change |
| ResetUsuarioPasswordCommandHandler | usuario.password_reset |
| UpdateUsuarioPermisosOverridesHandler | usuario.permisos_update |
Patrón por handler (per design #D-1):
using (var tx = new TransactionScope(Required, ReadCommitted, AsyncFlowEnabled))
{
await repo.UpdateAsync(...);
await audit.LogAsync(...);
tx.Complete();
}
// post-commit reads OUTSIDE the using block
var updated = await repo.GetDetailAsync(...);
Metadata captured:
- usuario.create: after={username, nombre, apellido, email, rol} — NO password.
- usuario.update: {before, after} diff of editable fields.
- usuario.password_reset: {targetId} only — tempPassword is NEVER persisted to
audit (returned to caller once, never stored).
- usuario.permisos_update: {before, after} of grant/deny override lists.
Key fix during implementation: initially used 'using var tx = ...' (bare
declaration). This kept the TransactionScope active for the rest of the method,
causing 'The current TransactionScope is already complete' when post-commit
reads (GetDetailAsync) tried to enlist. Solution: explicit 'using (var tx = ...)
{ ... }' block that disposes the scope before post-commit reads.
AuditContextMissingException surfaces from AuditLogger when IAuditContext
lacks ActorUserId — fail-closed per #REQ-AUD-4. In integration tests, the
middleware populates ActorUserId from the JWT sub of the authenticated admin.
Test updates: 6 existing unit test classes now inject IAuditLogger mock:
- CreateUsuarioCommandHandlerTests
- UpdateUsuarioCommandHandlerTests
- DeactivateUsuarioCommandHandlerTests
- ReactivateUsuarioCommandHandlerTests
- ChangeMyPasswordCommandHandlerTests
- ResetUsuarioPasswordCommandHandlerTests
Follow-up #6 ([Auditoría] Registrar admin creador en alta de usuarios) is
closed: CreateUsuarioCommandHandler now records ActorUserId = admin JWT sub
on every user creation. TODO comment removed.
Suite: 378/378 Application.Tests + 141/141 Api.Tests = 519/519 passing.
Closes#6
Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-UM-AUD, design, tasks#B7}