Commit Graph

38 Commits

Author SHA1 Message Date
b1be4a5573 fix(tests): propagar Rubro_History + permisos 25 a integration tests (CAT-001)
- SqlTestFixture: agrega EnsureV016SchemaAsync + seed del permiso catalogo:rubros:gestionar + Rubro_History al TablesToIgnore
- 6 test files con Respawner propio: agrega Rubro_History al TablesToIgnore
- 2 tests con count hardcoded (Permiso/RolPermiso): 24 -> 25 + rename
- 3 Api tests con count hardcoded (Auth/Permisos): 24 -> 25 + rename
2026-04-18 19:48:33 -03:00
3c264aa7a1 chore(udt-011): register DateOnlyJsonConverter in Program.cs AddJsonOptions 2026-04-18 09:47:19 -03:00
8dd668d5c5 test(udt-011): DateOnlyJsonConverter serialization tests (Red) 2026-04-18 09:47:13 -03:00
be6f76d107 test(udt-011): V015 migration tests for timezone views (Red) 2026-04-18 09:38:55 -03:00
8c08a706f0 test(adm-009): V014MigrationTests con filtros especificos por seed (no count total) 2026-04-17 19:11:55 -03:00
4544a000ae test(adm-009): FiscalController integration tests with JWT auth (Red→Green) 2026-04-17 18:39:55 -03:00
f4bd84c3f1 feat(adm-009): V014 seed 4 TipoDeIva + 24 IngresosBrutos + permiso fiscal:gestionar 2026-04-17 17:41:25 -03:00
93664612d5 test(adm-009): V014 migration integration tests (Red) 2026-04-17 17:32:02 -03:00
6458ee0106 revert(tests): eliminar tests de reserva/concurrencia/secuencialidad ADM-008
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.
2026-04-17 14:16:21 -03:00
65787db272 fix(adm-008): correcciones del verify loop
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).
2026-04-17 13:02:35 -03:00
48779543f9 test(api): integration tests CRUD + concurrencia + secuencialidad PuntosDeVenta
T5.3: 18 tests cubriendo 401/403, create, get, list, update, deactivate, reactivate, reservar, proximo.
T5.4: 50 tasks paralelas → 50 numeros distintos sin duplicados.
T5.5: 100 reservas en serie → {1..100} en orden.
2026-04-17 12:34:35 -03:00
3829c93af6 test(secciones): cobertura cascada de inactividad — issue #16 2026-04-17 11:46:14 -03:00
13480ad8c2 feat(api): MediosController + SeccionesController + ExceptionFilter mappings — ADM-001 B6
- POST/GET/PUT + deactivate/reactivate endpoints for /api/v1/admin/medios
- POST/GET/PUT + deactivate/reactivate endpoints for /api/v1/admin/secciones
- ExceptionFilter: add Medio/Seccion 404+409 mappings after RolInUseException
- Integration tests: 19 scenarios covering 401/403/201/404/409/idempotency/AuditEvent
- All 166 Api.Tests + 458 Application.Tests passing
2026-04-16 19:16:33 -03:00
a6f4011806 fix(tests): resolve ADM-001 regressions in Api.Tests fixture
- Update hardcoded permiso count from 21 → 22 in AuthControllerTests and
  PermisosEndpointTests after V011 added 'administracion:secciones:gestionar'
- The TestSupport SqlTestFixture already had Medio_History/Seccion_History in
  TablesToIgnore; tests were failing due to stale binaries (needed rebuild)
2026-04-16 19:08:32 -03:00
2bb90118ab feat(api): GET /audit/events + /health/audit (UDT-010 B10)
AuditController:
- GET /api/v1/audit/events?actorUserId&targetType&targetId&from&to&cursor&limit
- Protected by [RequirePermission("administracion:auditoria:ver")] — reuses
  the existing permission (V005/V006 seed assigns it to admin).
- 400 on limit out of [1,100] or from > to.
- Cursor-based DESC pagination via AuditEventRepository.QueryAsync.

