feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System.Reflection;
|
||||
|
||||
static partial class Tests
|
||||
{
|
||||
static async Task RunCP2ARuntimeSelectionTests()
|
||||
{
|
||||
await Run("CP2A-backend-capture-and-approved-existing-cohort", CP2ASelection);
|
||||
}
|
||||
|
||||
static Task CP2ASelection()
|
||||
{
|
||||
// A wrong branch, a widened cohort, or a mutable backend would break this boundary.
|
||||
var selectionType = typeof(Tests).Assembly.GetType("game.PlayerDataStorage.PlayerKVRuntimeSelection");
|
||||
Check(selectionType != null, "formal runtime selection must exist in the real production assembly");
|
||||
var capture = selectionType.GetMethod("Capture", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Check(capture != null, "capture API");
|
||||
object Take(string backend, string api, bool agg) => capture.Invoke(null, new object[] { backend, api, agg });
|
||||
var pf = Take(null, "http://unused.invalid/", false);
|
||||
Check((bool)selectionType.GetProperty("IsKV", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pf) == false,
|
||||
"missing backend keeps PF default without applying KV URL rules");
|
||||
var kv = Take("kv", "http://s101.local:18080/", false);
|
||||
Check((bool)selectionType.GetProperty("IsKV", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(kv),
|
||||
"exact KV backend and address admitted");
|
||||
var admit = selectionType.GetMethod("TryAdmitPlayer", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Check(admit != null, "cohort admission API");
|
||||
bool Allowed(string id, out string canonical)
|
||||
{
|
||||
var args = new object[] { id, null };
|
||||
var accepted = (bool)admit.Invoke(kv, args);
|
||||
canonical = (string)args[1];
|
||||
return accepted;
|
||||
}
|
||||
Check(Allowed("00101", out var first) && first == "101", "101 uses canonical decimal identity");
|
||||
Check(Allowed("102", out var second) && second == "102", "102 admitted");
|
||||
foreach (var id in new[] { "100", "103", "0", "-101", "+101", " 101", "", "9223372036854775808" })
|
||||
Check(!Allowed(id, out _), "unapproved or malformed account denied: " + id);
|
||||
foreach (var bad in new[] {
|
||||
new object[] { "KV", "http://s101.local:18080/", false },
|
||||
new object[] { "other", "http://s101.local:18080/", false },
|
||||
new object[] { "kv", "http://s101.local:18080", false },
|
||||
new object[] { "kv", "http://other.local:18080/", false },
|
||||
new object[] { "kv", "http://s101.local:18080/", true }
|
||||
})
|
||||
{
|
||||
try { capture.Invoke(null, bad); throw new InvalidOperationException("invalid capture accepted"); }
|
||||
catch (TargetInvocationException ex) when (ex.InnerException is ArgumentException or InvalidOperationException)
|
||||
{ Check(true, "invalid capture rejected"); }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user