using System.Buffers.Binary; using AcDream.Core.Net.Messages; namespace AcDream.Core.Net.Tests.Messages; /// /// Campaign CC CC2: the shared 0xF643 parser both CharacterRestore and /// CharacterCreate responses consume. See /// for the pre-existing CharacterRestore-shaped coverage that must survive /// this type's promotion unchanged. /// public sealed class CharGenVerificationResponseTests { [Fact] public void Parse_Ok_PopulatesIdentityPayload() { var w = AceWireWriter.GameMessage(CharGenVerificationResponse.ResponseOpcode) .Write((uint)CharGenVerificationResponse.Code.Ok) .WriteGuid(0x5000000Bu) .WriteString16L("+NewChar") .Write(0u); CharGenVerificationResponse.Parsed parsed = CharGenVerificationResponse.Parse(w.ToArray()); Assert.Equal(1u, parsed.RawCode); Assert.Equal(CharGenVerificationResponse.Code.Ok, parsed.AsCode); Assert.True(parsed.IsOk); Assert.Equal(0x5000000Bu, parsed.Guid); Assert.Equal("+NewChar", parsed.Name); Assert.Equal(0u, parsed.SecondsGreyedOut); } [Theory] [InlineData(0u, CharGenVerificationResponse.Code.Undef)] [InlineData(2u, CharGenVerificationResponse.Code.Pending)] [InlineData(3u, CharGenVerificationResponse.Code.NameInUse)] [InlineData(4u, CharGenVerificationResponse.Code.NameBanned)] [InlineData(5u, CharGenVerificationResponse.Code.Corrupt)] [InlineData(6u, CharGenVerificationResponse.Code.DatabaseDown)] [InlineData(7u, CharGenVerificationResponse.Code.AdminPrivilegeDenied)] public void Parse_EveryNonOkCode_IsFlagOnlyWithNullTrailingFields( uint rawCode, CharGenVerificationResponse.Code expectedCode) { var w = AceWireWriter.GameMessage(CharGenVerificationResponse.ResponseOpcode) .Write(rawCode); CharGenVerificationResponse.Parsed parsed = CharGenVerificationResponse.Parse(w.ToArray()); Assert.Equal(rawCode, parsed.RawCode); Assert.Equal(expectedCode, parsed.AsCode); Assert.False(parsed.IsOk); Assert.Null(parsed.Guid); Assert.Null(parsed.Name); Assert.Null(parsed.SecondsGreyedOut); } [Fact] public void Parse_UnknownCode_NeverThrowsOnTheCast() { // A plain enum cast never throws in C# — a private-server or future // retail revision sending a code we haven't named yet must not crash // the parser. var w = AceWireWriter.GameMessage(CharGenVerificationResponse.ResponseOpcode) .Write(99u); CharGenVerificationResponse.Parsed parsed = CharGenVerificationResponse.Parse(w.ToArray()); Assert.Equal(99u, parsed.RawCode); Assert.Equal((CharGenVerificationResponse.Code)99u, parsed.AsCode); Assert.False(parsed.IsOk); } [Fact] public void Parse_WrongOpcode_Throws() { byte[] bytes = new byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(bytes, 0xDEADBEEFu); Assert.Throws(() => CharGenVerificationResponse.Parse(bytes)); } [Fact] public void Parse_TruncatedAfterCode_Throws() { var w = AceWireWriter.GameMessage(CharGenVerificationResponse.ResponseOpcode) .Write((uint)CharGenVerificationResponse.Code.Ok); Assert.Throws(() => CharGenVerificationResponse.Parse(w.ToArray())); } [Fact] public void Parse_TruncatedBeforeCode_Throws() { var w = AceWireWriter.GameMessage(CharGenVerificationResponse.ResponseOpcode); Assert.Throws(() => CharGenVerificationResponse.Parse(w.ToArray())); } [Fact] public void ResponseOpcode_MatchesCharacterRestoresResponseOpcode() { // The whole point of this type: both families collide on the exact // same wire opcode. Assert.Equal(CharacterRestore.ResponseOpcode, CharGenVerificationResponse.ResponseOpcode); } }