AuditHealthCheck (IHealthCheck):
- Validates SYSTEM_VERSIONING ON on Usuario/Rol/Permiso/RolPermiso.
- Validates partition boundaries exist for next 3 months (both AuditEvent and
  SecurityEvent functions).
- Reports last audit event age (lenient 24h to accommodate dev/test quiet envs).
- Validates HISTORY_RETENTION_PERIOD == 10 YEARS on all 4 tables.
  Key fix during impl: sys.tables.history_retention_period is stored in UNITS
  (1=INFINITE, 3=DAY, 4=WEEK, 5=MONTH, 6=YEAR), NOT seconds. Assertion: period=10
  AND unit=6 (10 YEARS).
- Mapped at /health/audit via app.MapHealthChecks with tag 'audit'.

Tests (Strict TDD, integration against SIGCM2_Test):
- AuditControllerTests (5): without-auth 401, without-permission 403 (cajero),
  admin with filter returns events, invalid limit 400, from>to 400.
- AuditHealthCheckTests (1): returns Healthy with V010 applied.

Suite: 378/378 Application.Tests + 147/147 Api.Tests = 525/525 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-7/8, design, tasks#B10}
2026-04-16 17:05:40 -03:00
b619c05762 feat(audit): security events en Auth + authorization handlers (UDT-010 B9)
Instruments auth pipeline with ISecurityEventLogger per #REQ-AUTH-SEC:

LoginCommandHandler:
- login success → action=login result=success actorUserId=user.Id
- login failure disaggregated internally (client still sees 401 unified):
  user_not_found / user_inactive / invalid_password
  — attempts captured with attemptedUsername + FailureReason

LogoutCommandHandler:
- action=logout result=success actorUserId=cmd.UsuarioId

RefreshCommandHandler:
- refresh.issue success on successful rotation
- refresh.reuse_detected failure when revoked token is presented (chain
  revoke already happens; we add the security event with metadata.familyId)
- refresh.issue failure for: token_expired / sub_mismatch / user_not_found /
  user_inactive

PermissionAuthorizationHandler:
- permission.denied failure on require-permission rejection, with metadata
  { permissionRequired, endpoint, method }. ActorUserId from JWT sub.

DI: ISecurityEventLogger was already registered by B6 (AddInfrastructure).

Test updates: 4 test classes now inject ISecurityEventLogger mock:
- LoginCommandHandlerTests, LogoutCommandHandlerTests, RefreshCommandHandlerTests
- PermissionAuthorizationHandlerTests (Api.Tests)

Suite: 378/378 Application.Tests + 141/141 Api.Tests = 519/519 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-SEC-2/3/4/5 #REQ-AUTH-SEC,
design, tasks#B9}
2026-04-16 13:59:27 -03:00
0b4af4c332 feat(api): audit context middleware + scoped impl (UDT-010 B4)
Wires the request-scoped audit context per design #D-2:

Middleware pipeline in Program.cs:
  app.UseCors()
  app.UseMiddleware<CorrelationIdMiddleware>()  // PRE-AUTH
  app.UseAuthentication()
  app.UseMiddleware<AuditActorMiddleware>()     // POST-AUTH
  app.UseAuthorization()
  app.MapControllers()

SIGCM2.Api/Middleware/CorrelationIdMiddleware.cs:
- Preserves client-sent X-Correlation-Id header when a valid GUID, otherwise
  generates Guid.NewGuid(). Stores in HttpContext.Items (audit:correlationId).
- Captures Ip (Connection.RemoteIpAddress) + UserAgent header into Items.
- Echoes the correlation id back via response header (OnStarting + immediate
  set — immediate set makes unit testing against DefaultHttpContext reliable).

SIGCM2.Api/Middleware/AuditActorMiddleware.cs:
- Reads JWT 'sub' claim from authenticated HttpContext.User, parses to int,
  stores as audit:actorUserId. Anonymous / non-numeric sub leaves it unset.

SIGCM2.Infrastructure/Audit/AuditContext.cs (IAuditContext scoped impl):
- Reads Items entries via IHttpContextAccessor. Returns null / Guid.Empty
  when no HttpContext is available (jobs, tests without middleware).
