using YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
///
/// SwapEodPositionService.NormalizeInterestSignForReport 的回归测试。
/// -----------------------------------------------------------------
/// 守卫提交 f4ffe710 中报表分支的符号归一化逻辑(原内联于 SearchEodSwapList)。
///
/// 业务口径:
/// - 仅当 InterestDirection > 0 时执行(兼容历史 0 方向脏数据)
/// - 普通利息腿:收取为正、支付为负(interestRatio = Direction==收取 ? 1 : -1)
/// - 预付金腿(初始预付金/追加预付金):利息方向与保证金本金方向相反,interestRatio 取反
/// - TdCloseInterest / RealizedInterest 统一按 Math.Abs × interestRatio 重写
/// - RealizedPnl 重算为 RealizedInterest + RealizedInterestFee(兼容历史未同步落库)
///
/// 抽为 public static 纯函数以支持无库单测。
///
[TestClass]
public class SwapReportInterestSignNormalizeTest
{
// ================================================================
// 场景1:普通利息腿收取方向 → 利息维持正号
// TdCloseInterest=-1000(历史脏数据符号错) → 归一化为 +1000
// RealizedInterest=-2000 → +2000
// RealizedPnl = 2000 + 100 = 2100
// ================================================================
[TestMethod]
public void 普通利息腿_收取方向_利息归一化为正()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.收取,
tdCloseInterest: -1000m,
realizedInterest: -2000m,
realizedInterestFee: 100m);
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(1000m, pos.TdCloseInterest, 0.0001m, "TdCloseInterest 应归一化为 +1000");
Assert.AreEqual(2000m, pos.RealizedInterest, 0.0001m, "RealizedInterest 应归一化为 +2000");
Assert.AreEqual(2100m, pos.RealizedPnl, 0.0001m, "RealizedPnl = 2000 + 100 = 2100");
}
// ================================================================
// 场景2:普通利息腿支付方向 → 利息归一化为负
// TdCloseInterest=1000(历史脏数据符号错) → 归一化为 -1000
// RealizedInterest=2000 → -2000
// RealizedPnl = -2000 + 100 = -1900
// ================================================================
[TestMethod]
public void 普通利息腿_支付方向_利息归一化为负()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.支付,
tdCloseInterest: 1000m,
realizedInterest: 2000m,
realizedInterestFee: 100m);
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(-1000m, pos.TdCloseInterest, 0.0001m, "TdCloseInterest 应归一化为 -1000");
Assert.AreEqual(-2000m, pos.RealizedInterest, 0.0001m, "RealizedInterest 应归一化为 -2000");
Assert.AreEqual(-1900m, pos.RealizedPnl, 0.0001m, "RealizedPnl = -2000 + 100 = -1900");
}
// ================================================================
// 场景3:预付金腿收取方向 → 利息方向反向为负
// 原因:预付金腿的利息方向与保证金本金方向相反
// TdCloseInterest=1000 → -1000
// RealizedInterest=2000 → -2000
// ================================================================
[TestMethod]
public void 预付金腿_收取方向_利息反向为负()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.初始预付金,
interestDirection: (int)SwapDirectionEnum.收取,
tdCloseInterest: 1000m,
realizedInterest: 2000m,
realizedInterestFee: 0m);
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(-1000m, pos.TdCloseInterest, 0.0001m,
"预付金腿收取方向:利息反向为负");
Assert.AreEqual(-2000m, pos.RealizedInterest, 0.0001m,
"预付金腿收取方向:累计利息反向为负");
Assert.AreEqual(-2000m, pos.RealizedPnl, 0.0001m,
"RealizedPnl = -2000 + 0 = -2000");
}
// ================================================================
// 场景4:预付金腿支付方向 → 利息方向反向为正
// ================================================================
[TestMethod]
public void 预付金腿_支付方向_利息反向为正()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.追加预付金,
interestDirection: (int)SwapDirectionEnum.支付,
tdCloseInterest: -1000m,
realizedInterest: -2000m,
realizedInterestFee: 50m);
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(1000m, pos.TdCloseInterest, 0.0001m,
"预付金腿支付方向:利息反向为正");
Assert.AreEqual(2000m, pos.RealizedInterest, 0.0001m,
"预付金腿支付方向:累计利息反向为正");
Assert.AreEqual(2050m, pos.RealizedPnl, 0.0001m,
"RealizedPnl = 2000 + 50 = 2050");
}
// ================================================================
// 场景5:InterestDirection=0 → 不处理(兼容历史 0 方向脏数据)
// 所有字段保持原值不变
// ================================================================
[TestMethod]
public void 方向为零_不处理_字段保持原值()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: 0,
tdCloseInterest: -999m,
realizedInterest: -888m,
realizedInterestFee: 77m);
pos.RealizedPnl = 555m; // 预置旧值
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(-999m, pos.TdCloseInterest, 0.0001m, "方向=0:TdCloseInterest 不变");
Assert.AreEqual(-888m, pos.RealizedInterest, 0.0001m, "方向=0:RealizedInterest 不变");
Assert.AreEqual(555m, pos.RealizedPnl, 0.0001m, "方向=0:RealizedPnl 不重算");
}
// ================================================================
// 场景6:InterestDirection 为负 → 不处理(防御性,对应原 "> 0" 判断)
// ================================================================
[TestMethod]
public void 方向为负_不处理_字段保持原值()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: -1,
tdCloseInterest: -999m,
realizedInterest: -888m,
realizedInterestFee: 77m);
pos.RealizedPnl = 555m;
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(-999m, pos.TdCloseInterest, 0.0001m, "方向<0:TdCloseInterest 不变");
Assert.AreEqual(555m, pos.RealizedPnl, 0.0001m, "方向<0:RealizedPnl 不重算");
}
// ================================================================
// 场景7:利息为零 → Math.Abs(0)=0,归一化后仍为 0,RealizedPnl=费用
// ================================================================
[TestMethod]
public void 利息为零_归一化后仍为零_盈亏等于费用()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.收取,
tdCloseInterest: 0m,
realizedInterest: 0m,
realizedInterestFee: 300m);
SwapEodPositionService.NormalizeInterestSignForReport(pos);
Assert.AreEqual(0m, pos.TdCloseInterest, 0.0001m);
Assert.AreEqual(0m, pos.RealizedInterest, 0.0001m);
Assert.AreEqual(300m, pos.RealizedPnl, 0.0001m, "RealizedPnl = 0 + 300 = 300");
}
// ================================================================
// 场景8:null 入参 —— 抛 NullReferenceException(现状锚点)
// 生产代码未加 null 检查,InterestDirection 解引用即 NRE。
// 若未来改为 ArgumentNullException,此处需同步更新。
// ================================================================
[TestMethod]
public void Null入参_抛NullReferenceException()
{
Assert.ThrowsException(() =>
SwapEodPositionService.NormalizeInterestSignForReport(null!));
}
// ================================================================
// Helper:构造 eod_swap_position
// ================================================================
private static eod_swap_position NewPosition(
int interestMode,
int interestDirection,
decimal tdCloseInterest,
decimal realizedInterest,
decimal realizedInterestFee)
{
return new eod_swap_position
{
InterestMode = interestMode,
InterestDirection = interestDirection,
TdCloseInterest = tdCloseInterest,
RealizedInterest = realizedInterest,
RealizedInterestFee = realizedInterestFee
};
}
}
}