using game.PlayerDataStorage; partial class RouteTests { static PlayerKVEditResult Write(PlayerKVGameSessionHandle h,string k,string v,bool remote=true)=>(PlayerKVEditResult)Call("WriteKV",h,k,v,remote); static Task Flush(PlayerKVGameSessionHandle h,CancellationToken t=default)=>(Task)Call("FlushKVAsync",h,t); static Task Ticket(PlayerKVGameSessionHandle h,PlayerKVSyncTicket k,CancellationToken t=default)=>(Task)Call("WaitForKVTicketAsync",h,k,t); static TaskCompletionSource Gate()=>new(TaskCreationOptions.RunContinuationsAsynchronously); static async Task More(){ await Run("C2-write-localonly-and-existing-pending",LocalOnly); await Run("C2-input-and-guard-priority",Guards); await Run("C2-real-storage-error-cleanup",Storage); await Run("C2-conflict-and-unknown-read",NonReady); await Run("C2-superseded-and-watermark",Watermark); await Run("C2-response-lost-and-401",TransportFaults); await Run("C2-cancelled-wait-does-not-cancel-drain",CancelDrain); await Run("C2-ticket-cancellation-and-foreign-owner",TicketOwnership); await Run("C2-stale-cross-account-and-same-account",Stale); await Run("C2-close-versus-write-admission",CloseRace); await Run("C2-unexpected-exception-not-disguised",Unexpected); await Run("C3-internal-static-surface",Surface); } static async Task LocalOnly(){ await using var f=new Fixture();var h=await f.Open(); var remote=Write(h,"k","remote");Check(remote.Accepted&&remote.Ticket!=null&&f.Fake.Writes==0,"accepted locally only: "+remote.StateResult.Error+" uncertain="+remote.StateResult.LocalCommitMayHaveSucceeded+" cache="+f.Cache); var local=Write(h,"k","local",false);Check(local.Accepted&&local.Ticket==null,"localonly no ticket"); ReadIs(Read(h,"k"),"None",true,"local");await Flush(h); Check(f.Fake.Remote["101"]["k"]=="remote","localonly does not erase remote pending");ReadIs(Read(h,"k"),"None",true,"local"); Check((await Ticket(h,remote.Ticket)).Status==PlayerKVTicketStatus.Confirmed,"original remote ticket confirmed"); Check(Write(h,"k","newRemote").Accepted,"remote after localonly");ReadIs(Read(h,"k"),"None",true,"newRemote");await Flush(h); Check(f.Fake.Remote["101"]["k"]=="newRemote","remote clears localonly shadow"); Check(!Write(h,"k",null).Accepted&&Write(h,"","").StateResult.Error==PlayerKVChangeError.Validation,"invalid values"); Check(Write(h,"k","").Accepted,"empty string is value");await Flush(h);Check(f.Fake.Remote["101"].ContainsKey("k")&&f.Fake.Remote["101"]["k"]=="","not deleted"); } static async Task Guards(){ Check(Write(null,"k","v").StateResult.Error==PlayerKVChangeError.Validation,"null write validation"); Check((await Flush(null)).TransportError==PlayerKVError.Validation,"null flush validation"); Check((await Ticket(null,null)).TransportError==PlayerKVError.Validation,"null wait validation"); await using var f=new Fixture();var h=await f.Open();Check(PlayerKVGameLoadGuard.TryEnter(out var g),"own guard"); try{ ReadIs(Read(h,"k"),"LoadGuardActive",null,null); Check(Write(null,null,null).StateResult.Error==PlayerKVChangeError.Blocked,"guard before input"); var flush=await Flush(h);Check(flush.Blocked==1&&flush.Confirmed==0&&flush.Unknown==0&&flush.Superseded==0&&flush.TransportError==PlayerKVError.Cancelled,"guard flush"); Check((await Ticket(h,null)).Status==PlayerKVTicketStatus.Blocked,"guard wait"); Check(g.Violations==4&&g.Count(PlayerKVGuardPoint.ReadLocal)==1&&g.Count(PlayerKVGuardPoint.Update)==1&&g.Count(PlayerKVGuardPoint.Sync)==2,"guard exact counters"); Check(f.Fake.Writes==0&&f.Fake.Gets==1,"no network when guarded"); }finally{g.Dispose();} Check(UnityEngine.PlayerPrefs.Calls==0&&PlayFab.PlayFabClientAPI.Calls==0,"no legacy side effects"); } static async Task Storage(){ await using var f=new Fixture();var h=await f.Open();var temporary=Directory.GetFiles(f.Cache,"state.json",SearchOption.AllDirectories).Single()+".tmp"; Check(Path.GetFullPath(temporary).StartsWith(Path.GetFullPath(f.Cache)+Path.DirectorySeparatorChar,StringComparison.OrdinalIgnoreCase),"own temporary path"); Directory.CreateDirectory(temporary); try{ var result=Write(h,"k","fault");Check(!result.Accepted&&result.StateResult.Error==PlayerKVChangeError.StorageError,"real IO storage failure"); Check(!result.StateResult.LocalCommitMayHaveSucceeded&&result.StateResult.AttemptSequence>0&&result.StateResult.Revision>0&&result.Ticket==null,"original storage fields"); ReadIs(Read(h,"k"),"NotReady",null,null);Check(f.Fake.Writes==0,"no storage retry/send"); }finally{Directory.Delete(temporary,false);Check(!Directory.Exists(temporary),"P3-1 no fault directory remains");} } static async Task NonReady(){ foreach(var unknown in new[]{false,true}){ await using var f=new Fixture();var seeded=PlayerKVSyncState.Open(f.Cache,Api,"101");Check(seeded.Success,"seed cache"); using(var state=seeded.State){state.Reconcile(new Dictionary{{"k","base"}});state.Update("k","pending");if(unknown){state.BeginBatch(long.MaxValue,out var batch);state.FailBatch(batch.Id,true);}} f.Fake.Remote["101"]["k"]="conflicting";var h=await f.Open();Check(h.Status==(unknown?PlayerKVSyncStatus.UnknownOutcome:PlayerKVSyncStatus.Conflict),"nonready actual state"); ReadIs(Read(h,"k"),"NotReady",null,null);Check(!Write(h,"k","blocked").Accepted,"notready blocks write"); } } static async Task Watermark(){ await using var f=new Fixture();var h=await f.Open();var first=Write(h,"k","v1");var second=Write(h,"k","v2");var summary=await Flush(h); Check((await Ticket(h,first.Ticket)).Status==PlayerKVTicketStatus.Superseded,"superseded not confirmed");Check((await Ticket(h,second.Ticket)).Status==PlayerKVTicketStatus.Confirmed,"new ticket confirmed");Check(summary.Superseded==1&&summary.Confirmed==1,"flush counts"); var entered=Gate();var release=Gate();f.Fake.Before=async(r,t)=>{if(r.RequestUri.AbsolutePath.EndsWith("Set")){entered.TrySetResult(true);await release.Task;}}; var a=Write(h,"k","before");var flush=Flush(h);await entered.Task.WaitAsync(TimeSpan.FromSeconds(10));var b=Write(h,"k","after");release.TrySetResult(true);await flush; Check(f.Fake.Remote["101"]["k"]=="before","old watermark excludes later edit");Check((await Ticket(h,a.Ticket)).Status==PlayerKVTicketStatus.Confirmed,"first dispatched ticket"); await Flush(h);Check((await Ticket(h,b.Ticket)).Status==PlayerKVTicketStatus.Confirmed&&f.Fake.Remote["101"]["k"]=="after","next watermark confirms later ticket"); } static async Task TransportFaults(){ foreach(var unauthorized in new[]{false,true}){ await using var f=new Fixture();var h=await f.Open();f.Fake.Unauthorized=unauthorized;f.Fake.DropResponse=!unauthorized; var edit=Write(h,"k","attempt");var result=await Flush(h);var ticket=await Ticket(h,edit.Ticket); Check(ticket.Status==PlayerKVTicketStatus.Unknown&&result.Unknown>0,"uncertain transport preserved"); Check(f.Fake.Writes==1,"no automatic resend");Check(!Read(h,"k").Success&&Read(h,"k").Found==null,"failure not default save"); if(!unauthorized)Check(f.Fake.Remote["101"]["k"]=="attempt","lost response after remote applied"); } } static async Task CancelDrain(){ await using var f=new Fixture();var h=await f.Open();var entered=Gate();var release=Gate(); f.Fake.Before=async(r,t)=>{if(r.RequestUri.AbsolutePath.EndsWith("Set")){entered.TrySetResult(true);await release.Task;}}; Write(h,"k","held");using var c=new CancellationTokenSource();var wait=Flush(h,c.Token);await entered.Task.WaitAsync(TimeSpan.FromSeconds(10));c.Cancel(); var result=await wait;Check(result.TransportError==PlayerKVError.Cancelled,"cancel observer"); var close=f.Bridge.CloseAsync();try{Check(!close.IsCompleted,"close waits ignored cancellation transport");Check(!Write(h,"k","late").Accepted,"synchronous close revocation");}finally{release.TrySetResult(true);} await close.WaitAsync(TimeSpan.FromSeconds(10));Check(f.Fake.Writes==1,"no retry from cancelled observer"); await using var f2=new Fixture();var h2=await f2.Open();var edit=Write(h2,"k","pre-cancel");using var pre=new CancellationTokenSource();pre.Cancel(); var cancelled=await Flush(h2,pre.Token);Check(cancelled.TransportError==PlayerKVError.Cancelled,"pre-cancel observer");await f2.Bridge.CloseAsync();Check(f2.Fake.Writes<=1,"pre-cancel no retry, may dispatch"); } static async Task TicketOwnership(){ await using var f=new Fixture();var h=await f.Open();var edit=Write(h,"k","v");using var c=new CancellationTokenSource();c.Cancel(); Check((await Ticket(h,edit.Ticket,c.Token)).Status==PlayerKVTicketStatus.Cancelled&&f.Fake.Writes==0,"ticket cancellation no flush"); Check((await Ticket(h,null)).TransportError==PlayerKVError.Validation,"null ticket"); await using var other=new Fixture();var otherHandle=await other.Open();Check((await Ticket(otherHandle,edit.Ticket)).TransportError==PlayerKVError.Validation,"foreign owner same player/epoch"); await Flush(h);Check((await Ticket(h,edit.Ticket)).Status==PlayerKVTicketStatus.Confirmed,"cancel did not erase ticket"); } static async Task Stale(){ foreach(var nextId in new[]{"101","102"}){ await using var f=new Fixture();var a=await f.Open();var edit=Write(a,"k","A");await Flush(a);Check((await Ticket(a,edit.Ticket)).Status==PlayerKVTicketStatus.Confirmed,"A historical confirmed"); await f.Bridge.CloseAsync();f.Current++;var b=await f.Open(nextId);var expected=(string)Read(b,"k").Value;var writes=f.Fake.Writes;var gets=f.Fake.Gets; ReadIs(Read(a,"k"),"StaleOrClosing",null,null);Check(!Write(a,"k","bad").Accepted,"old write blocked");Check((await Flush(a)).Blocked>0,"old flush blocked"); Check((await Ticket(b,edit.Ticket)).Status==PlayerKVTicketStatus.Blocked,"A ticket not B authority");Check((await Ticket(a,edit.Ticket)).Status==PlayerKVTicketStatus.Blocked,"old handle cannot observe as current"); var businessMemory=expected;var lateRead=Read(a,"k");if(lateRead.Success)businessMemory="late mutation"; Check(businessMemory==expected,"sample callback rejects stale authority before changing shared memory"); ReadIs(Read(b,"k"),"None",true,expected);Check(f.Fake.Writes==writes&&f.Fake.Gets==gets,"B requests and cache unchanged");Check(a.OwnerInstanceId!=b.OwnerInstanceId&&a.Epoch!=b.Epoch,"new owner and epoch"); } } static async Task CloseRace(){ for(int i=0;i<16;i++){ await using var f=new Fixture();var h=await f.Open();var begin=Gate();var edit=Task.Run(async()=>{await begin.Task;return Write(h,"k","race");});var close=Task.Run(async()=>{await begin.Task;await f.Bridge.CloseAsync();}); var read=Task.Run(async()=>{await begin.Task;return (object)Read(h,"k");}); begin.SetResult(true);await Task.WhenAll(edit,close,read);Check(edit.Result.Accepted||edit.Result.StateResult.Error==PlayerKVChangeError.Blocked,"accepted on A or refused, no false compensation"); dynamic observed=read.Result;Check(observed.Error==PlayerKVKeyReadError.StaleOrClosing || (observed.Success && observed.Found==true && (observed.Value=="base" || observed.Value=="race")),"read racing close observes only admitted A snapshot or stale"); Check(!Write(h,"k","after").Accepted&&f.Fake.Writes==0,"late writes refused/no network"); } } static async Task Unexpected(){ await using var f=new Fixture();var h=await f.Open();f.ThrowCurrent=true; try{foreach(var action in new Action[]{()=>Read(h,"k"),()=>Write(h,"k","v"),()=>Flush(h),()=>Ticket(h,null)}){ bool threw=false;try{action();}catch(InvalidOperationException e){threw=e.Message=="current-predicate-fault";}Check(threw,"unexpected fault must not become default/false success"); }}finally{f.ThrowCurrent=false;} } static Task Surface(){ foreach(var name in new[]{"ReadKV","WriteKV","FlushKVAsync","WaitForKVTicketAsync"}){ var m=typeof(PlayFabMgr).GetMethod(name,System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Static); Check(m!=null&&m.IsAssembly&&m.IsStatic,"internal static ordinary method "+name); } Check(typeof(PlayFabMgr).Assembly.GetType("game.PlayerDataStorage.PlayerKVSaveRouteEditorValidationHost")==null,"editor host absent from service/player probe"); Check(typeof(PlayFabMgr).GetField("_instance",System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.NonPublic).GetValue(null)==null,"never instantiated PF component"); return Task.CompletedTask; } }