using game.PlayerDataStorage; using Newtonsoft.Json; using Newtonsoft.Json.Linq; static partial class Tests { static JObject PartnerFixture() => JObject.FromObject(new { FirstOpen=false, EventId=18, StartTime="2026-09-23T00:00:00.0000000Z", EndTime="2026-09-24T00:00:00.0000000Z", CurrentPoints=1,CurrentMiningProgress=2,Token=3,InvitedCount=4,TicketCount=5, CompletedNodesList=new[]{new[]{6,7},Array.Empty()},BuilderPartnerScore=new[]{new{Item1=8,Item2=9}}, PartnerBonus=new{Item1=1.25f,Item2="2026-09-23T01:00:00.0000000Z"},FreeSpinNum=10, Bots=new Dictionary{[11]=new{Id="synthetic-bot",ScorePlayer=12,ScoreBot=13,Grade=14,TargetScore=15,WakeStartTime="2026-09-23T02:00:00.0000000Z",WakingSeconds=16}}, IsRecvFinalReward=true,UIFinishedPartners=new[]{true,false},IsFinished=true,SpinState=2,InflationRate=2.5f, IsEnableAutoMatch=true,LastAutoMatchTime="2026-09-23T03:00:00.0000000Z" }); static PlayerKVPartnerState PartnerDecode(JObject o,PlayerKVSchemaDiagnostics errors=null) => PlayerKVSaveSchema.DecodePartner( new Dictionary{["EventPartnerGatherData"]=o.ToString(Formatting.None)},SchemaSnapshot(),errors ?? new()); static async Task RunPartnerSchemaTests() { await Run("M18-all-fields-missing-deep-copy",()=> { var o=PartnerFixture(); var a=PartnerDecode(o); Check(a.Present && !a.FirstOpen && a.EventId==18 && a.StartTime.Day==23 && a.EndTime.Day==24,"identity and dates"); Check(a.CurrentPoints==1 && a.CurrentMiningProgress==2 && a.Token==3 && a.InvitedCount==4 && a.TicketCount==5,"counts"); Check(a.CompletedNodesList[0].SequenceEqual(new[]{6,7}) && a.CompletedNodesList[1].Count==0 && a.BuilderPartnerScore[0]==(8,9),"nested nodes and tuple"); Check(a.BonusPercent==1.25f && a.BonusExpireUtc.Hour==1 && a.FreeSpinNum==10,"bonus tuple"); var b=a.Bots[11]; Check(b.Id=="synthetic-bot" && b.ScorePlayer==12 && b.ScoreBot==13 && b.Grade==14 && b.TargetScore==15 && b.WakeStartTime.Hour==2 && b.WakingSeconds==16,"all bot data"); Check(a.IsRecvFinalReward && a.UIFinishedPartners.SequenceEqual(new[]{true,false}) && a.IsFinished && a.SpinState==2 && a.InflationRate==2.5f && a.IsEnableAutoMatch && a.LastAutoMatchTime.Hour==3,"remaining scalar/list fields"); o["CompletedNodesList"][0][0]=999; ((JObject)o["Bots"]).RemoveAll(); Check(a.CompletedNodesList[0][0]==6 && a.Bots.Count==1,"detached input"); var missing=PlayerKVSaveSchema.DecodePartner(new Dictionary(),SchemaSnapshot(),new()); Check(!missing.Present && missing.Bots.Count==0 && missing.CompletedNodesList.Count==0 && missing.BuilderPartnerScore.Count==0 && missing.UIFinishedPartners.Count==0,"missing is empty"); return Task.CompletedTask; }); await Run("M18-present-strict-fields-and-nested-values",()=> { foreach(var property in PartnerFixture().Properties()) { var o=PartnerFixture(); o.Remove(property.Name); Check(PartnerDecode(o)==null,"each field required"); o=PartnerFixture(); o[property.Name]=JValue.CreateNull(); Check(PartnerDecode(o)==null,"each field rejects null"); } foreach(var path in new[]{"PartnerBonus.Item1","PartnerBonus.Item2","BuilderPartnerScore[0].Item1","BuilderPartnerScore[0].Item2", "CompletedNodesList[0]","CompletedNodesList[0][0]","UIFinishedPartners[0]","Bots.11.Id","Bots.11.ScorePlayer","Bots.11.ScoreBot", "Bots.11.Grade","Bots.11.TargetScore","Bots.11.WakeStartTime","Bots.11.WakingSeconds"}) { var o=PartnerFixture(); o.SelectToken(path).Replace(JValue.CreateNull()); Check(PartnerDecode(o)==null,"nested null rejected"); } foreach(var spin in new[]{-1,3,int.MaxValue}) { var o=PartnerFixture(); o["SpinState"]=spin; Check(PartnerDecode(o)==null,"undefined enum rejected"); } var invalid=PartnerFixture(); invalid["PartnerBonus"]["Item2"]="not-a-date"; Check(PartnerDecode(invalid)==null,"invalid date"); invalid=PartnerFixture(); invalid["InflationRate"]=double.MaxValue; Check(PartnerDecode(invalid)==null,"nonfinite float"); invalid=PartnerFixture(); invalid["Token"]=(long)int.MaxValue+1; Check(PartnerDecode(invalid)==null,"int overflow"); foreach(var raw in new[]{"null","[]","{\"EventId\":1,\"EventId\":2}"}) { var e=new PlayerKVSchemaDiagnostics(); Check(PlayerKVSaveSchema.DecodePartner(new Dictionary{["EventPartnerGatherData"]=raw},SchemaSnapshot(),e)==null && e.Count(18,PlayerKVSchemaError.Malformed)==1,"strict root"); } return Task.CompletedTask; }); } }