feat(domain): V008 migration + Usuario with-methods + DomainException hierarchy [UDT-008]
This commit is contained in:
@@ -2,6 +2,8 @@ using SIGCM2.Domain.Entities;
|
||||
|
||||
namespace SIGCM2.Application.Tests.Domain;
|
||||
|
||||
// ── UDT-008 tests ────────────────────────────────────────────────────────────
|
||||
|
||||
public class UsuarioTests
|
||||
{
|
||||
// Happy path: constructor sets all properties correctly
|
||||
@@ -69,4 +71,172 @@ public class UsuarioTests
|
||||
var usuario = new Usuario(2, "inactive", "$2a$12$hash", "Old", "User", null, "consulta", "[]", false);
|
||||
Assert.False(usuario.Activo);
|
||||
}
|
||||
|
||||
// ── UDT-008: new properties ───────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ForCreation_Defaults_MustChangePassword_False()
|
||||
{
|
||||
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||
Assert.False(u.MustChangePassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForCreation_Defaults_FechaModificacion_Null()
|
||||
{
|
||||
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||
Assert.Null(u.FechaModificacion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ForCreation_Defaults_UltimoLogin_Null()
|
||||
{
|
||||
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||
Assert.Null(u.UltimoLogin);
|
||||
}
|
||||
|
||||
// ── UDT-008: WithUpdatedProfile ──────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void WithUpdatedProfile_Returns_NewInstance()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUpdatedProfile("NuevoNombre", "NuevoApellido", "new@x.com", "cajero", true);
|
||||
Assert.NotSame(u, updated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithUpdatedProfile_Sets_Fields_Correctly()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUpdatedProfile("Pedro", "Gómez", "p@g.com", "cajero", false);
|
||||
Assert.Equal("Pedro", updated.Nombre);
|
||||
Assert.Equal("Gómez", updated.Apellido);
|
||||
Assert.Equal("p@g.com", updated.Email);
|
||||
Assert.Equal("cajero", updated.Rol);
|
||||
Assert.False(updated.Activo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithUpdatedProfile_Sets_FechaModificacion_To_UtcNow()
|
||||
{
|
||||
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUpdatedProfile("A", "B", null, "admin", true);
|
||||
Assert.NotNull(updated.FechaModificacion);
|
||||
Assert.True(updated.FechaModificacion >= before);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithUpdatedProfile_Preserves_Immutable_Fields()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUpdatedProfile("X", "Y", null, "cajero", true);
|
||||
Assert.Equal(u.Id, updated.Id);
|
||||
Assert.Equal(u.Username, updated.Username);
|
||||
Assert.Equal(u.PasswordHash, updated.PasswordHash);
|
||||
}
|
||||
|
||||
// ── UDT-008: WithNewPasswordHash ─────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void WithNewPasswordHash_Returns_NewInstance()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithNewPasswordHash("newhash", mustChangePassword: false);
|
||||
Assert.NotSame(u, updated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithNewPasswordHash_Sets_Hash_And_MustChange()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithNewPasswordHash("newhash", mustChangePassword: true);
|
||||
Assert.Equal("newhash", updated.PasswordHash);
|
||||
Assert.True(updated.MustChangePassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithNewPasswordHash_Clears_MustChange_When_False()
|
||||
{
|
||||
var u = MakeUsuario(mustChangePassword: true);
|
||||
var updated = u.WithNewPasswordHash("hash2", mustChangePassword: false);
|
||||
Assert.False(updated.MustChangePassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithNewPasswordHash_Sets_FechaModificacion()
|
||||
{
|
||||
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithNewPasswordHash("hash2", false);
|
||||
Assert.NotNull(updated.FechaModificacion);
|
||||
Assert.True(updated.FechaModificacion >= before);
|
||||
}
|
||||
|
||||
// ── UDT-008: WithUltimoLogin ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void WithUltimoLogin_Returns_NewInstance()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUltimoLogin(DateTime.UtcNow);
|
||||
Assert.NotSame(u, updated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithUltimoLogin_Sets_UltimoLogin()
|
||||
{
|
||||
var ts = new DateTime(2026, 1, 15, 10, 0, 0, DateTimeKind.Utc);
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithUltimoLogin(ts);
|
||||
Assert.Equal(ts, updated.UltimoLogin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithUltimoLogin_Does_NOT_Touch_FechaModificacion()
|
||||
{
|
||||
var u = MakeUsuario();
|
||||
var originalFecha = u.FechaModificacion;
|
||||
var updated = u.WithUltimoLogin(DateTime.UtcNow);
|
||||
Assert.Equal(originalFecha, updated.FechaModificacion);
|
||||
}
|
||||
|
||||
// ── UDT-008: WithMustChangePassword ──────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void WithMustChangePassword_Sets_Value_True()
|
||||
{
|
||||
var u = MakeUsuario(mustChangePassword: false);
|
||||
var updated = u.WithMustChangePassword(true);
|
||||
Assert.True(updated.MustChangePassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithMustChangePassword_Sets_FechaModificacion()
|
||||
{
|
||||
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||
var u = MakeUsuario();
|
||||
var updated = u.WithMustChangePassword(true);
|
||||
Assert.NotNull(updated.FechaModificacion);
|
||||
Assert.True(updated.FechaModificacion >= before);
|
||||
}
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static Usuario MakeUsuario(bool mustChangePassword = false)
|
||||
=> new(
|
||||
id: 1,
|
||||
username: "testuser",
|
||||
passwordHash: "$2a$12$hash",
|
||||
nombre: "Test",
|
||||
apellido: "User",
|
||||
email: "test@x.com",
|
||||
rol: "admin",
|
||||
permisosJson: "[]",
|
||||
activo: true,
|
||||
fechaModificacion: null,
|
||||
ultimoLogin: null,
|
||||
mustChangePassword: mustChangePassword
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
_connection = new SqlConnection(_connectionString);
|
||||
await _connection.OpenAsync();
|
||||
|
||||
// V008: ensure MustChangePassword column and IX_Usuario_Activo_Rol exist in test DB
|
||||
await EnsureV008SchemaAsync();
|
||||
|
||||
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
||||
{
|
||||
DbAdapter = DbAdapter.SqlServer,
|
||||
@@ -83,6 +86,38 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies V008 schema changes idempotently to the test database.
|
||||
/// Mirrors V008__add_mustchangepassword_and_indexes.sql.
|
||||
/// </summary>
|
||||
private async Task EnsureV008SchemaAsync()
|
||||
{
|
||||
const string addColumn = """
|
||||
IF COL_LENGTH('dbo.Usuario', 'MustChangePassword') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Usuario
|
||||
ADD MustChangePassword BIT NOT NULL
|
||||
CONSTRAINT DF_Usuario_MustChangePassword DEFAULT(0);
|
||||
END
|
||||
""";
|
||||
|
||||
const string addIndex = """
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'IX_Usuario_Activo_Rol'
|
||||
AND object_id = OBJECT_ID('dbo.Usuario')
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX IX_Usuario_Activo_Rol
|
||||
ON dbo.Usuario(Activo, Rol)
|
||||
INCLUDE (Id, Username, Email, UltimoLogin, FechaModificacion);
|
||||
END
|
||||
""";
|
||||
|
||||
await _connection.ExecuteAsync(addColumn);
|
||||
await _connection.ExecuteAsync(addIndex);
|
||||
}
|
||||
|
||||
private async Task SeedPermisosCanonicalAsync()
|
||||
{
|
||||
const string sql = """
|
||||
@@ -183,11 +218,11 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
const string sql = """
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin')
|
||||
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo)
|
||||
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
|
||||
VALUES (
|
||||
'admin',
|
||||
'$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW',
|
||||
'Administrador', 'Sistema', 'admin', '["*"]', 1
|
||||
'Administrador', 'Sistema', 'admin', '["*"]', 1, 0
|
||||
);
|
||||
""";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
|
||||
Reference in New Issue
Block a user