feat(application): IAvisoQueryRepository + NullAvisoQueryRepository (CAT-002)

This commit is contained in:
2026-04-19 08:18:56 -03:00
parent ddd28ea4d5
commit 673194e249
4 changed files with 87 additions and 1 deletions

View File

@@ -0,0 +1,24 @@
namespace SIGCM2.Application.Abstractions.Persistence;
/// <summary>
/// Query-only access to Aviso counts by Rubro.
/// CAT-002 introduces the contract. The real Dapper-based impl lands in PRD-002
/// (when dbo.Aviso exists). Until then, NullAvisoQueryRepository is the binding.
/// </summary>
public interface IAvisoQueryRepository
{
/// <summary>
/// Returns the count of avisos (active, non-archived) assigned to the given rubro.
/// </summary>
Task<int> CountAvisosEnRubroAsync(int rubroId, CancellationToken ct = default);
/// <summary>
/// Returns a dictionary of { rubroId → count } for the provided ids.
/// Used by GetRubroTreeQueryHandler to avoid N+1 when populating TieneAvisos per node.
/// The implementation MUST do a single query; the stub returns an empty dictionary
/// (every rubro gets 0 via dictionary.GetValueOrDefault).
/// </summary>
Task<IReadOnlyDictionary<int, int>> CountAvisosBatchAsync(
IReadOnlyCollection<int> rubroIds,
CancellationToken ct = default);
}

View File

@@ -0,0 +1,22 @@
using SIGCM2.Application.Abstractions.Persistence;
namespace SIGCM2.Application.Avisos;
/// <summary>
/// STUB — PRD-002 reemplaza con AvisoQueryRepository contra dbo.Aviso.
/// Returns 0 / empty dictionary so every handler guard passes and every tree node shows TieneAvisos=false.
/// This is intentional for CAT-002: the mechanism is installed; the data feed arrives in PRD-002.
/// </summary>
public sealed class NullAvisoQueryRepository : IAvisoQueryRepository
{
private static readonly IReadOnlyDictionary<int, int> Empty =
new Dictionary<int, int>(capacity: 0);
public Task<int> CountAvisosEnRubroAsync(int rubroId, CancellationToken ct = default)
=> Task.FromResult(0);
public Task<IReadOnlyDictionary<int, int>> CountAvisosBatchAsync(
IReadOnlyCollection<int> rubroIds,
CancellationToken ct = default)
=> Task.FromResult(Empty);
}

View File

@@ -67,6 +67,8 @@ using SIGCM2.Application.Rubros.Move;
using SIGCM2.Application.Rubros.GetTree; using SIGCM2.Application.Rubros.GetTree;
using SIGCM2.Application.Rubros.GetById; using SIGCM2.Application.Rubros.GetById;
using SIGCM2.Application.Rubros.Dtos; using SIGCM2.Application.Rubros.Dtos;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Avisos;
namespace SIGCM2.Application; namespace SIGCM2.Application;
@@ -152,7 +154,10 @@ public static class DependencyInjection
services.AddScoped<ICommandHandler<ListIngresosBrutosQuery, PagedResult<IngresosBrutosDto>>, ListIngresosBrutosQueryHandler>(); services.AddScoped<ICommandHandler<ListIngresosBrutosQuery, PagedResult<IngresosBrutosDto>>, ListIngresosBrutosQueryHandler>();
services.AddScoped<ICommandHandler<GetHistorialIngresosBrutosQuery, IReadOnlyList<HistorialCadenaIibbDto>>, GetHistorialIngresosBrutosQueryHandler>(); services.AddScoped<ICommandHandler<GetHistorialIngresosBrutosQuery, IReadOnlyList<HistorialCadenaIibbDto>>, GetHistorialIngresosBrutosQueryHandler>();
// Rubros (CAT-001) // Rubros (CAT-001 + CAT-002)
// CAT-002: Regla de Oro Rama vs Hoja — stub binding until PRD-002 provides real impl
services.AddScoped<IAvisoQueryRepository, NullAvisoQueryRepository>();
services.AddScoped<ICommandHandler<CreateRubroCommand, RubroCreatedDto>, CreateRubroCommandHandler>(); services.AddScoped<ICommandHandler<CreateRubroCommand, RubroCreatedDto>, CreateRubroCommandHandler>();
services.AddScoped<ICommandHandler<UpdateRubroCommand, RubroUpdatedDto>, UpdateRubroCommandHandler>(); services.AddScoped<ICommandHandler<UpdateRubroCommand, RubroUpdatedDto>, UpdateRubroCommandHandler>();
services.AddScoped<ICommandHandler<DeactivateRubroCommand, RubroStatusDto>, DeactivateRubroCommandHandler>(); services.AddScoped<ICommandHandler<DeactivateRubroCommand, RubroStatusDto>, DeactivateRubroCommandHandler>();

View File

@@ -0,0 +1,35 @@
using FluentAssertions;
using SIGCM2.Application.Avisos;
namespace SIGCM2.Application.Tests.Avisos;
public class NullAvisoQueryRepositoryTests
{
private readonly NullAvisoQueryRepository _repo = new();
[Fact]
public async Task CountAvisosEnRubroAsync_Always_ReturnsZero()
{
var result = await _repo.CountAvisosEnRubroAsync(rubroId: 99);
result.Should().Be(0);
}
[Fact]
public async Task CountAvisosBatchAsync_WithIds_ReturnsDictionaryAllZero()
{
var result = await _repo.CountAvisosBatchAsync([1, 2, 3]);
result.GetValueOrDefault(1, 0).Should().Be(0);
result.GetValueOrDefault(2, 0).Should().Be(0);
result.GetValueOrDefault(3, 0).Should().Be(0);
}
[Fact]
public async Task CountAvisosBatchAsync_EmptyIds_ReturnsEmptyDictionary()
{
var result = await _repo.CountAvisosBatchAsync([]);
result.Should().HaveCount(0);
}
}