using YLErp.Modules.DataProviderModule; namespace YLErp.Modules.DataProviderModule { /// /// Fr007FixingCache 快照缓存行为契约(纯内存,不连库;Loader/时钟均为注入接缝): /// ① 首次访问批量预载、命中 O(1);② miss 不进快照(负缓存防线——当日发布前 miss、发布后须能查到); /// ③ 写侧版本失效:Invalidate 后立即重载取到新值(不等 TTL);④ TTL 过期自动重载(直改库兜底); /// ⑤ TTL 窗口内无写入不重载(零查询稳态)。 /// [TestClass] public class Fr007FixingCacheTest { private static readonly DateTime D1 = new(2026, 7, 6); private static readonly DateTime D2 = new(2026, 7, 13); private int _loadCount; private Dictionary _market; [TestInitialize] public void Init() { _loadCount = 0; _market = new Dictionary { [D1] = 0.0142, [D2] = 0.01425 }; Fr007FixingCache.ResetForTest(); Fr007FixingCache.LoadSnapshot = () => { _loadCount++; return new Dictionary(_market); }; } [TestCleanup] public void Cleanup() => Fr007FixingCache.ResetForTest(); private static void AdvanceClock(long ticks) => Fr007FixingCache.NowTicks = () => ticks; [TestMethod] public void 首次访问预载并命中() { Fr007FixingCache.NowTicks = () => 1_000_000L; Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p1), "预载后历史定盘应命中"); Assert.AreEqual(0.0142, p1, 1e-12); Assert.AreEqual(1, _loadCount, "首次访问恰好装载一次"); Assert.IsTrue(Fr007FixingCache.TryGet(D2, out var p2), "同快照内多次命中"); Assert.AreEqual(0.01425, p2, 1e-12); Assert.AreEqual(1, _loadCount, "命中不应重复装载"); } [TestMethod] public void miss不进快照_当日新发布经直查兜底后TTL内可见() { Fr007FixingCache.NowTicks = () => 1_000_000L; var today = new DateTime(2026, 8, 18); Assert.IsFalse(Fr007FixingCache.TryGet(today, out _), "快照无该行应 miss(调用方直查库兜底)"); Assert.AreEqual(1, _loadCount); // 直查库发现了新发布的当日行 → 写侧失效(SwapFlowService 场景)→ 重载后可见 _market[today] = 0.0143; Fr007FixingCache.Invalidate(); Assert.IsTrue(Fr007FixingCache.TryGet(today, out var p), "写侧失效重载后当日行应可见"); Assert.AreEqual(0.0143, p, 1e-12); Assert.AreEqual(2, _loadCount); } [TestMethod] public void 写侧失效立即重载取到修正值_不等TTL() { Fr007FixingCache.NowTicks = () => 1_000_000L; Fr007FixingCache.TryGet(D1, out _); Assert.AreEqual(1, _loadCount); _market[D1] = 0.0150; // 界面修正错价 Fr007FixingCache.Invalidate(); // 时钟只走了 1 tick(远小于 TTL),仍必须重载 Fr007FixingCache.NowTicks = () => 1_000_001L; Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p), "修正后仍应命中"); Assert.AreEqual(0.0150, p, 1e-12, "TTL 未到也必须看到写侧修正值"); Assert.AreEqual(2, _loadCount, "版本失效应立即触发重载"); } [TestMethod] public void TTL过期自动重载_直改库兜底() { Fr007FixingCache.NowTicks = () => 1_000_000L; Fr007FixingCache.TryGet(D1, out _); Assert.AreEqual(1, _loadCount); _market[D1] = 0.0160; // 直改库(无 Invalidate) Fr007FixingCache.NowTicks = () => 1_000_000L + TimeSpan.FromMinutes(1).Ticks + 1; // TTL+1 tick Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p)); Assert.AreEqual(0.0160, p, 1e-12, "TTL 过期后应重载并看到直改库的新值"); Assert.AreEqual(2, _loadCount); } [TestMethod] public void TTL窗口内无写入零重载() { Fr007FixingCache.NowTicks = () => 1_000_000L; Fr007FixingCache.TryGet(D1, out _); Fr007FixingCache.NowTicks = () => 1_000_000L + TimeSpan.FromMinutes(1).Ticks - 1; // TTL-1 tick:稳态窗口 for (int i = 0; i < 10; i++) Fr007FixingCache.TryGet(D1, out _); Assert.AreEqual(1, _loadCount, "稳态窗口内多次读取零重载(零查询)"); } } }