- ActorRoleId intentionally null for now — rol code → id resolution is
  deferred; the logger may resolve it at persist time in a later batch.

DI registration (Infrastructure/DependencyInjection.cs):
- services.AddScoped<IAuditContext, AuditContext>()

Tests (Strict TDD):
- CorrelationIdMiddlewareTests (6): generates/preserves/handles-malformed
  correlation id, sets response header, captures ip/ua, calls next.
- AuditActorMiddlewareTests (5): authenticated/anonymous/no-sub/non-numeric/
  calls-next.
- AuditContextTests (7): reads from Items, null-http-context defaults,
  ActorRoleId currently null.

Suite: 355/355 Application.Tests + 141/141 Api.Tests = 496/496 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-3/9, design#D-2, tasks#B4}
2026-04-16 13:32:13 -03:00
c95bc7fe01 fix(tests): extend Respawn + collection config for UDT-010 temporal tables
Follow-up of B1 (V010 migration). Issues found when running the full suite
cross-assembly:

1. Respawn 'Cannot delete rows from a temporal history table' error:
   4 per-class Respawner configs in SIGCM2.Application.Tests did not
   include the newly-created *_History tables introduced by V010
   (Usuario_History / Rol_History / Permiso_History / RolPermiso_History).
   The engine rejects direct DELETE on system-versioned history tables.
   Extended TablesToIgnore in all 4 configs.

2. FK_RefreshToken_Usuario violation in RolRepositoryTests.InitializeAsync:
   Manual 'DELETE FROM Usuario' failed when residual RefreshTokens from
   prior suites existed. Added 'DELETE FROM RefreshToken' before the
   Usuario cleanup to respect FK order. Latent bug surfaced by a new
   test-run ordering — not UDT-010 specific, but fixed in scope.

3. UQ_Usuario_Username duplicate admin race:
   TransactionScopeSpikeTests (B0) and V010MigrationTests (B1) were
   missing [Collection("ApiIntegration")], causing them to run in
   parallel with the rest of SIGCM2.Api.Tests and race on SeedAdmin.
   Serialized by adding the Collection attribute.

Suite now passes cross-assembly: 130/130 Api.Tests + 336/336 Application.Tests.

Refs: sdd/udt-010-auditoria-trazabilidad/apply-progress (B1 follow-up)
2026-04-16 13:22:56 -03:00
1c79dfa0a4 feat(db): V010 audit infrastructure + temporal tables
Applied to SIGCM2 (dev) and SIGCM2_Test.

V010__audit_infrastructure.sql (idempotent, ~280 LoC):
- Filegroups AUDIT_HOT + AUDIT_COLD with physical files (per-DB logical names
  via DB_NAME() prefix to avoid collision in dev/test).
- pf/ps_AuditEvent_Monthly + pf/ps_SecurityEvent_Monthly (RANGE RIGHT,
  DATETIME2(3), 14 boundaries 2026-01..2027-02 → 15 partitions). Job extends
  forward monthly in B11.
- dbo.AuditEvent (partitioned, clustered PK on OccurredAt+Id) + 4 indexes
  (Actor/Target/Action/Correlation) with PAGE compression.
- dbo.SecurityEvent (partitioned) + 3 indexes (Actor/Action_Result/Ip_Failure).
- CHECK constraints: Action LIKE '%.%', ISJSON(Metadata), Result IN (success|failure).
- SYSTEM_VERSIONING ON in Usuario/Rol/Permiso/RolPermiso with 10 YEARS retention +
  PAGE compression in history tables.
- No hard FK on ActorUserId → Usuario.Id (soft FK — audit must survive user deletion).

V010_ROLLBACK.sql: emergency reversal (WARNING: destroys all audit history).

database/README.md: migration order + V010 prod-apply notes.

tests/SIGCM2.TestSupport/SqlTestFixture.cs:
- EnsureV010SchemaAsync() validates audit infra is applied (fails fast with
  clear message if not — migration itself requires ALTER DATABASE privileges
  and is applied manually via sqlcmd).
