Files
EIS-dotnet/tests/Eis.Infrastructure.Tests/Caching/ApplicationCacheTests.cs
T
biss fea91a19af 本阶段迁移已完成。
Excel:班级、账号、考生、缴费、考场、成绩、志愿、录取、报到及正式录取名册均改为 ClosedXML 原生处理,支持模板、校验、筛选和导入预检。[SystemWorkbook.cs](C:\\Users\\BI\\Documents\\EIS-dotnet\\src\\Eis.Infrastructure\\Spreadsheets\\SystemWorkbook.cs)
文书:录取通知书模板、成绩单/准考证/通知书业务数据与防伪验真已脱离 Node;保留现有前端排版导出效果。[AdminAdmissionService.Documents.cs](C:\\Users\\BI\\Documents\\EIS-dotnet\\src\\Eis.Infrastructure\\Administration\\AdminAdmissionService.Documents.cs)
缓存:完成 Redis 命名空间版本缓存、有界本地回退、请求合并和并发失效保护;公开数据及成绩写入后立即失效。[ApplicationCache.cs](C:\\Users\\BI\\Documents\\EIS-dotnet\\src\\Eis.Infrastructure\\Caching\\ApplicationCache.cs)
迁移清单已更新为完成。[MIGRATION.md](C:\\Users\\BI\\Documents\\EIS-dotnet\\MIGRATION.md)
2026-07-23 14:14:51 +08:00

79 lines
2.9 KiB
C#

using System.Text.Json.Nodes;
using Eis.Infrastructure.Caching;
namespace Eis.Infrastructure.Tests.Caching;
public sealed class ApplicationCacheTests
{
[Fact]
public async Task RememberJsonAsync_CoalescesConcurrentLocalLoads()
{
using var cache = new ApplicationCache(redisUrl: null);
var calls = 0;
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<JsonObject> Load()
{
Interlocked.Increment(ref calls);
await release.Task;
return new JsonObject { ["value"] = 42 };
}
var first = cache.RememberJsonAsync("public", "home", Load);
var second = cache.RememberJsonAsync("public", "home", Load);
release.SetResult();
var values = await Task.WhenAll(first, second);
Assert.Equal(1, calls);
Assert.All(values, value => Assert.Equal(42, value["value"]!.GetValue<int>()));
}
[Fact]
public async Task InvalidateAsync_DropsLocalNamespaceWithoutAffectingOthers()
{
using var cache = new ApplicationCache(redisUrl: null);
var publicCalls = 0;
var resultCalls = 0;
Task<JsonObject> PublicLoader() =>
Task.FromResult(new JsonObject { ["generation"] = ++publicCalls });
Task<JsonObject> ResultLoader() =>
Task.FromResult(new JsonObject { ["generation"] = ++resultCalls });
_ = await cache.RememberJsonAsync("public", "home", PublicLoader);
_ = await cache.RememberJsonAsync("results", "candidate:1", ResultLoader);
Assert.False(await cache.InvalidateAsync("public"));
var publicValue = await cache.RememberJsonAsync("public", "home", PublicLoader);
var resultValue = await cache.RememberJsonAsync("results", "candidate:1", ResultLoader);
Assert.Equal(2, publicValue["generation"]!.GetValue<int>());
Assert.Equal(1, resultValue["generation"]!.GetValue<int>());
}
[Fact]
public async Task InvalidateAsync_PreventsInFlightLoadFromRepopulatingOldGeneration()
{
using var cache = new ApplicationCache(redisUrl: null);
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var calls = 0;
async Task<JsonObject> SlowLoader()
{
var value = Interlocked.Increment(ref calls);
await release.Task;
return new JsonObject { ["generation"] = value };
}
var staleLoad = cache.RememberJsonAsync("public", "home", SlowLoader);
_ = await cache.InvalidateAsync("public");
var refreshed = await cache.RememberJsonAsync(
"public",
"home",
() => Task.FromResult(new JsonObject { ["generation"] = Interlocked.Increment(ref calls) }));
release.SetResult();
_ = await staleLoad;
Assert.Equal(2, refreshed["generation"]!.GetValue<int>());
}
}