test(swap): 补ApproveSwapTrade/ApplySwapTrade测试+3个seam(借鉴testable分支)

借鉴 origin/glms/feature/refactor-swap-event-testable 的 SwapUnwindScenarioTest 场景4/5,
补全当前分支缺失的平仓审批流程覆盖。

SwapDealService 新增3个 protected virtual seam:
- FindSwapEvent(查待审核事件)
- FindFlowEventsByEventId(查事件关联流水)
- CloseReCheckSetTrade(前置校验包装)
ApproveSwapTrade/ApplySwapTrade 重构为调seam,生产行为不变。

SwapDealSettlementTest 新增2个测试(SD_005/006,共6个):
- SD_005 ApproveSwapTrade 全平仓审核: 反序列化swap_event.EventData→资金流水→TradeStatus
- SD_006 ApplySwapTrade 提交审核: CloseReCheck前置校验+SwapRealizedPnL=SwapCloseAmount

融合策略: 保留当前分支SwapIncome资金流水断言独特价值(SD_001/002),
借鉴testable分支审批流程覆盖(SD_005/006),不二选一。
SwapModule 157测试全绿,无回归。
This commit is contained in:
hjhan
2026-07-03 08:30:36 +08:00
parent cf0d965ff8
commit 129e4bed0d
2 changed files with 138 additions and 22 deletions
@@ -1,3 +1,4 @@
using Newtonsoft.Json;
using YLErp.DBModels;
using YLErp.DBModels.Enums;
@@ -32,13 +33,21 @@ namespace YLErp.Modules.SwapModule
private sealed class StubDealService : SwapDealService
{
private readonly trade _trade;
private readonly Dictionary<int, swap_event> _swapEvents;
private readonly Dictionary<long, List<swap_flow_event>> _flowEventsByEventId;
public List<(double amount, string action, DateTime date)> ClientCashCalls = new();
public List<(UnwindData data, int eventType, int clientCashId)> SaveSwapDealCalls = new();
public int SaveAllChangesCount;
public int CloseReCheckCallCount;
public StubDealService(trade td) : base(new OptUserInfo(0, nameof(SwapDealSettlementTest), OptUserFrom.UnitTest))
public StubDealService(trade td,
Dictionary<int, swap_event> swapEvents = null,
Dictionary<long, List<swap_flow_event>> flowEventsByEventId = null)
: base(new OptUserInfo(0, nameof(SwapDealSettlementTest), OptUserFrom.UnitTest))
{
_trade = td;
_swapEvents = swapEvents ?? new Dictionary<int, swap_event>();
_flowEventsByEventId = flowEventsByEventId ?? new Dictionary<long, List<swap_flow_event>>();
}
protected override trade FindTrade(int tradeId) => tradeId == _trade.id ? _trade : null;
@@ -56,6 +65,24 @@ namespace YLErp.Modules.SwapModule
return SaveSwapDealCalls.Count; // 返回自增 eventId
}
// ApproveSwapTrade 查待审核事件:从内存字典取(key=eventType
protected override swap_event FindSwapEvent(int tradeId, int eventType)
{
return _swapEvents.TryGetValue(eventType, out var evt) ? evt : null;
}
// ApproveSwapTrade 查事件关联流水:从内存字典取
protected override List<swap_flow_event> FindFlowEventsByEventId(long eventId)
{
return _flowEventsByEventId.TryGetValue(eventId, out var list) ? list : new List<swap_flow_event>();
}
// ApplySwapTrade 的前置校验:计数,不实际执行
protected override void CloseReCheckSetTrade(int swapTradeId, bool isSwap, bool needCheck)
{
CloseReCheckCallCount++;
}
protected override void SaveAllChanges() { SaveAllChangesCount++; }
protected override void ExecuteInTransaction(Action action) => action(); // 不包事务,直接执行
protected override void CallSaveSwapTradeClientCash(trade td, DateTime valueDate) { } // 空操作
@@ -247,5 +274,86 @@ namespace YLErp.Modules.SwapModule
$"TradingAmount 应=ExitDirtyPrice(1.02)×CloseQty(1000)=1020,实际={closeEvent.TradingAmount}");
Console.WriteLine($"SD_004 通过:ExitDirtyFeePrice={closeEvent.TradingAmountFeeAvg}ExitCleanFeePrice={closeEvent.TradingAmountNetFeeAvg}TradingAmount={closeEvent.TradingAmount} ✅");
}
// ================================================================
// SD_005ApproveSwapTrade 审核通过 —— 反序列化事件、资金流水、持仓状态
// ================================================================
/// <summary>
/// [SD_005] ApproveSwapTrade 审核通过全部平仓
/// ------------------------------------------------------------
/// 后端 SwapDealService.ApproveSwapTrade:从 swap_event.EventData 反序列化 UnwindData
/// 据此生成资金流水 + 更新持仓状态。
/// 借鉴 testable 分支 SwapUnwindScenarioTest.Scenario4,验证:
/// - SwapRealizedPnL 从事件反序列化正确(EventData JSON
/// - 资金流水金额 = -SwapRealizedPnL
/// - 全平仓 → TradeStatus=已平仓
/// </summary>
[TestMethod]
public void SD_005_ApproveSwapTrade_全平仓审核_反序列化事件并记账()
{
var td = CreateTrade();
// 构造待审核事件:EventData 里序列化了 UnwindData(含 SwapRealizedPnL=8000
var unwindData = CreateUnwindData(swapRealizedPnL: 8000m,
closeMethod: (int)CloseMethodEnum., closePercent: 1m,
closeQty: 10000m, closeNotionalValue: 1000000m);
var swapEvent = new swap_event
{
id = 1, SwapTradeId = SwapTradeId,
EventType = (int)SwapEventTypeEnum., Invalid = false,
EventData = JsonConvert.SerializeObject(unwindData)
};
var flowEvents = new Dictionary<long, List<swap_flow_event>>
{
[1] = new List<swap_flow_event> { new swap_flow_event { id = 1, EventId = 1, PositionId = 1 } }
};
var service = new StubDealService(td,
swapEvents: new Dictionary<int, swap_event> { [(int)SwapEventTypeEnum.] = swapEvent },
flowEventsByEventId: flowEvents);
service.ApproveSwapTrade(td, (int)SwapEventTypeEnum.);
// 资金流水:从反序列化的 SwapRealizedPnL(8000) 记账 → -8000
Assert.AreEqual(1, service.ClientCashCalls.Count, "全平仓无预付金时应1条资金流水");
Assert.AreEqual(-8000.0, service.ClientCashCalls[0].amount, 0.001, "资金流水=-反序列化的SwapRealizedPnL");
// 持仓状态
Assert.AreEqual("已平仓", td.TradeStatus, "审核全平仓 TradeStatus=已平仓");
Console.WriteLine($"SD_005 通过:审核反序列化 SwapRealizedPnL=8000,资金流水={service.ClientCashCalls[0].amount}TradeStatus={td.TradeStatus} ✅");
}
// ================================================================
// SD_006ApplySwapTrade 提交审核 —— 前置校验 + 保存事件
// ================================================================
/// <summary>
/// [SD_006] ApplySwapTrade 提交审核
/// ------------------------------------------------------------
/// 后端 SwapDealService.ApplySwapTrade:调 CloseReCheckSetTrade 前置校验 + SaveSwapDeal(approve=true)。
/// 借鉴 testable 分支 SwapUnwindScenarioTest.Scenario5,验证:
/// - CloseReCheckSetTrade 被调用1次
/// - SaveSwapDeal 以 approve=true 调用(事件类型正确)
/// - SwapRealizedPnL = SwapCloseAmountApplySwapTrade 内部赋值)
/// </summary>
[TestMethod]
public void SD_006_ApplySwapTrade_提交审核_前置校验与保存事件()
{
var td = CreateTrade();
var service = new StubDealService(td);
// 前端提交时 SwapCloseAmount=6000(前端算好的总额),SwapRealizedPnL 初始可能为0
var unwindData = CreateUnwindData(swapRealizedPnL: 0m);
unwindData.SwapCloseAmount = 6000m; // 模拟前端传入的平仓总额
service.ApplySwapTrade(unwindData, (int)SwapEventTypeEnum.);
// 前置校验被调用
Assert.AreEqual(1, service.CloseReCheckCallCount, "应调用 CloseReCheckSetTrade 1次");
// SaveSwapDeal 以 approve=true 调用
Assert.AreEqual(1, service.SaveSwapDealCalls.Count, "应调用 SaveSwapDeal 1次");
Assert.AreEqual((int)SwapEventTypeEnum., service.SaveSwapDealCalls[0].eventType, "事件类型=平仓");
// SwapRealizedPnL 应被赋值为 SwapCloseAmountApplySwapTrade 内部 cs:1631
Assert.AreEqual(6000m, service.SaveSwapDealCalls[0].data.SwapRealizedPnL, 0.001m,
"SwapRealizedPnL 应=SwapCloseAmount(6000)");
Console.WriteLine($"SD_006 通过:CloseReCheck 调用{service.CloseReCheckCallCount}次,SwapRealizedPnL={service.SaveSwapDealCalls[0].data.SwapRealizedPnL} ✅");
}
}
}