using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model; namespace YLErp.Modules.SwapModule { /// /// SwapEodPositionService 的可测试化基类(纯内存,不连库)。 /// ============================================================================ /// 收敛各 ScenarioTest 中 Stub 子类的重复 override: /// - PersistEodSwapPosition:收集到列表,不写库 /// - SaveAllChanges:no-op /// - GetCurrencyRate:返回 1.0(本币) /// - 统一构造函数(注入 OptUserInfo,标记 UnitTest 来源) /// /// 暴露 PersistedPositions / SaveChangesCount / ClientCashCalls 等输出捕获属性, /// 供断言使用。各测试子类按需再 override 业务 seam(FindTrade/GetUnderlyingPrice 等)。 /// /// 设计原则: /// - 只收敛 8/8 Stub 都重复的高频 override,不预设业务数据注入方式 /// (ComposePage 用属性字典、SwapPositionCompose 用构造函数 List,差异留给子类) /// - 不提供 Execute* 包装器(签名各异且大多只出现 1-2 次,留在各子类避免基类膨胀) /// ============================================================================ /// public class TestableSwapEodPositionService : SwapEodPositionService { /// 捕获所有持久化的 eod 持仓(按调用顺序) public List PersistedPositions { get; } = new(); /// /// 捕获所有持久化的互换流水事件(按调用顺序)。 /// 生产 PersistFlowEvent 写 DbContext.swap_flow_event;GetConsumedInterest 也读同一张表。 /// 测试不连库,这里把真实收盘产生的 swap_flow_event 收集起来, /// 使 GetConsumedInterest 能像生产一样从"真实累积的流水"里算已结利息, /// 而无需硬编码、无需连库。这是消除"return 0m"伪绿的关键。 /// public List FlowEvents { get; } = new(); /// SaveAllChanges 调用次数 public int SaveChangesCount { get; private set; } /// AddClientCash 调用记录(金额, 操作) public List<(double amount, string action)> ClientCashCalls { get; } = new(); /// SwapPositionCompose 使用的公司行为内存数据;默认空,避免测试访问数据库。 public List ExDividendInfos { get; } = new(); /// 自增 id 模拟器(新增 eod 时分配 id) private int _nextId = 1; protected TestableSwapEodPositionService(string testName) : base(new OptUserInfo(0, testName ?? nameof(TestableSwapEodPositionService), OptUserFrom.UnitTest)) { } // ===== 高频 seam override(8/8 Stub 都重复,收敛到基类)===== protected override void PersistEodSwapPosition(eod_swap_position position) { if (position.id == 0) position.id = _nextId++; PersistedPositions.Add(position); } /// /// 捕获真实收盘产生的 swap_flow_event(生产写 DbContext.swap_flow_event)。 /// 与 PersistEodSwapPosition 同理,这里只收集不写库,供 GetConsumedInterest 真实计算。 /// protected void PersistFlowEvent(swap_flow_event flowEvent) { if (flowEvent.id == 0) flowEvent.id = _nextId++; FlowEvents.Add(flowEvent); } protected override void SaveAllChanges() { SaveChangesCount++; } protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) { return 1.0; // 本币,汇率=1 } protected override List FindExDividendInfos(DateTime settleDate) { return ExDividendInfos .Where(x => x.ValidStatus && x.EffectiveDate.HasValue && x.EffectiveDate.Value.Date == settleDate.Date) .ToList(); } protected override decimal GetFundCorporateActionClosePrice( ex_dividend_info dividendInfo, decimal fallbackPrice) => fallbackPrice; protected override decimal GetDividendTaxRate() { return 0m; } protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate) { ClientCashCalls.Add((amount, action)); return _nextId++; } } }