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 }; var d = MatchRequestMsg.Decode(m.Encode()); Assert.Equal(m.GameId, d.GameId); Assert.Equal(m.Version, d.Version); } [Fact] public void MatchFound_RoundTrips_WithPlayers() { var m = new MatchFoundMsg { RoomId = "room-42", Version = 3, SelfPlayerId = 1001, Players = { new PlayerInfo { PlayerId = 1001, Name = "Alice", IsAI = false }, new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true }, }, }; var d = MatchFoundMsg.Decode(m.Encode()); Assert.Equal("room-42", d.RoomId); Assert.Equal(3, d.Version); Assert.Equal(1001, d.SelfPlayerId); 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(-1, d.Players[1].PlayerId); Assert.True(d.Players[1].IsAI); } [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" }; var d = ErrorMsg.Decode(m.Encode()); Assert.Equal(404, d.Code); Assert.Equal("version mismatch", d.Message); } [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); } [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); } } }