using System.Collections.Generic; using Xunit; using XWorld.Framework; using XWorld.Framework.Protocol; namespace XWorld.Framework.Tests { public class FrameworkMessagesTests { [Fact] public void Heartbeat_RoundTrips() { var m = new HeartbeatMsg { ClientTimeMs = 1_725_000_000_123L }; var d = HeartbeatMsg.Decode(m.Encode()); Assert.Equal(m.ClientTimeMs, d.ClientTimeMs); } [Fact] public void MatchRequest_RoundTrips() { var m = new MatchRequestMsg { GameId = "rock_paper_scissors", Version = 3, RequestId = 42 }; var d = MatchRequestMsg.Decode(m.Encode()); Assert.Equal(m.GameId, d.GameId); Assert.Equal(m.Version, d.Version); Assert.Equal(m.RequestId, d.RequestId); } [Fact] public void MatchFound_RoundTrips_WithPlayers() { var m = new MatchFoundMsg { GameId = "rps", RoomId = "room-42", Version = 3, SelfPlayerId = 1001, RequestId = 99, Players = { new PlayerInfo { PlayerId = 1001, Name = "Alice", IsAI = false, Level = 12, AppearanceType = 3, AvatarId = 77, }, new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true, Level = 1, AppearanceType = 0, AvatarId = 0, }, }, }; var d = MatchFoundMsg.Decode(m.Encode()); Assert.Equal("rps", d.GameId); Assert.Equal("room-42", d.RoomId); Assert.Equal(3, d.Version); Assert.Equal(1001, d.SelfPlayerId); Assert.Equal(99, d.RequestId); Assert.Equal(2, d.Players.Count); Assert.Equal(1001, d.Players[0].PlayerId); Assert.Equal("Alice", d.Players[0].Name); Assert.False(d.Players[0].IsAI); Assert.Equal(12, d.Players[0].Level); Assert.Equal(3, d.Players[0].AppearanceType); Assert.Equal(77, d.Players[0].AvatarId); Assert.Equal(-1, d.Players[1].PlayerId); Assert.Equal("AI", d.Players[1].Name); Assert.True(d.Players[1].IsAI); Assert.Equal(1, d.Players[1].Level); } [Fact] public void GameInput_RoundTrips() { var m = new GameInputMsg { GameOpcode = 12, Payload = new byte[] { 9, 8, 7 } }; var d = GameInputMsg.Decode(m.Encode()); Assert.Equal((ushort)12, d.GameOpcode); Assert.Equal(new byte[] { 9, 8, 7 }, d.Payload); } [Fact] public void Snapshot_RoundTrips() { var m = new SnapshotMsg { Tick = 250, StateBlob = new byte[] { 0xDE, 0xAD } }; var d = SnapshotMsg.Decode(m.Encode()); Assert.Equal(250, d.Tick); Assert.Equal(new byte[] { 0xDE, 0xAD }, d.StateBlob); } [Fact] public void RoomEnd_RoundTrips() { var m = new RoomEndMsg { WinnerPlayerId = 1001, ResultBlob = new byte[] { 1 } }; var d = RoomEndMsg.Decode(m.Encode()); Assert.Equal(1001, d.WinnerPlayerId); Assert.Equal(new byte[] { 1 }, d.ResultBlob); } [Fact] public void Error_RoundTrips() { var m = new ErrorMsg { Code = 404, Message = "version mismatch", RequestId = 7 }; var d = ErrorMsg.Decode(m.Encode()); Assert.Equal(404, d.Code); Assert.Equal("version mismatch", d.Message); Assert.Equal(7, d.RequestId); } [Fact] public void MatchControl_RoundTrips() { var m = new MatchControlMsg { RequestId = 123 }; var d = MatchControlMsg.Decode(m.Encode()); Assert.Equal(123, d.RequestId); Assert.Equal(0, MatchControlMsg.Decode(System.Array.Empty()).RequestId); } [Fact] public void MatchAssigned_RoundTrips() { var m = new MatchAssignedMsg { GameId = "rps", Version = 2, RequestId = 88 }; var d = MatchAssignedMsg.Decode(m.Encode()); Assert.Equal("rps", d.GameId); Assert.Equal(2, d.Version); Assert.Equal(88, d.RequestId); } [Fact] public void WorldChatMessages_RoundTrip() { var send = new WorldChatSendMsg { Text = "hello world" }; WorldChatSendMsg decodedSend = WorldChatSendMsg.Decode(send.Encode()); Assert.Equal("hello world", decodedSend.Text); var broadcast = new WorldChatMessageMsg { PlayerId = 42, PlayerName = "Alice", Text = "hello everyone", ServerTimeMs = 123456789L }; WorldChatMessageMsg decodedBroadcast = WorldChatMessageMsg.Decode(broadcast.Encode()); Assert.Equal(42, decodedBroadcast.PlayerId); Assert.Equal("Alice", decodedBroadcast.PlayerName); Assert.Equal("hello everyone", decodedBroadcast.Text); Assert.Equal(123456789L, decodedBroadcast.ServerTimeMs); } [Fact] public void Opcodes_AreStableValues() { // 线网兼容性:opcode 数值固定,改动等于破坏协议 Assert.Equal((ushort)1, (ushort)FrameworkOpcode.Heartbeat); Assert.Equal((ushort)2, (ushort)FrameworkOpcode.MatchRequest); Assert.Equal((ushort)3, (ushort)FrameworkOpcode.MatchFound); Assert.Equal((ushort)4, (ushort)FrameworkOpcode.GameInput); Assert.Equal((ushort)5, (ushort)FrameworkOpcode.Snapshot); Assert.Equal((ushort)6, (ushort)FrameworkOpcode.RoomEnd); Assert.Equal((ushort)7, (ushort)FrameworkOpcode.Error); Assert.Equal((ushort)16, (ushort)FrameworkOpcode.WorldChatSend); Assert.Equal((ushort)17, (ushort)FrameworkOpcode.WorldChatMessage); Assert.Equal((ushort)18, (ushort)FrameworkOpcode.MatchCancel); } [Fact] public void Message_TravelsInsideFrame() { // 端到端:DTO -> payload -> Frame -> wire -> Frame -> DTO var req = new MatchRequestMsg { GameId = "rps", Version = 1 }; var frame = new Frame(Channel.Framework, (ushort)FrameworkOpcode.MatchRequest, req.Encode()); byte[] wire = FrameCodec.Encode(frame); Frame back = FrameCodec.Decode(wire); Assert.Equal(Channel.Framework, back.Channel); Assert.Equal((ushort)FrameworkOpcode.MatchRequest, back.Opcode); var d = MatchRequestMsg.Decode(back.Payload); Assert.Equal("rps", d.GameId); Assert.Equal(1, d.Version); } [Fact] public void GameInput_NullPayload_RoundTrips_AsEmpty() { var m = new GameInputMsg { GameOpcode = 5, Payload = null }; var d = GameInputMsg.Decode(m.Encode()); Assert.Equal((ushort)5, d.GameOpcode); Assert.NotNull(d.Payload); Assert.Empty(d.Payload); } [Fact] public void Error_NullMessage_RoundTrips_AsEmpty() { var m = new ErrorMsg { Code = 7, Message = null }; var d = ErrorMsg.Decode(m.Encode()); Assert.Equal(7, d.Code); Assert.Equal(string.Empty, d.Message); } [Fact] public void MatchFound_EmptyPlayerList_RoundTrips() { var m = new MatchFoundMsg { RoomId = "r0", Version = 1, SelfPlayerId = 0 }; // Players 默认空列表,不添加任何玩家 var d = MatchFoundMsg.Decode(m.Encode()); Assert.Equal("r0", d.RoomId); Assert.Equal(1, d.Version); Assert.Equal(0, d.SelfPlayerId); Assert.NotNull(d.Players); Assert.Empty(d.Players); } [Fact] public void MatchFound_DecodesLegacyPlayersWithoutDisplayFields() { var w = new PacketWriter(); w.WriteString("legacy-room"); w.WriteVarInt(1); w.WriteVarInt(1001); w.WriteVarUInt(2); w.WriteVarInt(1001); w.WriteString("LegacyAlice"); w.WriteBool(false); w.WriteVarInt(-1); w.WriteString("AI"); w.WriteBool(true); MatchFoundMsg d = MatchFoundMsg.Decode(w.ToArray()); Assert.Equal("legacy-room", d.RoomId); Assert.Equal(0, d.RequestId); Assert.Equal(2, d.Players.Count); Assert.Equal(1001, d.Players[0].PlayerId); Assert.Equal("LegacyAlice", d.Players[0].Name); Assert.False(d.Players[0].IsAI); Assert.Equal(0, d.Players[0].Level); Assert.Equal(0, d.Players[0].AppearanceType); Assert.Equal(0, d.Players[0].AvatarId); Assert.Equal(-1, d.Players[1].PlayerId); Assert.Equal("AI", d.Players[1].Name); Assert.True(d.Players[1].IsAI); } } }