feat(application): RubroTreeBuilder + GetRubroTree con tieneAvisos (CAT-002)

This commit is contained in:
2026-04-19 08:25:13 -03:00
parent c03aad8c5a
commit f861dfa826
2 changed files with 80 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Avisos;
using SIGCM2.Application.Rubros.GetById;
using SIGCM2.Application.Rubros.GetTree;
using SIGCM2.Domain.Entities;
@@ -13,6 +14,7 @@ public class GetRubroTreeQueryHandlerTests
{
private static readonly FakeTimeProvider FakeTime = new(new DateTimeOffset(2026, 4, 18, 12, 0, 0, TimeSpan.Zero));
private readonly IRubroRepository _repo = Substitute.For<IRubroRepository>();
private readonly IAvisoQueryRepository _avisoQuery = Substitute.For<IAvisoQueryRepository>();
private static Rubro MakeRubro(int id, int? parentId = null, bool activo = true)
=> new(id, parentId, $"Rubro{id}", 0, activo, null, FakeTime.GetUtcNow().UtcDateTime, null);
@@ -24,8 +26,10 @@ public class GetRubroTreeQueryHandlerTests
{
_repo.GetAllAsync(false, Arg.Any<CancellationToken>())
.Returns(new[] { MakeRubro(1), MakeRubro(2) });
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int>());
var handler = new GetRubroTreeQueryHandler(_repo);
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
var result = await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
result.Should().HaveCount(2);
@@ -36,8 +40,10 @@ public class GetRubroTreeQueryHandlerTests
{
_repo.GetAllAsync(true, Arg.Any<CancellationToken>())
.Returns(new[] { MakeRubro(1), MakeRubro(2, activo: false) });
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int>());
var handler = new GetRubroTreeQueryHandler(_repo);
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
var result = await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: true));
await _repo.Received(1).GetAllAsync(true, Arg.Any<CancellationToken>());
@@ -49,13 +55,77 @@ public class GetRubroTreeQueryHandlerTests
{
_repo.GetAllAsync(Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns(Array.Empty<Rubro>());
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int>());
var handler = new GetRubroTreeQueryHandler(_repo);
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
var result = await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
result.Should().BeEmpty();
}
// ── CAT-002: TieneAvisos populated via batch ─────────────────────────────
[Fact]
public async Task Handle_PopulatesTieneAvisos_True_WhenBatchResultContainsCount()
{
_repo.GetAllAsync(false, Arg.Any<CancellationToken>())
.Returns(new[] { MakeRubro(1), MakeRubro(2), MakeRubro(3) });
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int> { { 1, 2 } });
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
var result = await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
result.Single(n => n.Id == 1).TieneAvisos.Should().BeTrue();
result.Single(n => n.Id == 2).TieneAvisos.Should().BeFalse();
result.Single(n => n.Id == 3).TieneAvisos.Should().BeFalse();
}
[Fact]
public async Task Handle_CallsBatchExactlyOnce_WithAllRubroIds()
{
_repo.GetAllAsync(false, Arg.Any<CancellationToken>())
.Returns(new[] { MakeRubro(1), MakeRubro(2), MakeRubro(3) });
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int>());
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
await _avisoQuery.Received(1).CountAvisosBatchAsync(
Arg.Any<IReadOnlyCollection<int>>(),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task Handle_EmptyTree_CallsBatchWithEmptyList()
{
_repo.GetAllAsync(Arg.Any<bool>(), Arg.Any<CancellationToken>())
.Returns(Array.Empty<Rubro>());
_avisoQuery.CountAvisosBatchAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<int, int>());
var handler = new GetRubroTreeQueryHandler(_repo, _avisoQuery);
await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
await _avisoQuery.Received(1).CountAvisosBatchAsync(
Arg.Is<IReadOnlyCollection<int>>(ids => ids.Count == 0),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task Handle_StubBehavior_AllNodesTieneAvisosFalse()
{
_repo.GetAllAsync(false, Arg.Any<CancellationToken>())
.Returns(new[] { MakeRubro(1), MakeRubro(2) });
var handler = new GetRubroTreeQueryHandler(_repo, new NullAvisoQueryRepository());
var result = await handler.Handle(new GetRubroTreeQuery(IncluirInactivos: false));
result.Should().AllSatisfy(n => n.TieneAvisos.Should().BeFalse());
}
// ── GetRubroByIdQueryHandler ─────────────────────────────────────────────
[Fact]