保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
136 lines
8.0 KiB
C#
136 lines
8.0 KiB
C#
using game;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Net;
|
|
|
|
#if UNITY_EDITOR
|
|
static partial class Probe
|
|
{
|
|
sealed class IdentityWireCapture
|
|
{
|
|
internal readonly List<(string Path, string Body)> Requests = new();
|
|
internal HttpStatusCode NextStatus = HttpStatusCode.OK;
|
|
internal string NextBody = "{\"Code\":1}";
|
|
internal Task<HttpResponseMessage> Respond(string path, string body)
|
|
{
|
|
Requests.Add((path, body));
|
|
var result = new HttpResponseMessage(NextStatus) { Content = new StringContent(NextBody) };
|
|
NextStatus = HttpStatusCode.OK; NextBody = "{\"Code\":1}";
|
|
return Task.FromResult(result);
|
|
}
|
|
internal bool TryHandle(HttpRequestMessage request, out Task<HttpResponseMessage> response)
|
|
{
|
|
response = null;
|
|
var path = request.RequestUri.AbsolutePath;
|
|
if (!new[] { "/friend/AddFriend", "/friend/DeleteFriend", "/friend/SearchFriend", "/friend/AcceptFriend",
|
|
"/friend/GetHomePage", "/friend/GetRecommendedFriends", "/friend/GetInvitedList",
|
|
"/friend/IsFriendWaitingForAccept" }.Contains(path)) return false;
|
|
Check(request.RequestUri.GetLeftPart(UriPartial.Authority) == "http://s101.local:18080" && request.Method == HttpMethod.Post,
|
|
"original friend route uses fixed official origin through in-memory transport");
|
|
response = Respond(path, request.Content.ReadAsStringAsync().GetAwaiter().GetResult());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class IdentityPFTransport : System.Reflection.DispatchProxy
|
|
{
|
|
public Func<string, string, Task<string>> Send;
|
|
protected override object Invoke(System.Reflection.MethodInfo method, object[] args)
|
|
{
|
|
if (method.Name != "FriendRequest") throw new Exception("Unexpected PF fixture transport: " + method.Name);
|
|
return Send("/friend/" + args[0], Newtonsoft.Json.JsonConvert.SerializeObject(args[1]));
|
|
}
|
|
}
|
|
sealed class IdentityPFUser : PlayFabUserService { public IdentityPFUser() { UserId = "ABCD1234"; } }
|
|
|
|
static async Task IdentityCompatibility(IUserService user, ICustomServerMgr server, IdentityWireCapture wire, string other)
|
|
{
|
|
GContext.container.Values[typeof(PlayerData)] = new PlayerData { lv = 10 };
|
|
var friend = new FriendService { userService = user, customServerMgr = server };
|
|
friend.FriendList.Add(new LeadboardItemData { playFabId = other });
|
|
var initialSDK = Effects.SDK;
|
|
JObject Last(string action)
|
|
{
|
|
var last = wire.Requests[^1];
|
|
Check(last.Path == "/friend/" + action, "original route retained: " + action);
|
|
return JObject.Parse(last.Body);
|
|
}
|
|
void Fields(JObject value, params string[] fields) => Check(value.Properties().Select(p => p.Name).OrderBy(x=>x)
|
|
.SequenceEqual(fields.OrderBy(x=>x)), "original field names retained, no userId alias added");
|
|
|
|
wire.NextBody = "{\"Code\":-3,\"Msg\":\"synthetic compatibility failure\"}";
|
|
var add = JObject.Parse(await friend.AddFriend(other));
|
|
var request = Last("AddFriend"); Fields(request, "MySelf", "MyFriendCount", "FriendPlayFabId");
|
|
Fields((JObject)request["MySelf"], "PlayFabId", "DisplayName", "AvatarUrl", "Level");
|
|
Check((string)request["MySelf"]["PlayFabId"] == user.UserId && (string)request["FriendPlayFabId"] == other,
|
|
"self uses authenticated identity and friend retains the distinct target identity");
|
|
Check((int)request["MySelf"]["Level"] == 10 && (int)request["MyFriendCount"] == 1 && (int)add["Code"] == -3,
|
|
"original data and server business failure are returned unchanged");
|
|
|
|
wire.NextBody = "{\"Code\":1,\"Info\":{\"PlayFabId\":\"" + other + "\",\"Level\":12}}";
|
|
var search = JObject.Parse(await friend.SearchFriend(other));
|
|
request = Last("SearchFriend"); Fields(request, "PlayFabId");
|
|
Check((string)request["PlayFabId"] == other && (string)search["Info"]["PlayFabId"] == other,
|
|
"target UserID round trips in legacy PlayFabId field without substituting self");
|
|
await friend.AcceptFriend(2, user.UserId, other);
|
|
request = Last("AcceptFriend"); Fields(request, "Status", "PlayFabId", "FriendPlayFabId");
|
|
Check((int)request["Status"] == 2 && (string)request["PlayFabId"] == user.UserId && (string)request["FriendPlayFabId"] == other,
|
|
"original accept/ignore status and two identities stay distinct");
|
|
await friend.GetHomePage(other, 3);
|
|
request = Last("GetHomePage"); Fields(request, "PlayFabId", "PageNo");
|
|
Check((string)request["PlayFabId"] == other && (int)request["PageNo"] == 3, "home-page target and page retained");
|
|
await friend.GetRecommendedFriends(user.UserId, 4);
|
|
request = Last("GetRecommendedFriends"); Fields(request, "PlayFabId", "PageNo");
|
|
Check((string)request["PlayFabId"] == user.UserId && (int)request["PageNo"] == 4, "recommendation self identity and page retained");
|
|
await friend.IsFriendWaitingForAccept(user.UserId, other);
|
|
request = Last("IsFriendWaitingForAccept"); Fields(request, "PlayFabId", "FriendPlayFabId");
|
|
Check((string)request["PlayFabId"] == user.UserId && (string)request["FriendPlayFabId"] == other,
|
|
"invitation status uses self and target without conversion");
|
|
await friend.GetInvitedList();
|
|
Check(wire.Requests[^1] == ("/friend/GetInvitedList", "\"\""), "cookie-authenticated original empty body remains unchanged");
|
|
|
|
wire.NextBody = "{\"Code\":-1}";
|
|
Check(!await friend.DeleteFriend(other) && friend.FriendList.Count == 1, "business failure retains original friend list");
|
|
request = Last("DeleteFriend"); Fields(request, "FriendPlayFabId");
|
|
Check((string)request["FriendPlayFabId"] == other, "delete targets the supplied UserID");
|
|
Check(await friend.DeleteFriend(other) && friend.FriendList.Count == 0, "original successful delete updates list");
|
|
foreach (var id in new[] { user.UserId, other })
|
|
{
|
|
var invitation = friend.GetInvitationCodeByPlayFabID(id).Replace("-", "");
|
|
Check(friend.DecryptInvitationCodeToUlong(invitation).ToString("X") == id,
|
|
"original invitation codec round trips identity string " + id);
|
|
}
|
|
Check(Effects.SDK == initialSDK, "ordinary friend requests do not call PlayFab SDK");
|
|
if (user is KVUserService)
|
|
{
|
|
wire.NextStatus = HttpStatusCode.InternalServerError;
|
|
Check(await friend.SearchFriend(other) == null, "HTTP failure retains original null result, no fake success");
|
|
Check(user.IsAuthorized, "ordinary HTTP 500 does not invent logout");
|
|
}
|
|
Pass("identity-legacy-fields-real-friend-service-" + user.UserId);
|
|
}
|
|
|
|
static async Task IdentityPFCompatibility()
|
|
{
|
|
var wire = new IdentityWireCapture();
|
|
var server = System.Reflection.DispatchProxy.Create<ICustomServerMgr, IdentityPFTransport>();
|
|
((IdentityPFTransport)(object)server).Send = async (path, body) =>
|
|
await (await wire.Respond(path, body)).Content.ReadAsStringAsync();
|
|
await IdentityCompatibility(new IdentityPFUser(), server, wire, "FE123456");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Only unused UI/config dependencies of the linked real FriendService are fixtures.
|
|
namespace game
|
|
{
|
|
public class FriendAddItemData { }
|
|
public class FriendAddPanelBackendOperation
|
|
{
|
|
public void StartInvite(string id, Action<FriendAddPanelBackendOperation, string, int> callback) =>
|
|
throw new NotSupportedException("UI operation is outside this protocol test.");
|
|
public void Clear() { }
|
|
}
|
|
public class LeadboardItemData { public string playFabId, displayName, avatarUrl; public DateTime? LastLogin; public int value, position, rank;
|
|
public void SetData(PlayFab.ClientModels.PlayerLeaderboardEntry entry) => throw new NotSupportedException("Leaderboard reads are outside this statistics-write test."); }
|
|
}
|