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, "稳态窗口内多次读取零重载(零查询)");
}
[TestMethod]
public void TTL过期重载失败_保留旧快照不抛_且窗口内不重试()
{
Fr007FixingCache.NowTicks = () => 1_000_000L;
Assert.IsTrue(Fr007FixingCache.TryGet(D1, out _), "先成功装载一次");
Assert.AreEqual(1, _loadCount);
Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); };
Fr007FixingCache.NowTicks = () => 1_000_000L + TimeSpan.FromMinutes(1).Ticks + 1; // TTL 过期 → 触发重载 → 失败
// 不抛:历史定盘不可变,命中继续走旧快照(异常会直接 fail 本用例)
Assert.IsTrue(Fr007FixingCache.TryGet(D1, out var p), "重载失败应保留旧快照继续命中");
Assert.AreEqual(0.0142, p, 1e-12, "旧快照值不变");
Assert.AreEqual(2, _loadCount, "失败的重载尝试恰好一次");
Fr007FixingCache.TryGet(D2, out _); // TTL 时钟已被重置:窗口内不再重试(防重试风暴)
Assert.AreEqual(2, _loadCount, "失败后TTL窗口内不得反复重试全表SELECT");
}
[TestMethod]
public void 首次装载失败_miss不抛_窗口内不重试()
{
Fr007FixingCache.LoadSnapshot = () => { _loadCount++; throw new InvalidOperationException("db down"); };
Fr007FixingCache.NowTicks = () => 1_000_000L;
Assert.IsFalse(Fr007FixingCache.TryGet(D1, out _), "装载失败=空快照miss(调用方直查库兜底,新数据该响仍响)");
Assert.AreEqual(1, _loadCount);
Fr007FixingCache.TryGet(D1, out _);
Assert.AreEqual(1, _loadCount, "失败后TTL窗口内不重试");
}
}
}