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
This commit is contained in:
@@ -11,11 +11,11 @@ namespace SIGCM2.Api.Tests.Usuarios;
|
||||
/// <summary>
|
||||
/// Integration tests for POST api/v1/users (UDT-003).
|
||||
/// These tests run against SIGCM2_Test database via TestWebAppFactory.
|
||||
/// Each test class instance gets the full WebApp factory (shared via IClassFixture).
|
||||
/// DB reset happens once per test run (SqlTestFixture.InitializeAsync → ResetAndSeedAsync).
|
||||
/// TestWebAppFactory is shared across the whole "ApiIntegration" collection
|
||||
/// (see ApiIntegrationCollection) — one factory, one RSA singleton, one DB state.
|
||||
/// </summary>
|
||||
[Collection("ApiIntegration")]
|
||||
public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory>, IAsyncLifetime
|
||||
public sealed class CreateUsuarioEndpointTests : IAsyncLifetime
|
||||
{
|
||||
private const string TestConnectionString =
|
||||
"Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;";
|
||||
@@ -387,4 +387,46 @@ public sealed class CreateUsuarioEndpointTests : IClassFixture<TestWebAppFactory
|
||||
await DeleteUsuarioAsync(newUsername);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scenario 7 (UDT-004 Phase 5.3): 400 — rol existe pero está inactivo
|
||||
// ---------------------------------------------------------------------------
|
||||
[Fact]
|
||||
public async Task CreateUsuario_WithInactiveRol_Returns400()
|
||||
{
|
||||
var adminToken = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
const string codigo = "udt004_inactive_rol";
|
||||
const string testUser = "udt004_inactive_rol_user";
|
||||
|
||||
await using var conn = new SqlConnection(TestConnectionString);
|
||||
await conn.OpenAsync();
|
||||
await conn.ExecuteAsync(
|
||||
"INSERT INTO dbo.Rol (Codigo, Nombre, Activo) VALUES (@Codigo, N'Inactivo Test', 0);",
|
||||
new { Codigo = codigo });
|
||||
|
||||
try
|
||||
{
|
||||
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
||||
{
|
||||
username = testUser,
|
||||
password = "Secure1234!",
|
||||
nombre = "Test",
|
||||
apellido = "Inactive",
|
||||
email = (string?)null,
|
||||
rol = codigo
|
||||
}, adminToken);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
|
||||
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.True(json.TryGetProperty("errors", out var errors), "Response must contain 'errors'");
|
||||
// Validation error should be on the Rol field
|
||||
Assert.True(errors.EnumerateObject().Any(p => p.Name.Equals("Rol", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteUsuarioAsync(testUser);
|
||||
await conn.ExecuteAsync("DELETE FROM dbo.Rol WHERE Codigo = @Codigo", new { Codigo = codigo });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user