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
This commit is contained in:
@@ -85,7 +85,8 @@ public static class DependencyInjection
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtOpts.Audience,
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
ClockSkew = TimeSpan.Zero,
|
||||
RoleClaimType = "rol"
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -56,6 +56,44 @@ public sealed class UsuarioRepository : IUsuarioRepository
|
||||
return MapRow(row);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByUsernameAsync(string username, CancellationToken ct = default)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT COUNT(1) FROM dbo.Usuario WHERE Username = @Username
|
||||
""";
|
||||
|
||||
await using var connection = _connectionFactory.CreateConnection();
|
||||
await connection.OpenAsync(ct);
|
||||
|
||||
var count = await connection.ExecuteScalarAsync<int>(sql, new { Username = username });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
public async Task<int> AddAsync(Usuario usuario, CancellationToken ct = default)
|
||||
{
|
||||
// DF handles: Activo (1), PermisosJson ('[]'), FechaCreacion (GETDATE())
|
||||
const string sql = """
|
||||
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Email, Rol)
|
||||
OUTPUT INSERTED.Id
|
||||
VALUES (@Username, @PasswordHash, @Nombre, @Apellido, @Email, @Rol)
|
||||
""";
|
||||
|
||||
await using var connection = _connectionFactory.CreateConnection();
|
||||
await connection.OpenAsync(ct);
|
||||
|
||||
var id = await connection.ExecuteScalarAsync<int>(sql, new
|
||||
{
|
||||
usuario.Username,
|
||||
usuario.PasswordHash,
|
||||
usuario.Nombre,
|
||||
usuario.Apellido,
|
||||
usuario.Email,
|
||||
usuario.Rol
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private static Usuario MapRow(UsuarioRow row)
|
||||
=> new(
|
||||
id: row.Id,
|
||||
|
||||
Reference in New Issue
Block a user