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 }; } } }