using YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
///
/// SwapEodPositionService.CalculateWeightedMarginInterest 的回归测试。
/// -----------------------------------------------------------------
/// 守卫提交 a0f8dc9d "refactor(SwapModule): 简化预付金利息计算逻辑"。
///
/// 旧实现:按 InterestPrincipalFix 绝对值加权平均利率 × 总本金,对方向不敏感,
/// 当收取/支付双腿并存时会把支付方向的利息错误计为收益。
/// 新实现:InterestIncomeSum 已是各腿利息金额,按方向(收取=+1,支付=-1)轧差求和。
///
/// 抽为 public static 纯函数以支持无库单测。本测试锁定方向轧差契约。
///
[TestClass]
public class SwapWeightedMarginInterestTest
{
// ================================================================
// 场景1:空集合 → 0(Sum 空序列默认值)
// ================================================================
[TestMethod]
public void 空集合_返回0()
{
var result = SwapEodPositionService.CalculateWeightedMarginInterest(
Enumerable.Empty());
Assert.AreEqual(0m, result, 0.0001m, "空集合轧差应为 0");
}
// ================================================================
// 场景2:单腿收取 → InterestIncomeSum 原值
// ================================================================
[TestMethod]
public void 单腿收取_利息原值计入()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 1000m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(1000m, result, 0.0001m, "单腿收取:+1000");
}
// ================================================================
// 场景3:单腿支付 → InterestIncomeSum 取负
// ================================================================
[TestMethod]
public void 单腿支付_利息取负计入()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 1000m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(-1000m, result, 0.0001m, "单腿支付:-1000");
}
// ================================================================
// 场景4:双腿轧差(收取 1000 + 支付 600 → 400)
// 旧 bug:按本金加权会忽略方向,结果不是 400
// ================================================================
[TestMethod]
public void 双腿轧差_收取大于支付_净额为正()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 1000m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 600m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(400m, result, 0.0001m, "双腿轧差:1000 - 600 = 400");
}
// ================================================================
// 场景5:本金为零但 InterestIncomeSum 非零 —— 回归旧 bug 关键场景
// 旧实现:totalWeight=0 → 返回 0,丢失利息
// 新实现:不看本金,按方向轧差 InterestIncomeSum
// ================================================================
[TestMethod]
public void 本金为零_利息仍按方向轧差_不丢失()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取,
interestIncomeSum: 500m, interestPrincipalFix: 0m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付,
interestIncomeSum: 200m, interestPrincipalFix: 0m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(300m, result, 0.0001m,
"本金为零时旧实现返回 0 丢失利息,新实现应按方向轧差 = 500 - 200 = 300");
}
// ================================================================
// 场景6:多腿混合方向 —— 收取 100+200,支付 50+80 → 170
// ================================================================
[TestMethod]
public void 多腿混合方向_正确轧差()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 100m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 50m),
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 200m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 80m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(170m, result, 0.0001m, "多腿轧差:(100+200) - (50+80) = 170");
}
// ================================================================
// 场景7:null 入参防御 —— Sum 对 null 抛 ArgumentNullException
// 仅作行为锚点:若未来改为 null 安全,此处需同步更新
// ================================================================
[TestMethod]
public void Null入参_抛ArgumentNullException()
{
Assert.ThrowsException(() =>
SwapEodPositionService.CalculateWeightedMarginInterest(null!));
}
// ================================================================
// Helper:构造 eod_swap_position(只设置参与计算的 2 个字段 + 可选本金)
// ================================================================
private static eod_swap_position NewMargin(
int interestDirection,
decimal interestIncomeSum,
decimal interestPrincipalFix = 0m)
{
return new eod_swap_position
{
InterestDirection = interestDirection,
InterestIncomeSum = interestIncomeSum,
InterestPrincipalFix = interestPrincipalFix
};
}
}
}