using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model; using static YLErp.Modules.SwapModule.TestableSwapTradeAutoService; namespace YLErp.Modules.SwapModule { [TestClass] public class MergeComposeScenarioTest { private const string UnderlyingCode = "220205.IB"; private const int ClientId = 10; private static readonly DateTime TradeDate = new DateTime(2025, 4, 24); #region 场景1:空merge列表 → 直接返回 [TestMethod] public void Scenario1_EmptyMergeList_ShouldReturn() { var service = CreateService(); service.ExecuteMergeRestModeCompose(new List(), TradeDate); Assert.AreEqual(0, service.CreatedTrades.Count, "不应创建任何交易"); } #endregion #region 场景2:客户不存在 → 抛异常 [TestMethod] [ExpectedException(typeof(ServiceException))] public void Scenario2_ClientNotFound_ShouldThrow() { // 不注入任何 client var service = CreateService(clients: new Dictionary()); var merges = new List { CreateMerge() }; service.ExecuteMergeRestModeCompose(merges, TradeDate); } #endregion #region 场景3:客户未设置场外互换权限 → 抛异常 [TestMethod] [ExpectedException(typeof(ServiceException))] public void Scenario3_ClientNoSwapPermission_ShouldThrow() { var client = CreateClient(hasSwapPermission: false); var service = CreateService(clients: new Dictionary { [ClientId] = client }); var merges = new List { CreateMerge() }; service.ExecuteMergeRestModeCompose(merges, TradeDate); } #endregion #region 场景4:无TRS簿记账户 → 抛异常 [TestMethod] [ExpectedException(typeof(ServiceException))] public void Scenario4_NoEtradingRule_ShouldThrow() { var client = CreateClient(); // etradingRuleFactory 返回 null var service = CreateService( clients: new Dictionary { [ClientId] = client }, etradingRuleFactory: (side, num) => null ); var merges = new List { CreateMerge() }; service.ExecuteMergeRestModeCompose(merges, TradeDate); } #endregion #region 场景5:找不到簿记账户资产单元 → 抛异常 [TestMethod] [ExpectedException(typeof(ServiceException))] public void Scenario5_NoAssetUnit_ShouldThrow() { var client = CreateClient(); var service = CreateService( clients: new Dictionary { [ClientId] = client }, etradingRuleFactory: (side, num) => CreateEtradingRule("TRS_ACCOUNT"), assets: new Dictionary() // 空的,找不到 ); var merges = new List { CreateMerge() }; service.ExecuteMergeRestModeCompose(merges, TradeDate); } #endregion #region 场景6:单条merge + 无持仓 → DealNoPosition 创建一笔交易 [TestMethod] public void Scenario6_SingleMerge_NoPosition_ShouldCreateOneTrade() { var client = CreateClient(); var service = CreateService( clients: new Dictionary { [ClientId] = client }, etradingRuleFactory: (side, num) => CreateEtradingRule("TRS_ACCOUNT"), assets: new Dictionary { ["TRS_ACCOUNT"] = CreateAssetUnit() }, underlyings: new Dictionary { [UnderlyingCode] = CreateUnderlying() }, positions: new List() ); var merges = new List { CreateMerge(qty: 100000) }; service.ExecuteMergeRestModeCompose(merges, TradeDate); Assert.AreEqual(1, service.CreatedTrades.Count, "应创建1笔交易"); Assert.AreEqual(0, service.UnwindCalls.Count, "无持仓不应调用平仓"); Assert.IsTrue(service.SaveChangesCount > 0, "应调用SaveChanges"); } #endregion #region 场景7:两条merge + 无持仓 → DealNoPosition 创建交易+平仓 [TestMethod] public void Scenario7_TwoMerges_NoPosition_ShouldCreateTradeAndUnwind() { var client = CreateClient(); var service = CreateService( clients: new Dictionary { [ClientId] = client }, etradingRuleFactory: (side, num) => CreateEtradingRule("TRS_ACCOUNT"), assets: new Dictionary { ["TRS_ACCOUNT"] = CreateAssetUnit() }, underlyings: new Dictionary { [UnderlyingCode] = CreateUnderlying() }, positions: new List() ); var merges = new List { CreateMerge(bsType: 1, qty: 100000), // 买 CreateMerge(bsType: 2, qty: -50000) // 卖 }; service.ExecuteMergeRestModeCompose(merges, TradeDate); // 两条流水:先开仓,再平仓(买100000 vs 卖50000 → 平50000 + 剩余开仓50000) Assert.IsTrue(service.UnwindCalls.Count > 0, "有两条流水应触发平仓操作"); } #endregion #region 场景8:找不到标的 → 抛异常 [TestMethod] [ExpectedException(typeof(ServiceException))] public void Scenario8_UnderlyingNotFound_ShouldThrow() { var client = CreateClient(); var service = CreateService( clients: new Dictionary { [ClientId] = client }, etradingRuleFactory: (side, num) => CreateEtradingRule("TRS_ACCOUNT"), assets: new Dictionary { ["TRS_ACCOUNT"] = CreateAssetUnit() }, underlyings: new Dictionary() // 空的 ); var merges = new List { CreateMerge() }; service.ExecuteMergeRestModeCompose(merges, TradeDate); } #endregion #region 辅助方法 private TestableSwapTradeAutoService CreateService( Dictionary clients = null, Dictionary assets = null, Dictionary underlyings = null, List positions = null, Func etradingRuleFactory = null, List trades = null, List flowEvents = null, List validTrades = null) { var user = new OptUserInfo(1, "Test", OptUserFrom.UnitTest); return new TestableSwapTradeAutoService( user, trades: trades, positions: positions ?? new List(), clients: clients ?? new Dictionary(), assets: assets ?? new Dictionary(), underlyings: underlyings ?? new Dictionary(), etradingRuleFactory: etradingRuleFactory, flowEvents: flowEvents ?? new List(), validTrades: validTrades ?? new List() ); } private swap_flow_merge CreateMerge(int bsType = 1, decimal qty = 100000, decimal avgPrice = 1.0020m) { return new swap_flow_merge { OccurTime = TradeDate, SwapTradeId = 9001, SwapTradeNo = "TEST-IS-001", UnderlyingCode = UnderlyingCode, BsType = bsType, TradingQty = qty, TradingAmount = Math.Abs(qty) * avgPrice, TradingAmountAvg = avgPrice, TradingAmountFeeAvg = avgPrice, TradingAmountNetAvg = avgPrice - 0.005m, TradingAmountNetFeeAvg = avgPrice - 0.005m, TradingFeePending = 0, ContractSize = 1, ClientId = ClientId, DataState = 1, FirstFlowTime = DateTime.Now }; } private Client CreateClient(bool hasSwapPermission = true) { var client = new Client { id = ClientId, Name = "测试客户", Number = "C001", BoundSide = BoundSideEnum.南向, SwapTradeType = 0 }; if (hasSwapPermission) { client.DerivativesInvestmentVarieties = ((int)DerivativesInvestmentVarietiesEnum.场外互换).ToString(); } else { client.DerivativesInvestmentVarieties = ""; } return client; } private EtradingRule CreateEtradingRule(string assetAccount) { return new EtradingRule { AssetAccount_0 = assetAccount, ClearingAgency_0 = "TEST_CLEARING" }; } private AssetUnit CreateAssetUnit() { return new AssetUnit { Name = "TRS_ACCOUNT", TraderIds = "1" }; } private underlying_manager CreateUnderlying() { return new underlying_manager { UnderlyingCode = UnderlyingCode, UnderlyingInstrumentType = "TBonds", ContractSize = 1 }; } #endregion } }