using System.Net; using System.Reflection; using System.Text; using game.Account; using game.PlayerDataStorage; using Newtonsoft.Json; static partial class Tests { static readonly Uri Api = new("http://synthetic.invalid/"); static readonly string RunId = DateTime.UtcNow.ToString("yyyyMMddHHmmss") + "_" + Guid.NewGuid().ToString("N"); static readonly string Root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../runs", RunId)); static readonly List Results = new(); static int assertions, failures; static async Task Main(string[] args) { if(args.Length==1 && args[0]=="--audit-precompiled") return PrecompiledAudit.Run(); if(args.Length==2 && args[0]=="--cleanup-failure") return await IsolatedCleanupFailure(args[1]); Directory.CreateDirectory(Root); await Run("B2-C1-metadata-one-auth-shared-cookie", SingleAuthentication); await Run("B2-C1-lease-once-before-after-close", Admission); await Run("B2-C1-denied-routes-zero-send", Routes); await Run("B2-C1-real-handler-rejected", RealHandlerRejected); await Run("B2-C1-cyclic-handler-rejected-without-recursive-dispose", CyclicHandlerRejected); await Run("B2-C1-active-send-drains-before-session", Drain); await Run("B2-C1-reentrant-cancel-same-task", Reentry); await Run("B2-C1-dispose-failure-retains-session", DisposeFailure); await Run("B2-C1-issuance-close-race", IssuanceRace); await Run("B2-C1-factory-revokes-context", FactoryRevocation); await Run("B2-C1-stale-success-body-discarded", StaleResponse); await Run("B2-C1-401-and-redirect-evidence", Responses); await Run("B2-C1-registry-timeout-then-real-drain", RegistryTimeout); await Run("B2-C1-registry-failure-retained", RegistryFailure); await Run("B2-C1-registry-fails-before-other-drains", RegistryEarlyFailure); await Run("B2-C1-new-failure-wakes-existing-observer", RegistryNewFailure); await Run("B2-C1-registry-registration-during-await", RegistryRegistration); await Run("B2-C1-registry-invalid-cancel", RegistryCancellation); await Run("B2-C1-process-registry-cross-caller", ProcessBarrier); await RunSchemaTests(); await RunD3Tests(); await RunCollectionSchemaTests(); await RunClubPackSchemaTests(); await RunCampEventSchemaTests(); await RunOfferSchemaTests(); await RunBuffTriggerSchemaTests(); await RunActivitySchemaTests(); await RunPinataSchemaTests(); await RunCubeSchemaTests(); await RunBattlePassSchemaTests(); await RunPotionSchemaTests(); await RunPlayerSchemaTests(); await RunShopSchemaTests(); await RunEventBaseSchemaTests(); await RunFishSchemaTests(); await RunPartnerSchemaTests(); await RunLoginSchemaTests(); await RunAggregateTests(); await RunCoordinatorTests(); var report = new { runId = RunId, checkpoint = "4-validation", failures, assertions, accessedFormalServer = false, tests = Results }; await File.WriteAllTextAsync(Path.Combine(Root, "result.json"), JsonConvert.SerializeObject(report, Formatting.Indented)); Console.WriteLine($"RESULT tests={Results.Count} failed={failures} assertions={assertions} run={RunId}"); return failures == 0 ? 0 : 1; } static async Task Run(string name, Func test) { var before = assertions; try { await test().WaitAsync(TimeSpan.FromSeconds(30)); Results.Add(new { name, passed = true, assertions = assertions - before }); Console.WriteLine("PASS " + name); } catch (Exception ex) { failures++; Results.Add(new { name, passed = false, error = ex.GetType().Name }); Console.WriteLine("FAIL " + name + " " + ex); } } static void Check(bool value, string message) { Interlocked.Increment(ref assertions); if (!value) throw new InvalidOperationException(message); } sealed class CyclicHandler : DelegatingHandler { } static async Task CyclicHandlerRejected() { var first = new CyclicHandler(); var second = new CyclicHandler(); first.InnerHandler = second; second.InnerHandler = first; var capability = PlayerKVControlledLoadCapability.CreateForValidation(_ => first); await Throws(() => { capability.CreateHandler(new CookieContainer()); return Task.CompletedTask; }); // No send occurred. Break the rejected graph before its owner disposes it. second.InnerHandler = new Handler((_, _) => throw new InvalidOperationException("Must not send.")); first.Dispose(); } static TaskCompletionSource Signal() => new(TaskCreationOptions.RunContinuationsAsynchronously); static HttpResponseMessage Json(string text, HttpStatusCode code = HttpStatusCode.OK) => new(code) { Content = new StringContent(text, Encoding.UTF8, "application/json") }; static async Task Throws(Func action) where T : Exception { try { await action(); } catch (T) { Check(true, "expected exception"); return; } throw new InvalidOperationException("Expected " + typeof(T).Name); } sealed class Handler : HttpMessageHandler { readonly Func> send; readonly Action dispose; internal Handler(Func> send, Action dispose = null) { this.send = send; this.dispose = dispose; } protected override Task SendAsync(HttpRequestMessage r, CancellationToken t) => send(r, t); protected override void Dispose(bool disposing) { if (disposing) dispose?.Invoke(); base.Dispose(disposing); } } sealed class Fixture { internal readonly List Order = new(); internal int Auth, Gets, BorrowedSends, SessionDisposes, LeaseDisposes; internal bool Current = true, FailLeaseDispose; internal Action DuringFactory; internal CookieContainer Jar; internal Func> Borrowed = (_, _) => Task.FromResult(Json("{}")); internal PlayerKVLoginContext Context; internal void Record(string x) { lock (Order) Order.Add(x); } internal Fixture() { Context = new PlayerKVLoginContext("101", Path.Combine(Root, "cache", Guid.NewGuid().ToString("N")), 7, _ => Current, Api, () => new S101AccountSession(Api, jar => { Jar = jar; return new Handler((r, t) => { Check(r.RequestUri.AbsolutePath == "/account/Login", "one synthetic auth route"); Auth++; jar.Add(Api, new Cookie("sid", "synthetic")); return Task.FromResult(Json("{\"state\":0,\"internalId\":17,\"newCreate\":true,\"createTime\":\"2026-01-01T00:00:00Z\",\"svrTime\":\"2026-01-02T00:00:00Z\"}")); }, () => { SessionDisposes++; Record("session"); }); }), () => new PlayerKVGameSessionBridge(jar => { Check(ReferenceEquals(Jar, jar), "bridge shares cookie identity"); return new Handler((r, t) => { Check(r.RequestUri.AbsolutePath == "/playerdata/Get", "get only, zero KV mutation"); Gets++; return Task.FromResult(Json("{\"data\":{}}")); }, () => Record("bridge")); })); } internal PlayerKVControlledLoadCapability Capability() => PlayerKVControlledLoadCapability.CreateForValidation(jar => { Check(ReferenceEquals(Jar, jar), "borrower shares exact cookie identity"); DuringFactory?.Invoke(); return new Handler((r, t) => { BorrowedSends++; return Borrowed(r, t); }, () => { LeaseDisposes++; Record("lease"); if (FailLeaseDispose) throw new IOException("synthetic cleanup fault"); }); }); internal async Task PreparedLease() { Check((await Context.PrepareAsync()).Prepared, "prepared"); Check(Context.TryAcquireAccountLease(Capability(), out var lease), "lease admitted"); return lease; } } static async Task SingleAuthentication() { var f = new Fixture(); var lease = await f.PreparedLease(); var d = f.Context.AccountDescriptor; Check(d.PlayerId == "101" && d.Epoch == 7 && d.InternalId == 17 && d.NewlyCreated, "metadata belongs to authentication"); Check(d.CreateTimeUtc.Year == 2026 && d.ServerTimeUtc.Day == 2, "metadata time"); Check((await lease.SendAsync(HttpMethod.Get, "__kv_validation/ok")).IsCurrent, "borrowed send"); Check(f.Auth == 1 && f.Gets == 1 && f.BorrowedSends == 1, "no second login"); await f.Context.CloseAsync(); Check(string.Join(",", f.Order) == "lease,bridge,session", "borrower drains before bridge and session"); Check(f.Context.AccountDescriptor == null && !lease.IsCurrent, "closed admission revoked"); } static async Task Admission() { var f = new Fixture(); Check(!f.Context.TryAcquireAccountLease(f.Capability(), out _), "before prepared rejected"); var lease = await f.PreparedLease(); Check(!f.Context.TryAcquireAccountLease(f.Capability(), out _), "one lease only"); Check(!f.Context.TryAcquireAccountLease(null, out _), "capability required"); var close = f.Context.CloseAsync(); Check(!f.Context.TryAcquireAccountLease(f.Capability(), out _), "close synchronous"); await close; Check(!(await lease.SendAsync(HttpMethod.Get, "__kv_validation/ok")).IsCurrent && f.BorrowedSends == 0, "after close no transport"); } static async Task Routes() { var f = new Fixture(); var l = await f.PreparedLease(); foreach (var route in new[] { "account/Login", "playerdata/Get", "playerdata/Update", "http://synthetic.invalid/__kv_validation/ok", "//synthetic.invalid/x", "__kv_validation/../ok", "__kv_validation/ok?x=1", "%5f%5fkv_validation/ok", "__kv_validation/ok#f" }) await Throws(() => l.SendAsync(HttpMethod.Get, route)); Check(f.BorrowedSends == 0, "rejected routes never sent"); await f.Context.CloseAsync(); } static async Task RealHandlerRejected() { var f = new Fixture(); await f.Context.PrepareAsync(); await Throws(() => { f.Context.TryAcquireAccountLease(PlayerKVControlledLoadCapability.CreateForValidation(_ => new HttpClientHandler()), out _); return Task.CompletedTask; }); Check(f.BorrowedSends == 0, "real handler never sends"); await f.Context.CloseAsync(); } static async Task Drain() { var f = new Fixture(); var entered = Signal(); var release = Signal(); var cancelled = Signal(); f.Borrowed = async (_, token) => { token.Register(() => cancelled.TrySetResult(true)); entered.TrySetResult(true); await release.Task; f.Record("send-ended"); return Json("late"); }; var l = await f.PreparedLease(); var send = l.SendAsync(HttpMethod.Get, "__kv_validation/ok"); await entered.Task; var close = f.Context.CloseAsync(); await cancelled.Task; Check(!close.IsCompleted && f.SessionDisposes == 0 && f.LeaseDisposes == 0, "no premature dispose despite cancellation"); release.TrySetResult(true); var response = await send; await close; Check(!response.IsCurrent && response.Body == null, "late response withheld"); Check(string.Join(",", f.Order) == "send-ended,lease,bridge,session", "full resource order"); } static async Task Reentry() { var f = new Fixture(); var entered = Signal(); var release = Signal(); var callback = Signal(); Task reentered = null; f.Borrowed = async (_, token) => { token.Register(() => { reentered = f.Context.CloseAsync(); callback.TrySetResult(true); }); entered.TrySetResult(true); await release.Task; return Json("{}"); }; var l = await f.PreparedLease(); var send = l.SendAsync(HttpMethod.Get, "__kv_validation/ok"); await entered.Task; var close = f.Context.CloseAsync(); await callback.Task; Check(ReferenceEquals(close, reentered), "cancellation reentry same task, no cycle"); release.TrySetResult(true); await send; await close; } static async Task DisposeFailure() { var f = new Fixture { FailLeaseDispose = true }; var l = await f.PreparedLease(); var close = f.Context.CloseAsync(); await Throws(() => close); Check(f.SessionDisposes == 0 && !f.Order.Contains("bridge"), "failed borrower cleanup prevents owner disposal"); Check(ReferenceEquals(close, f.Context.CloseAsync()) && !f.Context.TryAcquireAccountLease(f.Capability(), out _), "failure cannot reopen"); var registry = new PlayerKVShutdownRegistry(); registry.Register(close, f.Context); Check(registry.Status == PlayerKVShutdownStatus.Failed, "fault retained"); var retained = (Dictionary)typeof(PlayerKVShutdownRegistry) .GetField("pending", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(registry)!; Check(ReferenceEquals(retained[close], f.Context), "failed owner retained beyond caller lifetime"); } static async Task IssuanceRace() { for (int i = 0; i < 24; i++) { var f = new Fixture(); await f.Context.PrepareAsync(); var go = Signal(); var issue = Task.Run(async () => { await go.Task; return f.Context.TryAcquireAccountLease(f.Capability(), out _); }); var close = Task.Run(async () => { await go.Task; await f.Context.CloseAsync(); }); go.TrySetResult(true); await Task.WhenAll(issue, close); Check(f.SessionDisposes == 1 && !f.Context.IsCurrent, "race closes session once"); if (issue.Result) Check(f.LeaseDisposes == 1, "published borrower observed by close"); } } static async Task FactoryRevocation() { var f = new Fixture(); await f.Context.PrepareAsync(); f.DuringFactory = () => { _ = f.Context.CloseAsync(); }; Check(!f.Context.TryAcquireAccountLease(f.Capability(), out var l) && l == null, "reentrant factory cannot publish"); await f.Context.CloseAsync(); Check(f.LeaseDisposes == 1 && f.SessionDisposes == 1, "candidate still cleaned"); } static async Task StaleResponse() { var f = new Fixture(); f.Borrowed = (_, _) => { f.Current = false; return Task.FromResult(Json("should-not-escape")); }; var l = await f.PreparedLease(); var r = await l.SendAsync(HttpMethod.Get, "__kv_validation/ok"); Check(!r.IsCurrent && r.Body == null && r.StatusCode == null, "200 does not restore old epoch"); await f.Context.CloseAsync(); } static async Task Responses() { var f = new Fixture(); var l = await f.PreparedLease(); f.Borrowed = (_, _) => Task.FromResult(Json("{}", HttpStatusCode.Unauthorized)); Check((await l.SendAsync(HttpMethod.Get, "__kv_validation/unauthorized")).StatusCode == 401, "401 evidence for version-aware caller"); f.Borrowed = (_, _) => Task.FromResult(Json("{}", HttpStatusCode.Redirect)); await Throws(() => l.SendAsync(HttpMethod.Get, "__kv_validation/ok")); await f.Context.CloseAsync(); } static async Task RegistryTimeout() { var registry = new PlayerKVShutdownRegistry(); var closing = Signal(); var deadline = Signal(); registry.Register(closing.Task); var observe = registry.ObserveAsync(10000, delay: (_, _) => deadline.Task); deadline.TrySetResult(true); Check(await observe == PlayerKVShutdownStatus.Pending && !closing.Task.IsCompleted, "timeout not force-close"); closing.TrySetResult(true); await closing.Task; Check(registry.Status == PlayerKVShutdownStatus.Ready, "real completion opens barrier"); } static async Task RegistryFailure() { var registry = new PlayerKVShutdownRegistry(); var fail = Signal(); registry.Register(fail.Task); fail.TrySetException(new IOException("synthetic")); Check(await registry.ObserveAsync(10000) == PlayerKVShutdownStatus.Failed, "fault classified"); registry.Register(Task.CompletedTask); Check(registry.Status == PlayerKVShutdownStatus.Failed, "new task cannot erase failure"); Check(!typeof(PlayerKVShutdownRegistry).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Any(m => m.Name == "Clear" || m.Name == "Reset"), "no reset API"); } static async Task RegistryRegistration() { var registry = new PlayerKVShutdownRegistry(); var first = Signal(); var second = Signal(); var deadline = Signal(); registry.Register(first.Task); var observe = registry.ObserveAsync(10000, delay: (_, _) => deadline.Task); registry.Register(second.Task); first.TrySetResult(true); deadline.TrySetResult(true); Check(await observe == PlayerKVShutdownStatus.Pending, "newly registered work not missed"); second.TrySetResult(true); } static async Task RegistryEarlyFailure() { var registry = new PlayerKVShutdownRegistry(); var fail = Signal(); var slow = Signal(); var deadline = Signal(); registry.Register(fail.Task); registry.Register(slow.Task); var observe = registry.ObserveAsync(10000, delay: (_, _) => deadline.Task); fail.TrySetException(new IOException("synthetic")); Check(await observe == PlayerKVShutdownStatus.Failed && !slow.Task.IsCompleted && !deadline.Task.IsCompleted, "failure reported without waiting for unrelated cleanup or observation deadline"); slow.TrySetResult(true); } static async Task RegistryNewFailure() { var registry = new PlayerKVShutdownRegistry(); var slow = Signal(); var deadline = Signal(); registry.Register(slow.Task); var observe = registry.ObserveAsync(10000, delay: (_, _) => deadline.Task); registry.Register(Task.FromException(new IOException("synthetic"))); Check(await observe == PlayerKVShutdownStatus.Failed && !slow.Task.IsCompleted && !deadline.Task.IsCompleted, "new root failure wakes existing observer"); slow.TrySetResult(true); } static async Task RegistryCancellation() { var registry = new PlayerKVShutdownRegistry(); var close = Signal(); registry.Register(close.Task); Check(await registry.ObserveAsync(0) == PlayerKVShutdownStatus.Input, "invalid timeout Input"); using var cts = new CancellationTokenSource(); var observed = registry.ObserveAsync(10000, cts.Token); cts.Cancel(); Check(await observed == PlayerKVShutdownStatus.Cancelled && !close.Task.IsCompleted, "observer cancellation does not cancel cleanup"); close.TrySetResult(true); } static async Task ProcessBarrier() { var firstRoot = PlayerKVShutdownRegistry.Process; var close = Signal(); firstRoot.Register(close.Task); var secondRoot = PlayerKVShutdownRegistry.Process; Check(ReferenceEquals(firstRoot, secondRoot) && secondRoot.Status == PlayerKVShutdownStatus.Pending, "new caller same process barrier"); close.TrySetResult(true); Check(await secondRoot.ObserveAsync(1000) == PlayerKVShutdownStatus.Ready, "drained across caller replacement"); } }