using System.Threading.Tasks; using Xunit; using XWorld.Framework.Protocol; using XWorld.Server.Gateway; namespace XWorld.Server.Gateway.Tests { public class GatewayEndToEndTests { private static Frame JoinRps() => new Frame(Channel.Framework, (ushort)FrameworkOpcode.MatchRequest, new MatchRequestMsg { GameId = "rps", Version = 1 }.Encode()); [Fact] public async Task Heartbeat_IsEchoedBack() { var host = new GameServerHost(); await host.StartAsync(TestGames.Root, tickIntervalMs: 20); try { using var client = new WsTestClient(); await client.ConnectAsync(host.WsBaseUrl, pid: 7); await client.SendFrameAsync(new Frame(Channel.Framework, (ushort)FrameworkOpcode.Heartbeat, new HeartbeatMsg { ClientTimeMs = 999L }.Encode())); Frame? echo = null; for (int i = 0; i < 10; i++) { var f = await client.ReceiveFrameAsync(5000); if (f.Channel == Channel.Framework && f.Opcode == (ushort)FrameworkOpcode.Heartbeat) { echo = f; break; } } Assert.True(echo.HasValue, "未收到心跳回显"); Assert.Equal(999L, HeartbeatMsg.Decode(echo.Value.Payload).ClientTimeMs); await client.CloseAsync(); } finally { await host.StopAsync(); } } [Fact] public async Task Reconnect_ContinuesReceivingFromSameRoom() { var host = new GameServerHost(); // matchTimeoutTicks=3 → 单人后 AI 兜底成团;logicalDt=0.2 让 60s 对局慢跑;大重连窗口 await host.StartAsync(TestGames.Root, tickIntervalMs: 200, reconnectWindowTicks: 100, matchTimeoutTicks: 3, logicalDt: 0.2f); try { var client1 = new WsTestClient(); await client1.ConnectAsync(host.WsBaseUrl, pid: 42); await client1.SendFrameAsync(JoinRps()); // 收到 MatchFound 后的第一个游戏快照 bool gotSnapshot = false; for (int i = 0; i < 15 && !gotSnapshot; i++) { var f = await client1.ReceiveFrameAsync(5000); if (f.Channel == Channel.Game) gotSnapshot = true; } Assert.True(gotSnapshot, "重连前应先收到一个快照"); // 断开 await client1.CloseAsync(); client1.Dispose(); // 用同 pid 重连 using var client2 = new WsTestClient(); await client2.ConnectAsync(host.WsBaseUrl, pid: 42); // 房间仍在跑,下一个房间输出应发到 client2 bool gotAfterReconnect = false; for (int i = 0; i < 15 && !gotAfterReconnect; i++) { var f = await client2.ReceiveFrameAsync(5000); if (f.Channel == Channel.Game) gotAfterReconnect = true; else if (f.Channel == Channel.Framework && f.Opcode == (ushort)FrameworkOpcode.RoomEnd) gotAfterReconnect = true; } Assert.True(gotAfterReconnect, "重连后应继续收到房间输出(快照或 RoomEnd)"); await client2.CloseAsync(); } finally { await host.StopAsync(); } } } }