Files

80 lines
2.8 KiB
C#
Raw Permalink Normal View History

using FluentAssertions;
using NSubstitute;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Products.Pricing;
using SIGCM2.Domain.Entities;
namespace SIGCM2.Application.Tests.Products.Pricing;
/// <summary>
/// PRD-003 — ProductPricingService tests.
/// Covers: GetPriceAtAsync happy path, fecha sin precio → null (OQ-B contrato).
/// </summary>
public class ProductPricingServiceTests
{
private readonly IProductPriceRepository _repo = Substitute.For<IProductPriceRepository>();
private readonly ProductPricingService _service;
private static readonly DateOnly QueryDate = new(2026, 4, 19);
private static ProductPrice ActivePrice(decimal price = 150m) =>
new(Id: 1, ProductId: 1, Price: price,
PriceValidFrom: new DateOnly(2026, 1, 1), PriceValidTo: null,
FechaCreacion: new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
public ProductPricingServiceTests()
{
_service = new ProductPricingService(_repo);
}
// ── Happy path ─────────────────────────────────────────────────────────────
[Fact]
public async Task GetPriceAtAsync_ActivePriceCoverDate_ReturnsPrice()
{
_repo.GetActiveAsync(1, QueryDate, Arg.Any<CancellationToken>())
.Returns(ActivePrice(200m));
var result = await _service.GetPriceAtAsync(1, QueryDate);
result.Should().Be(200m);
}
[Fact]
public async Task GetPriceAtAsync_NoPriceForDate_ReturnsNull()
{
// OQ-B — si no hay historial para la fecha, retorna null.
// El consumidor (PRC-001) decide si usar BasePrice o lanzar excepción.
_repo.GetActiveAsync(1, QueryDate, Arg.Any<CancellationToken>())
.Returns((ProductPrice?)null);
var result = await _service.GetPriceAtAsync(1, QueryDate);
result.Should().BeNull();
}
[Fact]
public async Task GetPriceAtAsync_CallsGetActiveAsync_WithCorrectArgs()
{
_repo.GetActiveAsync(Arg.Any<int>(), Arg.Any<DateOnly>(), Arg.Any<CancellationToken>())
.Returns((ProductPrice?)null);
await _service.GetPriceAtAsync(productId: 5, date: QueryDate);
await _repo.Received(1).GetActiveAsync(5, QueryDate, Arg.Any<CancellationToken>());
}
[Fact]
public async Task GetPriceAtAsync_FutureDateOutsideWindow_ReturnsNull()
{
// Precio cerrado cuya ventana no cubre la fecha consultada → repo devuelve null
var futureDate = new DateOnly(2030, 1, 1);
_repo.GetActiveAsync(1, futureDate, Arg.Any<CancellationToken>())
.Returns((ProductPrice?)null);
var result = await _service.GetPriceAtAsync(1, futureDate);
result.Should().BeNull();
}
}