test(secciones): cobertura cascada de inactividad — issue #16
This commit is contained in:
@@ -389,6 +389,156 @@ public sealed class SeccionesControllerTests : IAsyncLifetime
|
||||
Assert.Equal("seccion_not_found", json.GetProperty("error").GetString());
|
||||
}
|
||||
|
||||
// ── CASCADA INACTIVIDAD (issue #16) ──────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSeccion_WhenMedioInactive_Returns409()
|
||||
{
|
||||
const string medioCodigo = "TSEC_UPD_INACT";
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var medioId = await CreateMedioAsync(medioCodigo, "Medio Inactivo Update", token);
|
||||
|
||||
using var createReq = BuildRequest(HttpMethod.Post, Endpoint, new
|
||||
{
|
||||
medioId,
|
||||
codigo = "SECUPDINACT",
|
||||
nombre = "Seccion Update Inactivo",
|
||||
tipo = "clasificados"
|
||||
}, token);
|
||||
var createResp = await _client.SendAsync(createReq);
|
||||
Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
|
||||
var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var secId = created.GetProperty("id").GetInt32();
|
||||
|
||||
// Deactivate the medio
|
||||
using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
||||
var deactMedioResp = await _client.SendAsync(deactMedioReq);
|
||||
Assert.Equal(HttpStatusCode.NoContent, deactMedioResp.StatusCode);
|
||||
|
||||
var auditBefore = await CountAuditEventsAsync("seccion.update", "Seccion", secId.ToString());
|
||||
|
||||
// Try to update seccion with inactive medio
|
||||
using var updateReq = BuildRequest(HttpMethod.Put, $"{Endpoint}/{secId}", new
|
||||
{
|
||||
nombre = "Nombre Cambiado",
|
||||
tipo = "notables"
|
||||
}, token);
|
||||
var updateResp = await _client.SendAsync(updateReq);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict, updateResp.StatusCode);
|
||||
var json = await updateResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
||||
|
||||
var auditAfter = await CountAuditEventsAsync("seccion.update", "Seccion", secId.ToString());
|
||||
Assert.Equal(auditBefore, auditAfter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteMedioIfExistsAsync(medioCodigo);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeactivateSeccion_WhenMedioInactive_Returns409()
|
||||
{
|
||||
const string medioCodigo = "TSEC_DEACT_INACT";
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var medioId = await CreateMedioAsync(medioCodigo, "Medio Inactivo Deactivate", token);
|
||||
|
||||
using var createReq = BuildRequest(HttpMethod.Post, Endpoint, new
|
||||
{
|
||||
medioId,
|
||||
codigo = "SECDEACTINACT",
|
||||
nombre = "Seccion Deactivate Inactivo",
|
||||
tipo = "clasificados"
|
||||
}, token);
|
||||
var createResp = await _client.SendAsync(createReq);
|
||||
Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
|
||||
var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var secId = created.GetProperty("id").GetInt32();
|
||||
|
||||
// Deactivate the medio
|
||||
using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
||||
var deactMedioResp = await _client.SendAsync(deactMedioReq);
|
||||
Assert.Equal(HttpStatusCode.NoContent, deactMedioResp.StatusCode);
|
||||
|
||||
var auditBefore = await CountAuditEventsAsync("seccion.deactivate", "Seccion", secId.ToString());
|
||||
|
||||
// Try to deactivate seccion with inactive medio
|
||||
using var deactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{secId}/deactivate", bearerToken: token);
|
||||
var deactResp = await _client.SendAsync(deactReq);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict, deactResp.StatusCode);
|
||||
var json = await deactResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
||||
|
||||
var auditAfter = await CountAuditEventsAsync("seccion.deactivate", "Seccion", secId.ToString());
|
||||
Assert.Equal(auditBefore, auditAfter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteMedioIfExistsAsync(medioCodigo);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReactivateSeccion_WhenMedioInactive_Returns409()
|
||||
{
|
||||
const string medioCodigo = "TSEC_REACT_INACT";
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var medioId = await CreateMedioAsync(medioCodigo, "Medio Inactivo Reactivate", token);
|
||||
|
||||
// Create seccion then deactivate it (while medio is still active)
|
||||
using var createReq = BuildRequest(HttpMethod.Post, Endpoint, new
|
||||
{
|
||||
medioId,
|
||||
codigo = "SECREACTINACT",
|
||||
nombre = "Seccion Reactivate Inactivo",
|
||||
tipo = "clasificados"
|
||||
}, token);
|
||||
var createResp = await _client.SendAsync(createReq);
|
||||
Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
|
||||
var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var secId = created.GetProperty("id").GetInt32();
|
||||
|
||||
// Deactivate seccion while medio is still active
|
||||
using var deactSecReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{secId}/deactivate", bearerToken: token);
|
||||
var deactSecResp = await _client.SendAsync(deactSecReq);
|
||||
Assert.Equal(HttpStatusCode.NoContent, deactSecResp.StatusCode);
|
||||
|
||||
// Now deactivate the medio
|
||||
using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
||||
var deactMedioResp = await _client.SendAsync(deactMedioReq);
|
||||
Assert.Equal(HttpStatusCode.NoContent, deactMedioResp.StatusCode);
|
||||
|
||||
var auditBefore = await CountAuditEventsAsync("seccion.reactivate", "Seccion", secId.ToString());
|
||||
|
||||
// Try to reactivate seccion with inactive medio
|
||||
using var reactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{secId}/reactivate", bearerToken: token);
|
||||
var reactResp = await _client.SendAsync(reactReq);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict, reactResp.StatusCode);
|
||||
var json = await reactResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
||||
|
||||
var auditAfter = await CountAuditEventsAsync("seccion.reactivate", "Seccion", secId.ToString());
|
||||
Assert.Equal(auditBefore, auditAfter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteMedioIfExistsAsync(medioCodigo);
|
||||
}
|
||||
}
|
||||
|
||||
// ── DEACTIVATE ────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -10,15 +10,21 @@ namespace SIGCM2.Application.Tests.Secciones.Deactivate;
|
||||
public class DeactivateSeccionCommandHandlerTests
|
||||
{
|
||||
private readonly ISeccionRepository _repo = Substitute.For<ISeccionRepository>();
|
||||
private readonly IMedioRepository _medioRepo = Substitute.For<IMedioRepository>();
|
||||
private readonly IAuditLogger _audit = Substitute.For<IAuditLogger>();
|
||||
private readonly DeactivateSeccionCommandHandler _handler;
|
||||
|
||||
private static Seccion MakeSeccion(int id = 1, bool activo = true)
|
||||
=> new(id, 1, "COD" + id, "Nombre", "clasificados", activo, DateTime.UtcNow, null);
|
||||
|
||||
private static Medio MakeMedio(int id = 1, bool activo = true)
|
||||
=> new(id, "COD" + id, "Medio " + id, TipoMedio.Diario, null, activo, DateTime.UtcNow, null);
|
||||
|
||||
public DeactivateSeccionCommandHandlerTests()
|
||||
{
|
||||
_handler = new DeactivateSeccionCommandHandler(_repo, _audit);
|
||||
_handler = new DeactivateSeccionCommandHandler(_repo, _medioRepo, _audit);
|
||||
// Default: medio is active
|
||||
_medioRepo.GetByIdAsync(Arg.Any<int>(), Arg.Any<CancellationToken>()).Returns(MakeMedio(1, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -78,4 +84,18 @@ public class DeactivateSeccionCommandHandlerTests
|
||||
metadata: Arg.Any<object?>(),
|
||||
ct: Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_MedioInactivo_ThrowsMedioInactivoExceptionAndNoAuditLogged()
|
||||
{
|
||||
_repo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeSeccion(1, true));
|
||||
_medioRepo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeMedio(1, activo: false));
|
||||
|
||||
await Assert.ThrowsAsync<MedioInactivoException>(
|
||||
() => _handler.Handle(new DeactivateSeccionCommand(1)));
|
||||
|
||||
await _audit.DidNotReceive().LogAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
|
||||
Arg.Any<object?>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,21 @@ namespace SIGCM2.Application.Tests.Secciones.Reactivate;
|
||||
public class ReactivateSeccionCommandHandlerTests
|
||||
{
|
||||
private readonly ISeccionRepository _repo = Substitute.For<ISeccionRepository>();
|
||||
private readonly IMedioRepository _medioRepo = Substitute.For<IMedioRepository>();
|
||||
private readonly IAuditLogger _audit = Substitute.For<IAuditLogger>();
|
||||
private readonly ReactivateSeccionCommandHandler _handler;
|
||||
|
||||
private static Seccion MakeSeccion(int id = 1, bool activo = false)
|
||||
=> new(id, 1, "COD" + id, "Nombre", "clasificados", activo, DateTime.UtcNow, null);
|
||||
|
||||
private static Medio MakeMedio(int id = 1, bool activo = true)
|
||||
=> new(id, "COD" + id, "Medio " + id, TipoMedio.Diario, null, activo, DateTime.UtcNow, null);
|
||||
|
||||
public ReactivateSeccionCommandHandlerTests()
|
||||
{
|
||||
_handler = new ReactivateSeccionCommandHandler(_repo, _audit);
|
||||
_handler = new ReactivateSeccionCommandHandler(_repo, _medioRepo, _audit);
|
||||
// Default: medio is active
|
||||
_medioRepo.GetByIdAsync(Arg.Any<int>(), Arg.Any<CancellationToken>()).Returns(MakeMedio(1, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -79,4 +85,18 @@ public class ReactivateSeccionCommandHandlerTests
|
||||
metadata: Arg.Any<object?>(),
|
||||
ct: Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_MedioInactivo_ThrowsMedioInactivoExceptionAndNoAuditLogged()
|
||||
{
|
||||
_repo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeSeccion(1, false));
|
||||
_medioRepo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeMedio(1, activo: false));
|
||||
|
||||
await Assert.ThrowsAsync<MedioInactivoException>(
|
||||
() => _handler.Handle(new ReactivateSeccionCommand(1)));
|
||||
|
||||
await _audit.DidNotReceive().LogAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
|
||||
Arg.Any<object?>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ namespace SIGCM2.Application.Tests.Secciones.Update;
|
||||
public class UpdateSeccionCommandHandlerTests
|
||||
{
|
||||
private readonly ISeccionRepository _repo = Substitute.For<ISeccionRepository>();
|
||||
private readonly IMedioRepository _medioRepo = Substitute.For<IMedioRepository>();
|
||||
private readonly IAuditLogger _audit = Substitute.For<IAuditLogger>();
|
||||
private readonly UpdateSeccionCommandHandler _handler;
|
||||
|
||||
private static Seccion MakeSeccion(int id = 1, string nombre = "Original", string tipo = "clasificados")
|
||||
=> new(id, 1, "COD" + id, nombre, tipo, true, DateTime.UtcNow, null);
|
||||
|
||||
private static Medio MakeMedio(int id = 1, bool activo = true)
|
||||
=> new(id, "COD" + id, "Medio " + id, TipoMedio.Diario, null, activo, DateTime.UtcNow, null);
|
||||
|
||||
private static UpdateSeccionCommand ValidCommand(int id = 1) => new(
|
||||
Id: id,
|
||||
Nombre: "Nuevo Nombre",
|
||||
@@ -23,7 +27,9 @@ public class UpdateSeccionCommandHandlerTests
|
||||
|
||||
public UpdateSeccionCommandHandlerTests()
|
||||
{
|
||||
_handler = new UpdateSeccionCommandHandler(_repo, _audit);
|
||||
_handler = new UpdateSeccionCommandHandler(_repo, _medioRepo, _audit);
|
||||
// Default: medio is active
|
||||
_medioRepo.GetByIdAsync(Arg.Any<int>(), Arg.Any<CancellationToken>()).Returns(MakeMedio(1, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -71,4 +77,18 @@ public class UpdateSeccionCommandHandlerTests
|
||||
metadata: Arg.Any<object?>(),
|
||||
ct: Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_MedioInactivo_ThrowsMedioInactivoExceptionAndNoAuditLogged()
|
||||
{
|
||||
_repo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeSeccion(1));
|
||||
_medioRepo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeMedio(1, activo: false));
|
||||
|
||||
await Assert.ThrowsAsync<MedioInactivoException>(
|
||||
() => _handler.Handle(ValidCommand(1)));
|
||||
|
||||
await _audit.DidNotReceive().LogAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
|
||||
Arg.Any<object?>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user