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.
This commit is contained in:
2026-04-15 12:31:29 -03:00
parent e0e9ec3b88
commit 34b714750a
37 changed files with 1510 additions and 27 deletions

View File

@@ -63,7 +63,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "Test",
apellido = "Usuario",
email = (string?)null,
rol = "vendedor"
rol = "cajero"
};
// ---------------------------------------------------------------------------
@@ -76,7 +76,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
await conn.ExecuteAsync("""
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = @Username)
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo)
VALUES (@Username, @Hash, 'Vendedor', 'Test', 'vendedor', '[]', 1)
VALUES (@Username, @Hash, 'Vendedor', 'Test', 'cajero', '[]', 1)
""",
new { Username = username, Hash = passwordHash });
}
@@ -126,7 +126,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "Vendedor",
apellido = "Test",
email = (string?)null,
rol = "vendedor"
rol = "cajero"
}, adminToken);
var createResp = await _client.SendAsync(createRequest);
@@ -190,7 +190,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "Integration",
apellido = "Test",
email = "integration@test.com",
rol = "vendedor"
rol = "cajero"
}, adminToken);
try
@@ -211,7 +211,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
Assert.True(id.GetInt32() > 0, "'id' must be positive");
Assert.Equal(newUsername, username.GetString());
Assert.Equal("vendedor", rol.GetString());
Assert.Equal("cajero", rol.GetString());
// Must NOT contain passwordHash
Assert.False(json.TryGetProperty("passwordHash", out _), "Response must NOT leak 'passwordHash'");
@@ -253,7 +253,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "First",
apellido = "User",
email = (string?)null,
rol = "vendedor"
rol = "cajero"
}, adminToken);
var firstResp = await _client.SendAsync(first);
Assert.Equal(HttpStatusCode.Created, firstResp.StatusCode);
@@ -266,7 +266,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "Second",
apellido = "User",
email = (string?)null,
rol = "consulta"
rol = "reportes"
}, adminToken);
var secondResp = await _client.SendAsync(second);
@@ -299,7 +299,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
await conn.ExecuteAsync("""
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = @Username)
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo)
VALUES (@Username, '$2a$12$placeholder_hash_for_race_test', 'Race', 'User', 'vendedor', '[]', 1)
VALUES (@Username, '$2a$12$placeholder_hash_for_race_test', 'Race', 'User', 'cajero', '[]', 1)
""", new { Username = username });
try
@@ -314,7 +314,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "Race",
apellido = "User",
email = (string?)null,
rol = "vendedor"
rol = "cajero"
}, adminToken);
var response = await _client.SendAsync(request);
@@ -357,7 +357,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
nombre = "E2E",
apellido = "Test",
email = (string?)null,
rol = "vendedor"
rol = "cajero"
}, adminToken);
var createResp = await _client.SendAsync(createReq);
@@ -380,7 +380,7 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
// Verify usuario in response
Assert.True(loginJson.TryGetProperty("usuario", out var usuario));
Assert.Equal("vendedor", usuario.GetProperty("rol").GetString());
Assert.Equal("cajero", usuario.GetProperty("rol").GetString());
}
finally
{