using YLErp; namespace YLErp.Modules.EodModule { /// /// 日终价格管理 —— 纯单元测试(不连库、秒级)。 /// 锁定两处改动的意图: /// 问题4:列表"标的种类"按真实类型显示,且路由键 UnderlyingInstrumentType 不变; /// 问题3:债券(china_bond_valuation)数据来源按是否手工改过区分"人工"/"系统"。 /// [TestClass] public class EodPriceDtoTest { #region 问题4:显示走真实类型,不再一律"商品期货";路由键保持不变 [TestMethod] [Description("有真实类型时,标的种类按真实类型显示,而非硬编码'商品期货'")] public void 有真实类型_按真实类型显示_而非商品期货() { // 模拟从 eod_commodity_future_price 出来、但真实是现券的一行 var dto = new EodUnderlyingPriceDto { UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures, // 路由键(旧硬编码值) RealInstrumentType = ConsGlobal.InstrumentType.CreditBonds // 真实类型=信用债 }; Assert.AreEqual("信用债", dto.UnderlyingInstrumentTypeCn, "显示应走真实类型"); Assert.AreNotEqual("商品期货", dto.UnderlyingInstrumentTypeCn, "不应再一律显示商品期货"); } [TestMethod] [Description("贵金属现货从商品期货表出来,也应显示真实种类")] public void 贵金属现货_显示黄金现货_而非商品期货() { var dto = new EodUnderlyingPriceDto { UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures, RealInstrumentType = ConsGlobal.InstrumentType.GoldSpot }; Assert.AreEqual("黄金现货", dto.UnderlyingInstrumentTypeCn); Assert.AreNotEqual("商品期货", dto.UnderlyingInstrumentTypeCn); } [TestMethod] [Description("真正的商品期货,真实类型=CommodityFutures,仍显示商品期货")] public void 真商品期货_仍显示商品期货() { var dto = new EodUnderlyingPriceDto { UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures, RealInstrumentType = ConsGlobal.InstrumentType.CommodityFutures }; Assert.AreEqual("商品期货", dto.UnderlyingInstrumentTypeCn); } [TestMethod] [Description("RealInstrumentType 为空时,回退到路由键,保证 null 安全不崩")] public void 真实类型为空_回退到路由键() { var dto = new EodUnderlyingPriceDto { UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures, RealInstrumentType = null }; Assert.AreEqual("商品期货", dto.UnderlyingInstrumentTypeCn, "?? 回退应等于路由键的中文"); } [TestMethod] [Description("路由键 UnderlyingInstrumentType 不受显示改动影响(保证'查看'不串表)")] public void 路由键不变_保证查看不串表() { var dto = new EodUnderlyingPriceDto { UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures, RealInstrumentType = ConsGlobal.InstrumentType.TBonds }; // 显示变了,但路由键仍是 CommodityFutures → EodPriceView 仍会去 eod_commodity_future_price 取数 Assert.AreEqual("利率债", dto.UnderlyingInstrumentTypeCn); Assert.AreEqual(ConsGlobal.InstrumentType.CommodityFutures, dto.UnderlyingInstrumentType); } #endregion #region 手工上传日终价格落表分流 [TestMethod] [Description("股指期货和基金进入股票价格表,Shibor、利率收益率和债券指数进入商品期货价格表")] public void 手工上传_按标的类型选择股票价格表() { Assert.IsTrue(SettlementPriceImportService.UseStockPriceTable(ConsGlobal.InstrumentType.StockIF)); Assert.IsTrue(SettlementPriceImportService.UseStockPriceTable(ConsGlobal.InstrumentType.Fund)); Assert.IsFalse(SettlementPriceImportService.UseStockPriceTable(ConsGlobal.InstrumentType.Shibor)); Assert.IsFalse(SettlementPriceImportService.UseStockPriceTable(ConsGlobal.InstrumentType.RateYield)); Assert.IsFalse(SettlementPriceImportService.UseStockPriceTable(ConsGlobal.InstrumentType.BondIndex)); } #endregion #region 问题3:债券数据来源按是否手工改过区分"人工"/"系统" [TestMethod] [Description("债券来源:被手工改过(update_user 有值)→人工,中债自动同步(update_user 为 NULL)→系统")] public void 债券来源_手工改过为人工_否则为系统() { Assert.AreEqual(EodPriceBase.人工, EodPriceService.ResolveBondDisplaySource(1024L)); Assert.AreEqual(EodPriceBase.系统, EodPriceService.ResolveBondDisplaySource(null)); } [TestMethod] [Description("来源常量应为约定的中文'人工'/'系统'")] public void 来源常量取值正确() { Assert.AreEqual("系统", EodPriceBase.系统); Assert.AreEqual("人工", EodPriceBase.人工); } #endregion #region 债券手工编辑:操作人写入已有列(不新增字段) [TestMethod] [Description("新增债券估值:create_user 与 update_user 都写入当前登录用户ID")] public void 新增债券估值_写入创建人与更新人() { var m = new ChinaBondValuation(); EodPriceService.StampBondOperator(m, 1024, isNew: true); Assert.AreEqual(1024L, m.create_user, "新增时应写创建人"); Assert.AreEqual(1024L, m.update_user, "新增时应写更新人"); } [TestMethod] [Description("更新已有债券估值:仅更新 update_user,保留原 create_user(不覆盖创建人)")] public void 更新债券估值_仅写更新人_保留创建人() { var m = new ChinaBondValuation { create_user = 7 }; EodPriceService.StampBondOperator(m, 1024, isNew: false); Assert.AreEqual(7L, m.create_user, "更新时不应覆盖原创建人"); Assert.AreEqual(1024L, m.update_user, "更新人应为本次操作者"); } [TestMethod] [Description("聚源/中债自动同步(外部ETL)不调用 StampBondOperator,故 create_user/update_user 保持 NULL = 自动同步")] public void 自动同步路径_操作人列为NULL() { // 注意:SettlementPriceImportService 是"手工上传"入口(会戳操作人),不是自动同步。 // 真正的聚源/中债自动同步在外部 ETL(本仓库无代码),其写入不经 StampBondOperator。 var m = new ChinaBondValuation(); // 模拟自动同步:仅写价格字段,不戳操作人 Assert.IsNull(m.create_user); Assert.IsNull(m.update_user); } [TestMethod] [Description("手工上传(SettlementPriceImportService):新增行(id==0)应写 create_user+update_user,使来源列显示上传人")] public void 手工上传新增_写入创建人与更新人() { // 模拟上传债券新增分支:eodPrice.id 默认 0 → isNew=true var m = new ChinaBondValuation(); EodPriceService.StampBondOperator(m, 2048, isNew: m.id == 0); Assert.AreEqual(2048L, m.create_user, "上传新增应写创建人"); Assert.AreEqual(2048L, m.update_user, "上传新增应写更新人"); } [TestMethod] [Description("手工上传(SettlementPriceImportService):命中已有行(id!=0)只写 update_user,保留原 create_user")] public void 手工上传更新_仅写更新人_保留创建人() { // 模拟上传命中已有债券行:id!=0 → isNew=false var m = new ChinaBondValuation { id = 55, create_user = 9 }; EodPriceService.StampBondOperator(m, 2048, isNew: m.id == 0); Assert.AreEqual(9L, m.create_user, "上传更新不应覆盖原创建人"); Assert.AreEqual(2048L, m.update_user, "上传更新应写本次上传人"); } #endregion #region 问题3补充:债券来源人工/系统(判定逻辑已并入上方"问题3"区域) #endregion #region 日期窗口解析("页面始终5条"根因锁定,纯单测不连库) [TestMethod] [Description("前端未传日期(年份<=2000) → 回退到 [今天-1年, 今天+1年)")] public void 未传日期_回退最近一年到明年() { var (start, end) = EodPriceService.ResolveValueDateWindow(DateTime.MinValue, DateTime.MinValue); Assert.AreEqual(DateTime.Today.AddYears(-1).Date, start.Date, "起始应回退到今天-1年"); Assert.AreEqual(DateTime.Today.AddYears(1).Date, end.Date, "结束应回退到今天+1年"); Assert.IsTrue(end > start, "窗口应正向"); } [TestMethod] [Description("列表页默认起止都填今天 → 窗口=[今天, 今天+1天),仅返回当天记录(即'5条'现象成因)")] public void 起止都填今天_窗口仅今天() { var today = DateTime.Today; var (start, end) = EodPriceService.ResolveValueDateWindow(today, today); Assert.AreEqual(today.Date, start.Date, "起始应为今天"); Assert.AreEqual(today.AddDays(1).Date, end.Date, "结束应为今天+1天(半开区间含今天)"); Assert.IsTrue(today >= start && today < end, "今天的记录应落入窗口"); Assert.IsFalse(today.AddDays(-1) >= start && today.AddDays(-1) < end, "昨天的记录不应落入仅今天窗口"); Assert.IsFalse(today.AddDays(1) >= start && today.AddDays(1) < end, "明天的记录不应落入仅今天窗口"); } [TestMethod] [Description("显式传区间(如近30天) → 原样生效,不被回退覆盖")] public void 显式区间_原样生效() { var start0 = DateTime.Today.AddDays(-30); var end0 = DateTime.Today; var (start, end) = EodPriceService.ResolveValueDateWindow(start0, end0); Assert.AreEqual(start0.Date, start.Date, "起始应等于传入"); Assert.AreEqual(end0.AddDays(1).Date, end.Date, "结束应等于传入+1天"); } [TestMethod] [Description("结束日=今天 → 半开区间上界=今天+1天,今天当天记录可命中")] public void 结束日今天_上界为明天_当天可命中() { var (start, end) = EodPriceService.ResolveValueDateWindow(DateTime.Today.AddDays(-365), DateTime.Today); var today = DateTime.Today; Assert.IsTrue(today >= start && today < end, "今天记录应命中"); Assert.IsFalse(today.AddDays(1) >= start && today.AddDays(1) < end, "明天记录不应命中"); } #endregion } }