- Respawn TablesToIgnore extended with *_History (engine rejects direct DELETE
  on system-versioned history tables).

tests/SIGCM2.Api.Tests/Audit/V010MigrationTests.cs — 5 smoke tests:
- AuditEvent insert+roundtrip with CorrelationId.
- CK_AuditEvent_Action rejects Action without '.'.
- CK_AuditEvent_Metadata rejects non-JSON.
- CK_SecurityEvent_Result rejects invalid Result.
- Usuario SYSTEM_VERSIONING: temporal query FOR SYSTEM_TIME AS OF returns
  pre-update state + Usuario_History populated.

Suite: 130/130 passing (previous 124 + spike B0 + 5 new B1). No regressions.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-1,2, #REQ-SEC-1,
design#D-4, tasks}
2026-04-16 13:10:04 -03:00
2d1d187f6e chore(udt-010): bootstrap rama + spike anti-MSDTC
Validates design decision #D-1 (TransactionScope ambient over IUnitOfWork):
TransactionScope with TransactionScopeAsyncFlowOption.Enabled does NOT
escalate to MSDTC when multiple SqlConnections share the same connection
string. Test passes (DistributedIdentifier == Guid.Empty).

Unblocks UDT-010 batches B1-B14.

Refs: sdd/udt-010-auditoria-trazabilidad/{design,tasks}
2026-04-16 12:56:17 -03:00
47323302cc feat(api): GET /api/v1/users/{id}/permisos con CQRS handler [UDT-009] 2026-04-15 21:43:08 -03:00
bf64ffb35e feat(api): PermissionAuthorizationHandler resuelve overrides desde DB por request [UDT-009] 2026-04-15 21:32:35 -03:00
7d96d5ff18 feat(api): ResetPassword admin — TempPasswordGenerator, handler, endpoint POST /{id}/password/reset [UDT-008]
Batch 7: POST /api/v1/users/{id}/password/reset (admin only).
- TempPasswordGenerator: RandomNumberGenerator.Fill, 12-char min, full charset diversity, never logs result
- ResetUsuarioPasswordCommandHandler: self-reset guard, 404, hash, mustChangePassword=true, revoke all tokens
- ExceptionFilter: CannotSelfResetException → 400 {error: cannot-self-reset}
- Unit tests: TempPasswordGeneratorTests (8), ResetUsuarioPasswordCommandHandlerTests (5)
- Integration tests: ResetPasswordEndpointTests (6) — 200/length/self-reset/404/401/403
2026-04-15 17:55:45 -03:00
a3bd066f7b feat(api): ChangeMyPassword — validator, handler, endpoint PUT /me/password [UDT-008] 2026-04-15 17:52:15 -03:00
473566f255 feat(api): Deactivate + Reactivate usuarios — idempotentes, anti-lockout, revoke tokens [UDT-008] 2026-04-15 17:50:54 -03:00
14c385fdb1 feat(api): UpdateUsuario — handler, validator, anti-lockout guard, revoke tokens [UDT-008] 2026-04-15 17:49:19 -03:00
2925336783 feat(api): List + GetById usuarios — handlers, repo, endpoints [UDT-008] 2026-04-15 17:46:23 -03:00
8513e99554 test(api): assert count 21 permisos admin post-V007 [UDT-006] 2026-04-15 16:49:54 -03:00
0218d8d371 feat(api): migrar controllers admin a RequirePermission [UDT-006] 2026-04-15 16:34:32 -03:00
58d0df601f feat(api): RequirePermissionAttribute + PermissionAuthorizationHandler [UDT-006] 2026-04-15 16:26:30 -03:00
1a864e9f8b fix(app): validar formato codigo rol en GetRolPermisos [UDT-005]
Agrega GetRolPermisosQueryValidator con regex ^[a-z][a-z0-9_]*$ para
rechazar codigos invalidos con 400 en GET /api/v1/roles/{codigo}/permisos.
2026-04-15 15:56:49 -03:00
4913a35d06 feat(api): BATCH 5 - PermisosController + tests HTTP [UDT-005] 2026-04-15 15:42:03 -03:00
57e4cdac01 chore(tests): limpia warning xUnit2012 en CreateUsuario_WithInactiveRol_Returns400
Reemplaza Assert.True(enumerable.Any(...)) por Assert.Contains idiomatico.
2026-04-15 13:03:18 -03:00
6f999b8fcd feat(api): UDT-004 controller de roles + refactor validator UDT-003 a lookup dinamico
- RolesController /api/v1/roles CRUD admin-only: GET list, GET {codigo}, POST, PUT, DELETE (soft-delete con guard 409)
- ExceptionFilter: mapea RolNotFound (404), RolAlreadyExists (409), RolInUse (409)
- DI: registra 5 handlers de Roles (Application) y IRolRepository/RolRepository (Infrastructure)
- CreateUsuarioCommandValidator: reemplaza whitelist hardcoded por IRolRepository.ExistsActiveByCodigoAsync via MustAsync; constructor recibe (AuthOptions, IRolRepository)
- Tests: 202 verdes (173 application + 29 api). Nuevas: RolesEndpointTests (13 integration), CreateUsuarioCommandValidatorTests reescrito con NSubstitute mock, CreateUsuario_WithInactiveRol_Returns400 en Api.Tests
- Fix: ApiIntegration pasa de IClassFixture (N factories) a ICollectionFixture (1 factory shared) — evitaba ObjectDisposedException sobre RSABCrypt al compartir coleccion con multiples test classes
- tests/tests.runsettings: MaxCpuCount=1 para evitar race entre assemblies sobre SIGCM2_Test
2026-04-15 12:50:24 -03:00
34b714750a feat(api): UDT-004 dominio + repositorio + application roles (tdd)
- Migraciones V003 (tabla Rol + 8 seeds canonicos) y V004 (drop CK + FK Usuario.Rol)
- Dominio: Rol entity + 3 excepciones (RolNotFound/AlreadyExists/InUse)
- Infraestructura: RolRepository (Dapper) con List/Get/ExistsActive/Add/Update/HasActiveUsuarios
- Application: CRUD queries y commands (List, Get, Create, Update, Deactivate) + validators (codigo regex ^[a-z][a-z0-9_]*$)
- Validator UDT-003: whitelist alineada a codigos canonicos (full IRolRepository lookup diferido a Phase 5.1)
- Tests: 169 application + 15 api (todos verdes). Respawn configurado para re-seedear Rol canonical post-reset.
- Estricto TDD: RED/GREEN/TRIANGULATE en todos los handlers nuevos.
2026-04-15 12:31:29 -03:00
3d598faffc feat(api): UDT-003 registro de usuarios — backend completo (Phases 1-6)
- Domain: Usuario.ForCreation factory, UsernameAlreadyExistsException, IUsuarioRepository extendido
- Application: CreateUsuarioCommand/Validator/Handler, UsuarioCreatedDto, AuthOptions password policy
- Infrastructure: UsuarioRepository.ExistsByUsernameAsync + AddAsync (INSERT OUTPUT INSERTED.Id), RoleClaimType="rol" en TokenValidationParameters
- Api: UsuariosController POST api/v1/users [Authorize(Roles="admin")], ExceptionFilter mapea UsernameAlreadyExistsException + SqlException 2627 → 409
- Tests (unit): 43 tests — 33 validator + 10 handler (107 total, green)
- Tests (integration): 7 tests CreateUsuarioEndpoint — 401/403/400/201/409/race/e2e (green)
- Fix: TestWebAppFactory.ConfigureTestServices reemplaza SqlConnectionFactory singleton con CS de test correcto
2026-04-15 10:47:48 -03:00
4e7b2690bd test(api): add Refresh and Logout endpoint integration tests RED 2026-04-14 13:28:44 -03:00
b657dc0d2a test(udt-001): backend unit and integration tests (30 tests) 2026-04-13 21:36:09 -03:00