using game.PlayerDataStorage; static partial class Tests { static PlayerKVIdentitySnapshot Identity(bool fresh=false,string id="FF",PlayerKVIdentityChannel channel=PlayerKVIdentityChannel.none) => new(id,fresh,channel,Array.Empty()); static PlayerKVLoginState LoginDecode(Dictionary raw,PlayerKVIdentitySnapshot identity=null) => PlayerKVSaveSchema.DecodeLogin(raw,identity ?? Identity(),new()); static async Task RunLoginSchemaTests() { await Run("M31-MMTest-identity-boundaries",()=> { Check(LoginDecode(new(){["MMTest"]="MMFailParam#-1"}).MMTest==-1,"declared signed value retained"); Check(LoginDecode(new(){["MMTest"]="MMFailParam#12"},Identity(true,"bad-id")).Normalizations.Count==0,"existing value does not use PF id"); foreach(var text in new[]{"old#123","", "something"}) { var a=LoginDecode(new(){["MMTest"]=text}); Check(a.MMTest==0 && a.Normalizations["MMTest"]=="MMFailParam#0","old format normalized"); } Check(LoginDecode(new()).MMTest==0 && LoginDecode(new(),Identity(true)).MMTest==15,"PF newly created determines bucket"); Check(LoginDecode(new(),Identity(false,"not-hex")).MMTest==0,"old PF profile ignores id format"); foreach(var id in new[]{"",null," 1","-1","0xFF","10000000000000000","invalid"}) Check(LoginDecode(new(),Identity(true,id))==null,"invalid new PF id"); foreach(var mm in new[]{null,"MMFailParam","MMFailParam#","MMFailParam#1#2","MMFailParam#2147483648","MMFailParam#1.0"}) Check(LoginDecode(new(){["MMTest"]=mm})==null,"malformed current layout"); return Task.CompletedTask; }); await Run("M31-channels-url-balance-and-intents",async()=> { var input=new Dictionary{["hwGoogle"]="1",["hwFacebook"]="0",["Apple"]="1",["EAccoutUrl"]="{\"synthetic\":\"avatar\"}",["TapJoy_Diamond"]="-3"}; var a=LoginDecode(input,Identity(channel:PlayerKVIdentityChannel.hwVkid)); Check(a.Linked.SequenceEqual(new[]{PlayerKVIdentityChannel.hwGoogle,PlayerKVIdentityChannel.Apple,PlayerKVIdentityChannel.hwVkid}) && a.TapJoyBalance==-3 && a.AccountUrls["synthetic"]=="avatar","merged private channels, url and balance"); Check(a.Normalizations.Count==2 && a.Normalizations["hwVkid"]=="1" && !input.ContainsKey("MMTest"),"two normalized copies without input mutation"); input.Clear(); Check(a.AccountUrls.Count==1 && a.Normalizations.Count==2,"independent immutable data"); foreach(var key in new[]{"hwGoogle","hwFacebook","Apple","hwVkid"}) foreach(var value in new[]{null,"true","2","01"}) Check(LoginDecode(new(){[key]=value},Identity(channel:PlayerKVIdentityChannel.hwVkid))==null,"flags exact 0/1"); foreach(var key in new[]{"EAccoutUrl","TapJoy_Diamond"}) Check(LoginDecode(new(){[key]="null"})==null,"bad optional field not missing"); var missing=LoginDecode(new()); Check(missing.AccountUrls.Count==0 && missing.TapJoyBalance==0 && missing.Linked.Count==0,"empty state"); var intents=new PlayerKVInitializationIntents(7,PlayerKVIdentityChannel.hwVkid); intents.Add(7,"MMTest","MMFailParam#1"); intents.Add(7,"MMTest","MMFailParam#2"); intents.Add(7,"hwVkid","1"); var captured=intents.Snapshot; Check(intents.Count==2 && captured["MMTest"]=="MMFailParam#2","dedupe and last value"); await Throws(()=>{intents.Add(8,"MMTest","x");return Task.CompletedTask;}); await Throws(()=>{intents.Add(7,"other","x");return Task.CompletedTask;}); intents.Discard(); intents.Discard(); Check(intents.Discarded && intents.Count==0 && intents.DiscardedCount==2 && captured.Count==2,"discard idempotent, historical immutable view"); await Throws(()=>{intents.Add(7,"MMTest","x");return Task.CompletedTask;}); }); } }