using game.PlayerDataStorage; using Newtonsoft.Json; static partial class Tests { static async Task RunShopSchemaTests() { await Run("M04-all-fields-strict-and-missing",() => { var daily="{\"ID\":1,\"index\":2,\"lastID\":3,\"time\":\"raw\"}"; var raw=new Dictionary { ["ShopPackBuyCount"]="{\"x\":1}",["ShopAllPackBuyCount"]="{\"2\":3}",["ShopGiftBuyCount"]="{\"4\":5}", ["Voucher"]="6",["ChainPackDataNew"]=daily,["TurntableAdData"]=daily, ["VIPAdPackData"]="{\"ID\":7,\"index\":8,\"day\":9,\"cdTime\":\"raw\",\"isOpen\":true}", ["BuyData"]="[1.5,2.5]",["AdvertAllCount"]="{\"-2\":10}",["DiamondShopRefreshCount"]="11", ["DiamondShopRefreshTime"]="2026-09-23T00:00:00.0000000Z", ["DiamondShopItemRecords"]="{\"12\":{\"ItemID\":13,\"Discount\":0.5,\"hasBought\":1}}", ["BlackFridayCouponDate"]="2026,9,23,1,2,3,14,15,1" }; var a=PlayerKVSaveSchema.DecodeShop(raw,SchemaSnapshot(),new()); Check(a!=null && a.Daily["x"]==1 && a.All[2]==3 && a.Gift[4]==5 && a.Voucher==6,"counts"); Check(a.Chain.Id==1 && a.Chain.Index==2 && a.Chain.LastId==3 && a.Chain.Time=="raw" && a.Turntable.LastId==3,"daily"); Check(a.Vip.Id==7 && a.Vip.Index==8 && a.Vip.Day==9 && a.Vip.Open && a.Vip.Time=="raw","advert"); Check(a.Buy.SequenceEqual(new[]{1.5f,2.5f}) && a.Advert[-2]==10 && a.RefreshCount==11 && a.RefreshTime.Year==2026,"saved shop fields"); Check(a.Items[12].Id==13 && a.Items[12].Discount==.5f && a.Items[12].Bought==1 && a.Coupon.EventId==14 && a.Coupon.RedirectId==15 && a.Coupon.Bought,"nested values"); foreach(var key in raw.Keys) { var bad=new Dictionary(raw) { [key]="null" }; Check(PlayerKVSaveSchema.DecodeShop(bad,SchemaSnapshot(),new())==null,"present bad key rejects"); bad[key]=null; Check(PlayerKVSaveSchema.DecodeShop(bad,SchemaSnapshot(),new())==null,"present null rejects"); } var errors=new PlayerKVSchemaDiagnostics(); Check(PlayerKVSaveSchema.DecodeShop(raw.ToDictionary(p=>p.Key,p=>"null"),SchemaSnapshot(),errors)==null && errors.TotalErrors==13,"all fields diagnosed"); var b=PlayerKVSaveSchema.DecodeShop(new Dictionary(),SchemaSnapshot(),new()); Check(b.Daily.Count==0 && b.All.Count==0 && b.Gift.Count==0 && b.Advert.Count==0 && b.Items.Count==0 && b.Buy.Count==0 && b.Chain==null && b.Turntable==null && b.Vip==null && b.Voucher==0 && b.RefreshCount==0 && b.RefreshTime==default && b.Coupon.ActivateTime==default && !b.Coupon.Bought,"missing clears all"); foreach(var coupon in new[] { "2026,9,23,1,2,3,1,2", "2026,2,30,1,2,3,1", "2026,9,23,1,2,3,1,2,2", "2026,9,23,1,2,3,1,2,1,0" }) Check(PlayerKVSaveSchema.DecodeShop(new Dictionary{["BlackFridayCouponDate"]=coupon},SchemaSnapshot(),new())==null,"bad coupon shape/date/bool"); b=PlayerKVSaveSchema.DecodeShop(new Dictionary{["BlackFridayCouponDate"]="2026,9,23,1,2,3,1"},SchemaSnapshot(),new()); Check(b.Coupon.EventId==1 && b.Coupon.RedirectId==0 && !b.Coupon.Bought,"legacy seven-slot coupon"); foreach(var key in new[] { "BuyData","DiamondShopItemRecords" }) Check(PlayerKVSaveSchema.DecodeShop(new Dictionary{[key]=key=="BuyData" ? "[1e999]" : "{\"1\":null}"},SchemaSnapshot(),new())==null,"invalid nested numeric"); return Task.CompletedTask; }); } }