test(upserver): 归档存档迁移与玩法闭环回归工具
This commit is contained in:
141
CodexOutput/upserver/KVGameLoadTests/ActivitySchemaTests.cs
Normal file
141
CodexOutput/upserver/KVGameLoadTests/ActivitySchemaTests.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using game.PlayerDataStorage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
static partial class Tests
|
||||
{
|
||||
static async Task RunActivitySchemaTests()
|
||||
{
|
||||
await Run("M15-activity-all-saved-fields-strict",() =>
|
||||
{
|
||||
var obj=JObject.FromObject(new { FirstOpen=true,EventId=11,RankTierLevel=11,Finished=11,RedirectID=11,Score=11,ProcessedScore=11,step=11,uiStep=11,index=11,StartTime="2026-09-23T00:00:00.0000000Z",EndTime="2026-09-23T00:00:00.0000000Z",eliminateNumber=new[] { 7,9 },robotAccountList=new[] { 7,9 },InflationRate=1.5f });
|
||||
Dictionary<string,string> Raw(JObject value)=>new() { ["FishingChallengeData"]=value.ToString(Formatting.None) };
|
||||
var state=PlayerKVSaveSchema.DecodeChallenge(Raw(obj),SchemaSnapshot(),new());
|
||||
Check(state!=null && state.IsPresent && !state.IsExplicitNull,"valid complete object, not explicit null");
|
||||
Check(state.FirstOpen==true,"typed FirstOpen");
|
||||
Check(state.EventId==11,"typed EventId");
|
||||
Check(state.RankTierLevel==11,"typed RankTierLevel");
|
||||
Check(state.Finished==11,"typed Finished");
|
||||
Check(state.RedirectID==11,"typed RedirectID");
|
||||
Check(state.Score==11,"typed Score");
|
||||
Check(state.ProcessedScore==11,"typed ProcessedScore");
|
||||
Check(state.step==11,"typed step");
|
||||
Check(state.uiStep==11,"typed uiStep");
|
||||
Check(state.index==11,"typed index");
|
||||
Check(state.StartTime==new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),"typed StartTime");
|
||||
Check(state.EndTime==new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),"typed EndTime");
|
||||
Check(state.eliminateNumber.SequenceEqual(new[] { 7,9 }),"typed eliminateNumber");
|
||||
Check(state.robotAccountList.SequenceEqual(new[] { 7,9 }),"typed robotAccountList");
|
||||
Check(state.InflationRate==1.5f,"typed InflationRate");
|
||||
foreach(var property in obj.Properties())
|
||||
{
|
||||
var bad=(JObject)obj.DeepClone(); bad.Remove(property.Name);
|
||||
Check(PlayerKVSaveSchema.DecodeChallenge(Raw(bad),SchemaSnapshot(),new())==null,"missing saved field rejects");
|
||||
bad=(JObject)obj.DeepClone(); bad[property.Name]=JValue.CreateNull();
|
||||
Check(PlayerKVSaveSchema.DecodeChallenge(Raw(bad),SchemaSnapshot(),new())==null,"null saved field rejects");
|
||||
}
|
||||
var missing = PlayerKVSaveSchema.DecodeChallenge(new Dictionary<string,string>(),SchemaSnapshot(),new());
|
||||
Check(!missing.IsPresent && !missing.IsExplicitNull,"missing separate");
|
||||
var empty = PlayerKVSaveSchema.DecodeChallenge(new Dictionary<string,string> { ["FishingChallengeData"]="null" },SchemaSnapshot(),new());
|
||||
Check(empty != null && empty.IsPresent && empty.IsExplicitNull,"explicit null is a present inactive challenge");
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
await Run("M15-explicit-null-raw-and-strict-boundaries", async () =>
|
||||
{
|
||||
foreach (var text in new[] { "null", " \r\n\tnull \t" })
|
||||
{
|
||||
var raw = ModernRaw();
|
||||
raw["FishingChallengeData"] = text;
|
||||
raw["outside-null-compat-probe"] = " untouched \r\n";
|
||||
var original = new Dictionary<string,string>(raw);
|
||||
var result = Aggregate(raw);
|
||||
Check(result.Verdict == PlayerKVSchemaVerdict.Valid, "explicit null aggregate valid");
|
||||
Check(result.Data.Module<PlayerKVChallengeState>(15).IsExplicitNull, "aggregate preserves explicit null marker");
|
||||
Check(result.Data.Raw.Count == original.Count, "raw key count unchanged");
|
||||
foreach (var pair in original)
|
||||
{
|
||||
Check(System.Text.Encoding.UTF8.GetBytes(result.Data.Raw[pair.Key]).SequenceEqual(
|
||||
System.Text.Encoding.UTF8.GetBytes(pair.Value)), "raw UTF8 bytes preserved including null whitespace");
|
||||
Check(raw[pair.Key] == pair.Value, "decode did not edit input");
|
||||
}
|
||||
raw["FishingChallengeData"] = "changed-after-decode";
|
||||
Check(result.Data.Raw["FishingChallengeData"] == text, "decoded raw is a separate copy");
|
||||
}
|
||||
Check(!Aggregate(ModernRaw()).Data.Raw.ContainsKey("FishingChallengeData"), "missing raw stays missing");
|
||||
foreach (var text in new[] { null, "", " \t", "\"null\"", "0", "true", "[]", "[null]", "{", "null null",
|
||||
"\vnull\v", "\fnull\f", "\u0085null", "\u00a0null\u00a0", "\ufeffnull", "/*x*/null", "null//x", "null,", "null,\"x\":1", "null}" })
|
||||
{
|
||||
var errors = new PlayerKVSchemaDiagnostics();
|
||||
var state = PlayerKVSaveSchema.DecodeChallenge(new Dictionary<string,string> { ["FishingChallengeData"] = text }, SchemaSnapshot(), errors);
|
||||
Check(state == null && errors.Count(15, PlayerKVSchemaError.Malformed) == 1, "only strict JSON null or complete object accepted");
|
||||
}
|
||||
await Throws<PlayerKVSchemaException>(() => { PlayerKVSaveSchema.Json("null"); return Task.CompletedTask; });
|
||||
await Throws<ArgumentException>(() =>
|
||||
{
|
||||
_ = new PlayerKVChallengeState(false,false,0,0,0,0,0,0,0,0,0,default,default,new List<int>(),new List<int>(),0,true);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
await Run("M26-activity-all-saved-fields-strict",() =>
|
||||
{
|
||||
var obj=JObject.FromObject(new { EventID=11,PinballMainID=11,SpinPoint=11,Scores=11,Multiple=11,Token=11,ConsumedToken=11,RewardIndex=11,Force=1.5f,LuckyValue=1.5f,InflationRate=1.5f });
|
||||
Dictionary<string,string> Raw(JObject value)=>new() { ["epbdk"]=value.ToString(Formatting.None) };
|
||||
var state=PlayerKVSaveSchema.DecodePinball(Raw(obj),SchemaSnapshot(),new());
|
||||
Check(state!=null && state.IsPresent,"valid complete state");
|
||||
Check(state.EventID==11,"typed EventID");
|
||||
Check(state.PinballMainID==11,"typed PinballMainID");
|
||||
Check(state.SpinPoint==11,"typed SpinPoint");
|
||||
Check(state.Scores==11,"typed Scores");
|
||||
Check(state.Multiple==11,"typed Multiple");
|
||||
Check(state.Token==11,"typed Token");
|
||||
Check(state.ConsumedToken==11,"typed ConsumedToken");
|
||||
Check(state.RewardIndex==11,"typed RewardIndex");
|
||||
Check(state.Force==1.5f,"typed Force");
|
||||
Check(state.LuckyValue==1.5f,"typed LuckyValue");
|
||||
Check(state.InflationRate==1.5f,"typed InflationRate");
|
||||
foreach(var property in obj.Properties())
|
||||
{
|
||||
var bad=(JObject)obj.DeepClone(); bad.Remove(property.Name);
|
||||
Check(PlayerKVSaveSchema.DecodePinball(Raw(bad),SchemaSnapshot(),new())==null,"missing saved field rejects");
|
||||
bad=(JObject)obj.DeepClone(); bad[property.Name]=JValue.CreateNull();
|
||||
Check(PlayerKVSaveSchema.DecodePinball(Raw(bad),SchemaSnapshot(),new())==null,"null saved field rejects");
|
||||
}
|
||||
Check(!PlayerKVSaveSchema.DecodePinball(new Dictionary<string,string>(),SchemaSnapshot(),new()).IsPresent,"missing separate");
|
||||
Check(PlayerKVSaveSchema.DecodePinball(new Dictionary<string,string> { ["epbdk"]="null" },SchemaSnapshot(),new())==null,"null not missing");
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
await Run("M27-activity-all-saved-fields-strict",() =>
|
||||
{
|
||||
var obj=JObject.FromObject(new { EventID=11,ScratchMainID=11,ChainPackID=11,Token=11,ConsumedToken=11,RewardTaskConsumedToken=11,EventScratchRewardTaskID=11,EventScratchCardID=11,TotalConsumedToken=11,ChainPackProgress=11,magIndex=11,IsOver=true,LuckyValue=1.5f,InflationRate=1.5f,StampRewardList=new[] { 7,9 },StampRewardDict=new Dictionary<int,object> { [7]=new { EventScratchCardID=8,Count=9 } } });
|
||||
Dictionary<string,string> Raw(JObject value)=>new() { ["estdk"]=value.ToString(Formatting.None) };
|
||||
var state=PlayerKVSaveSchema.DecodeScratch(Raw(obj),SchemaSnapshot(),new());
|
||||
Check(state!=null && state.IsPresent,"valid complete state");
|
||||
Check(state.EventID==11,"typed EventID");
|
||||
Check(state.ScratchMainID==11,"typed ScratchMainID");
|
||||
Check(state.ChainPackID==11,"typed ChainPackID");
|
||||
Check(state.Token==11,"typed Token");
|
||||
Check(state.ConsumedToken==11,"typed ConsumedToken");
|
||||
Check(state.RewardTaskConsumedToken==11,"typed RewardTaskConsumedToken");
|
||||
Check(state.EventScratchRewardTaskID==11,"typed EventScratchRewardTaskID");
|
||||
Check(state.EventScratchCardID==11,"typed EventScratchCardID");
|
||||
Check(state.TotalConsumedToken==11,"typed TotalConsumedToken");
|
||||
Check(state.ChainPackProgress==11,"typed ChainPackProgress");
|
||||
Check(state.magIndex==11,"typed magIndex");
|
||||
Check(state.IsOver==true,"typed IsOver");
|
||||
Check(state.LuckyValue==1.5f,"typed LuckyValue");
|
||||
Check(state.InflationRate==1.5f,"typed InflationRate");
|
||||
Check(state.StampRewardList.SequenceEqual(new[] { 7,9 }),"typed StampRewardList");
|
||||
Check(state.StampRewardDict[7].EventScratchCardID==8 && state.StampRewardDict[7].Count==9,"typed StampRewardDict");
|
||||
foreach(var property in obj.Properties())
|
||||
{
|
||||
var bad=(JObject)obj.DeepClone(); bad.Remove(property.Name);
|
||||
Check(PlayerKVSaveSchema.DecodeScratch(Raw(bad),SchemaSnapshot(),new())==null,"missing saved field rejects");
|
||||
bad=(JObject)obj.DeepClone(); bad[property.Name]=JValue.CreateNull();
|
||||
Check(PlayerKVSaveSchema.DecodeScratch(Raw(bad),SchemaSnapshot(),new())==null,"null saved field rejects");
|
||||
}
|
||||
Check(!PlayerKVSaveSchema.DecodeScratch(new Dictionary<string,string>(),SchemaSnapshot(),new()).IsPresent,"missing separate");
|
||||
Check(PlayerKVSaveSchema.DecodeScratch(new Dictionary<string,string> { ["estdk"]="null" },SchemaSnapshot(),new())==null,"null not missing");
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user