114 lines
4.6 KiB
C#
114 lines
4.6 KiB
C#
using FluentAssertions;
|
|
using NSubstitute;
|
|
using SIGCM2.Application.Abstractions.Persistence;
|
|
using SIGCM2.Application.Pricing.ChargeableChars;
|
|
using SIGCM2.Domain.Pricing.ChargeableChars;
|
|
|
|
namespace SIGCM2.Application.Tests.Pricing.ChargeableChars;
|
|
|
|
/// <summary>
|
|
/// PRC-001 — ChargeableCharConfigService tests.
|
|
/// Covers: per-medio wins over global, global fallback when no per-medio,
|
|
/// empty result when no config at all.
|
|
/// </summary>
|
|
public class ChargeableCharConfigServiceTests
|
|
{
|
|
private readonly IChargeableCharConfigRepository _repo = Substitute.For<IChargeableCharConfigRepository>();
|
|
private readonly ChargeableCharConfigService _service;
|
|
|
|
private static readonly DateOnly AsOf = new(2026, 4, 20);
|
|
|
|
public ChargeableCharConfigServiceTests()
|
|
{
|
|
_service = new ChargeableCharConfigService(_repo);
|
|
}
|
|
|
|
private static ChargeableCharConfig GlobalConfig(string symbol, decimal price) =>
|
|
ChargeableCharConfig.Rehydrate(10L, null, symbol, ChargeableCharCategories.Currency, price, AsOf, null, true);
|
|
|
|
private static ChargeableCharConfig MedioConfig(long id, int medioId, string symbol, decimal price) =>
|
|
ChargeableCharConfig.Rehydrate(id, medioId, symbol, ChargeableCharCategories.Currency, price, AsOf, null, true);
|
|
|
|
// ── Global fallback ─────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetActiveConfig_NoPerMedio_ReturnsGlobalConfigs()
|
|
{
|
|
_repo.GetActiveForMedioAsync(1, AsOf, Arg.Any<CancellationToken>())
|
|
.Returns(new List<ChargeableCharConfig>
|
|
{
|
|
GlobalConfig("$", 1.0m),
|
|
GlobalConfig("%", 0.5m),
|
|
});
|
|
|
|
var result = await _service.GetActiveConfigForMedioAsync(1, AsOf, CancellationToken.None);
|
|
|
|
result.Should().ContainKey("$");
|
|
result["$"].PricePerUnit.Should().Be(1.0m);
|
|
result.Should().ContainKey("%");
|
|
}
|
|
|
|
// ── Per-medio wins over global ───────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetActiveConfig_PerMedioExists_OverridesGlobalForSameSymbol()
|
|
{
|
|
_repo.GetActiveForMedioAsync(5, AsOf, Arg.Any<CancellationToken>())
|
|
.Returns(new List<ChargeableCharConfig>
|
|
{
|
|
GlobalConfig("$", 1.0m), // global price = 1.0
|
|
MedioConfig(20L, 5, "$", 3.0m), // per-medio price = 3.0 → wins
|
|
GlobalConfig("%", 0.5m), // global only
|
|
});
|
|
|
|
var result = await _service.GetActiveConfigForMedioAsync(5, AsOf, CancellationToken.None);
|
|
|
|
result["$"].PricePerUnit.Should().Be(3.0m); // per-medio wins
|
|
result["%"].PricePerUnit.Should().Be(0.5m); // global only
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveConfig_PerMedioExists_IncludesCorrectCategory()
|
|
{
|
|
_repo.GetActiveForMedioAsync(5, AsOf, Arg.Any<CancellationToken>())
|
|
.Returns(new List<ChargeableCharConfig>
|
|
{
|
|
MedioConfig(20L, 5, "$", 3.0m),
|
|
});
|
|
|
|
var result = await _service.GetActiveConfigForMedioAsync(5, AsOf, CancellationToken.None);
|
|
|
|
result["$"].Category.Should().Be(ChargeableCharCategories.Currency);
|
|
}
|
|
|
|
// ── Empty result ─────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetActiveConfig_NoConfigAtAll_ReturnsEmptyDictionary()
|
|
{
|
|
_repo.GetActiveForMedioAsync(1, AsOf, Arg.Any<CancellationToken>())
|
|
.Returns(new List<ChargeableCharConfig>());
|
|
|
|
var result = await _service.GetActiveConfigForMedioAsync(1, AsOf, CancellationToken.None);
|
|
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
// ── Key: Symbol, Value: snapshot ─────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetActiveConfig_KeyIsSymbol()
|
|
{
|
|
_repo.GetActiveForMedioAsync(1, AsOf, Arg.Any<CancellationToken>())
|
|
.Returns(new List<ChargeableCharConfig>
|
|
{
|
|
GlobalConfig("!", 2.0m),
|
|
});
|
|
|
|
var result = await _service.GetActiveConfigForMedioAsync(1, AsOf, CancellationToken.None);
|
|
|
|
result.Should().ContainKey("!");
|
|
result["!"].PricePerUnit.Should().Be(2.0m);
|
|
}
|
|
}
|