test: add unit tests for core packages

Add comprehensive test coverage for:
- common/token: token generation and RNG tests
- common/stringsupport: string encoding, CSV operations
- common/byteframe: binary read/write operations
- common/mhfcourse: course/subscription logic
- network/crypt_packet: packet header parsing
- network/binpacket: binary packet round-trips
- network/mhfpacket: packet interface and opcode mapping
- config: configuration struct and loading
- server/entranceserver: response building
- server/signserver: response ID constants
- server/signv2server: HTTP endpoint validation
- server/channelserver: session, semaphore, and handler tests

All tests pass with race detector enabled.
This commit is contained in:
Houmgaor
2026-01-30 00:19:27 +01:00
parent 69cc84aa2f
commit e929346bf3
14 changed files with 4583 additions and 0 deletions

View File

@@ -0,0 +1,401 @@
package binpacket
import (
"bytes"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
)
func TestMsgBinTargetedOpcode(t *testing.T) {
m := &MsgBinTargeted{}
if m.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("MsgBinTargeted.Opcode() = %v, want MSG_SYS_CAST_BINARY", m.Opcode())
}
}
func TestMsgBinTargetedParseEmpty(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(0) // TargetCount = 0
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 0 {
t.Errorf("TargetCount = %d, want 0", m.TargetCount)
}
if len(m.TargetCharIDs) != 0 {
t.Errorf("TargetCharIDs len = %d, want 0", len(m.TargetCharIDs))
}
}
func TestMsgBinTargetedParseSingleTarget(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(1) // TargetCount = 1
bf.WriteUint32(0x12345678) // TargetCharID
bf.WriteBytes([]byte{0xDE, 0xAD, 0xBE, 0xEF})
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 1 {
t.Errorf("TargetCount = %d, want 1", m.TargetCount)
}
if len(m.TargetCharIDs) != 1 {
t.Errorf("TargetCharIDs len = %d, want 1", len(m.TargetCharIDs))
}
if m.TargetCharIDs[0] != 0x12345678 {
t.Errorf("TargetCharIDs[0] = %x, want 0x12345678", m.TargetCharIDs[0])
}
if !bytes.Equal(m.RawDataPayload, []byte{0xDE, 0xAD, 0xBE, 0xEF}) {
t.Errorf("RawDataPayload = %v, want [0xDE, 0xAD, 0xBE, 0xEF]", m.RawDataPayload)
}
}
func TestMsgBinTargetedParseMultipleTargets(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(3) // TargetCount = 3
bf.WriteUint32(100)
bf.WriteUint32(200)
bf.WriteUint32(300)
bf.WriteBytes([]byte{0x01, 0x02, 0x03})
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 3 {
t.Errorf("TargetCount = %d, want 3", m.TargetCount)
}
if len(m.TargetCharIDs) != 3 {
t.Errorf("TargetCharIDs len = %d, want 3", len(m.TargetCharIDs))
}
if m.TargetCharIDs[0] != 100 || m.TargetCharIDs[1] != 200 || m.TargetCharIDs[2] != 300 {
t.Errorf("TargetCharIDs = %v, want [100, 200, 300]", m.TargetCharIDs)
}
}
func TestMsgBinTargetedBuild(t *testing.T) {
m := &MsgBinTargeted{
TargetCount: 2,
TargetCharIDs: []uint32{0x11111111, 0x22222222},
RawDataPayload: []byte{0xAA, 0xBB},
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
expected := []byte{
0x00, 0x02, // TargetCount
0x11, 0x11, 0x11, 0x11, // TargetCharIDs[0]
0x22, 0x22, 0x22, 0x22, // TargetCharIDs[1]
0xAA, 0xBB, // RawDataPayload
}
if !bytes.Equal(bf.Data(), expected) {
t.Errorf("Build() = %v, want %v", bf.Data(), expected)
}
}
func TestMsgBinTargetedRoundTrip(t *testing.T) {
original := &MsgBinTargeted{
TargetCount: 3,
TargetCharIDs: []uint32{1000, 2000, 3000},
RawDataPayload: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinTargeted{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.TargetCount != original.TargetCount {
t.Errorf("TargetCount = %d, want %d", parsed.TargetCount, original.TargetCount)
}
if len(parsed.TargetCharIDs) != len(original.TargetCharIDs) {
t.Errorf("TargetCharIDs len = %d, want %d", len(parsed.TargetCharIDs), len(original.TargetCharIDs))
}
for i := range original.TargetCharIDs {
if parsed.TargetCharIDs[i] != original.TargetCharIDs[i] {
t.Errorf("TargetCharIDs[%d] = %d, want %d", i, parsed.TargetCharIDs[i], original.TargetCharIDs[i])
}
}
if !bytes.Equal(parsed.RawDataPayload, original.RawDataPayload) {
t.Errorf("RawDataPayload = %v, want %v", parsed.RawDataPayload, original.RawDataPayload)
}
}
func TestMsgBinMailNotifyOpcode(t *testing.T) {
m := MsgBinMailNotify{}
if m.Opcode() != network.MSG_SYS_CASTED_BINARY {
t.Errorf("MsgBinMailNotify.Opcode() = %v, want MSG_SYS_CASTED_BINARY", m.Opcode())
}
}
func TestMsgBinMailNotifyBuild(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "TestPlayer",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
data := bf.Data()
// First byte should be 0x01 (Unk)
if data[0] != 0x01 {
t.Errorf("First byte = %x, want 0x01", data[0])
}
// Total length should be 1 (Unk) + 21 (padded name) = 22
if len(data) != 22 {
t.Errorf("Data len = %d, want 22", len(data))
}
}
func TestMsgBinMailNotifyBuildEmptyName(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if len(bf.Data()) != 22 {
t.Errorf("Data len = %d, want 22", len(bf.Data()))
}
}
func TestMsgBinChatOpcode(t *testing.T) {
m := &MsgBinChat{}
if m.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("MsgBinChat.Opcode() = %v, want MSG_SYS_CAST_BINARY", m.Opcode())
}
}
func TestMsgBinChatTypes(t *testing.T) {
tests := []struct {
chatType ChatType
value uint8
}{
{ChatTypeLocal, 1},
{ChatTypeGuild, 2},
{ChatTypeAlliance, 3},
{ChatTypeParty, 4},
{ChatTypeWhisper, 5},
}
for _, tt := range tests {
if uint8(tt.chatType) != tt.value {
t.Errorf("ChatType %v = %d, want %d", tt.chatType, uint8(tt.chatType), tt.value)
}
}
}
func TestMsgBinChatBuildParse(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeLocal,
Flags: 0x0000,
Message: "Hello",
SenderName: "Player",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.Unk0 != original.Unk0 {
t.Errorf("Unk0 = %d, want %d", parsed.Unk0, original.Unk0)
}
if parsed.Type != original.Type {
t.Errorf("Type = %d, want %d", parsed.Type, original.Type)
}
if parsed.Flags != original.Flags {
t.Errorf("Flags = %d, want %d", parsed.Flags, original.Flags)
}
if parsed.Message != original.Message {
t.Errorf("Message = %q, want %q", parsed.Message, original.Message)
}
if parsed.SenderName != original.SenderName {
t.Errorf("SenderName = %q, want %q", parsed.SenderName, original.SenderName)
}
}
func TestMsgBinChatBuildParseJapanese(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeGuild,
Flags: 0x0001,
Message: "こんにちは",
SenderName: "テスト",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Message != original.Message {
t.Errorf("Message = %q, want %q", parsed.Message, original.Message)
}
if parsed.SenderName != original.SenderName {
t.Errorf("SenderName = %q, want %q", parsed.SenderName, original.SenderName)
}
}
func TestMsgBinChatBuildParseEmpty(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeParty,
Flags: 0x0000,
Message: "",
SenderName: "",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Message != "" {
t.Errorf("Message = %q, want empty", parsed.Message)
}
if parsed.SenderName != "" {
t.Errorf("SenderName = %q, want empty", parsed.SenderName)
}
}
func TestMsgBinChatBuildFormat(t *testing.T) {
m := &MsgBinChat{
Unk0: 0x12,
Type: ChatTypeWhisper,
Flags: 0x3456,
Message: "Hi",
SenderName: "A",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
data := bf.Data()
// Verify header structure
if data[0] != 0x12 {
t.Errorf("Unk0 = %x, want 0x12", data[0])
}
if data[1] != uint8(ChatTypeWhisper) {
t.Errorf("Type = %x, want %x", data[1], uint8(ChatTypeWhisper))
}
// Flags at bytes 2-3 (big endian)
if data[2] != 0x34 || data[3] != 0x56 {
t.Errorf("Flags = %x%x, want 3456", data[2], data[3])
}
}
func TestMsgBinChatAllTypes(t *testing.T) {
types := []ChatType{
ChatTypeLocal,
ChatTypeGuild,
ChatTypeAlliance,
ChatTypeParty,
ChatTypeWhisper,
}
for _, chatType := range types {
t.Run("", func(t *testing.T) {
original := &MsgBinChat{
Type: chatType,
Message: "Test",
SenderName: "Player",
}
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Type != chatType {
t.Errorf("Type = %d, want %d", parsed.Type, chatType)
}
})
}
}

View File

@@ -0,0 +1,234 @@
package network
import (
"bytes"
"testing"
)
func TestCryptPacketHeaderLength(t *testing.T) {
if CryptPacketHeaderLength != 14 {
t.Errorf("CryptPacketHeaderLength = %d, want 14", CryptPacketHeaderLength)
}
}
func TestNewCryptPacketHeader(t *testing.T) {
// Create a valid 14-byte header
data := []byte{
0x01, // Pf0
0x02, // KeyRotDelta
0x00, 0x03, // PacketNum
0x00, 0x04, // DataSize
0x00, 0x05, // PrevPacketCombinedCheck
0x00, 0x06, // Check0
0x00, 0x07, // Check1
0x00, 0x08, // Check2
}
header, err := NewCryptPacketHeader(data)
if err != nil {
t.Fatalf("NewCryptPacketHeader() error = %v", err)
}
if header.Pf0 != 0x01 {
t.Errorf("Pf0 = %d, want 1", header.Pf0)
}
if header.KeyRotDelta != 0x02 {
t.Errorf("KeyRotDelta = %d, want 2", header.KeyRotDelta)
}
if header.PacketNum != 0x03 {
t.Errorf("PacketNum = %d, want 3", header.PacketNum)
}
if header.DataSize != 0x04 {
t.Errorf("DataSize = %d, want 4", header.DataSize)
}
if header.PrevPacketCombinedCheck != 0x05 {
t.Errorf("PrevPacketCombinedCheck = %d, want 5", header.PrevPacketCombinedCheck)
}
if header.Check0 != 0x06 {
t.Errorf("Check0 = %d, want 6", header.Check0)
}
if header.Check1 != 0x07 {
t.Errorf("Check1 = %d, want 7", header.Check1)
}
if header.Check2 != 0x08 {
t.Errorf("Check2 = %d, want 8", header.Check2)
}
}
func TestNewCryptPacketHeaderTooShort(t *testing.T) {
tests := []struct {
name string
data []byte
}{
{"empty", []byte{}},
{"1 byte", []byte{0x01}},
{"5 bytes", []byte{0x01, 0x02, 0x03, 0x04, 0x05}},
{"13 bytes", make([]byte, 13)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewCryptPacketHeader(tt.data)
if err == nil {
t.Errorf("NewCryptPacketHeader(%v) should return error for short data", tt.data)
}
})
}
}
func TestCryptPacketHeaderEncode(t *testing.T) {
header := &CryptPacketHeader{
Pf0: 0x01,
KeyRotDelta: 0x02,
PacketNum: 0x0003,
DataSize: 0x0004,
PrevPacketCombinedCheck: 0x0005,
Check0: 0x0006,
Check1: 0x0007,
Check2: 0x0008,
}
encoded, err := header.Encode()
if err != nil {
t.Fatalf("Encode() error = %v", err)
}
if len(encoded) != CryptPacketHeaderLength {
t.Errorf("Encode() len = %d, want %d", len(encoded), CryptPacketHeaderLength)
}
expected := []byte{
0x01, // Pf0
0x02, // KeyRotDelta
0x00, 0x03, // PacketNum
0x00, 0x04, // DataSize
0x00, 0x05, // PrevPacketCombinedCheck
0x00, 0x06, // Check0
0x00, 0x07, // Check1
0x00, 0x08, // Check2
}
if !bytes.Equal(encoded, expected) {
t.Errorf("Encode() = %v, want %v", encoded, expected)
}
}
func TestCryptPacketHeaderRoundTrip(t *testing.T) {
tests := []CryptPacketHeader{
{
Pf0: 0x00,
KeyRotDelta: 0x00,
PacketNum: 0x0000,
DataSize: 0x0000,
PrevPacketCombinedCheck: 0x0000,
Check0: 0x0000,
Check1: 0x0000,
Check2: 0x0000,
},
{
Pf0: 0xFF,
KeyRotDelta: 0xFF,
PacketNum: 0xFFFF,
DataSize: 0xFFFF,
PrevPacketCombinedCheck: 0xFFFF,
Check0: 0xFFFF,
Check1: 0xFFFF,
Check2: 0xFFFF,
},
{
Pf0: 0x12,
KeyRotDelta: 0x34,
PacketNum: 0x5678,
DataSize: 0x9ABC,
PrevPacketCombinedCheck: 0xDEF0,
Check0: 0x1234,
Check1: 0x5678,
Check2: 0x9ABC,
},
}
for i, original := range tests {
t.Run("", func(t *testing.T) {
encoded, err := original.Encode()
if err != nil {
t.Fatalf("Test %d: Encode() error = %v", i, err)
}
decoded, err := NewCryptPacketHeader(encoded)
if err != nil {
t.Fatalf("Test %d: NewCryptPacketHeader() error = %v", i, err)
}
if decoded.Pf0 != original.Pf0 {
t.Errorf("Test %d: Pf0 = %d, want %d", i, decoded.Pf0, original.Pf0)
}
if decoded.KeyRotDelta != original.KeyRotDelta {
t.Errorf("Test %d: KeyRotDelta = %d, want %d", i, decoded.KeyRotDelta, original.KeyRotDelta)
}
if decoded.PacketNum != original.PacketNum {
t.Errorf("Test %d: PacketNum = %d, want %d", i, decoded.PacketNum, original.PacketNum)
}
if decoded.DataSize != original.DataSize {
t.Errorf("Test %d: DataSize = %d, want %d", i, decoded.DataSize, original.DataSize)
}
if decoded.PrevPacketCombinedCheck != original.PrevPacketCombinedCheck {
t.Errorf("Test %d: PrevPacketCombinedCheck = %d, want %d", i, decoded.PrevPacketCombinedCheck, original.PrevPacketCombinedCheck)
}
if decoded.Check0 != original.Check0 {
t.Errorf("Test %d: Check0 = %d, want %d", i, decoded.Check0, original.Check0)
}
if decoded.Check1 != original.Check1 {
t.Errorf("Test %d: Check1 = %d, want %d", i, decoded.Check1, original.Check1)
}
if decoded.Check2 != original.Check2 {
t.Errorf("Test %d: Check2 = %d, want %d", i, decoded.Check2, original.Check2)
}
})
}
}
func TestCryptPacketHeaderBigEndian(t *testing.T) {
// Verify big-endian encoding
header := &CryptPacketHeader{
PacketNum: 0x1234,
}
encoded, err := header.Encode()
if err != nil {
t.Fatalf("Encode() error = %v", err)
}
// PacketNum is at bytes 2-3 (after Pf0 and KeyRotDelta)
if encoded[2] != 0x12 || encoded[3] != 0x34 {
t.Errorf("PacketNum encoding is not big-endian: %v", encoded[2:4])
}
}
func TestNewCryptPacketHeaderExtraBytes(t *testing.T) {
// Test with more than required bytes (should still work)
data := make([]byte, 20)
data[0] = 0x01 // Pf0
header, err := NewCryptPacketHeader(data)
if err != nil {
t.Fatalf("NewCryptPacketHeader() with extra bytes error = %v", err)
}
if header.Pf0 != 0x01 {
t.Errorf("Pf0 = %d, want 1", header.Pf0)
}
}
func TestCryptPacketHeaderZeroValues(t *testing.T) {
header := &CryptPacketHeader{}
encoded, err := header.Encode()
if err != nil {
t.Fatalf("Encode() error = %v", err)
}
expected := make([]byte, CryptPacketHeaderLength)
if !bytes.Equal(encoded, expected) {
t.Errorf("Encode() zero header = %v, want all zeros", encoded)
}
}

View File

@@ -0,0 +1,463 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestMHFPacketInterface(t *testing.T) {
// Verify that packets implement the MHFPacket interface
var _ MHFPacket = &MsgSysPing{}
var _ MHFPacket = &MsgSysTime{}
var _ MHFPacket = &MsgSysNop{}
var _ MHFPacket = &MsgSysEnd{}
var _ MHFPacket = &MsgSysLogin{}
var _ MHFPacket = &MsgSysLogout{}
}
func TestFromOpcodeReturnsCorrectType(t *testing.T) {
tests := []struct {
opcode network.PacketID
wantType string
}{
{network.MSG_HEAD, "*mhfpacket.MsgHead"},
{network.MSG_SYS_PING, "*mhfpacket.MsgSysPing"},
{network.MSG_SYS_TIME, "*mhfpacket.MsgSysTime"},
{network.MSG_SYS_NOP, "*mhfpacket.MsgSysNop"},
{network.MSG_SYS_END, "*mhfpacket.MsgSysEnd"},
{network.MSG_SYS_ACK, "*mhfpacket.MsgSysAck"},
{network.MSG_SYS_LOGIN, "*mhfpacket.MsgSysLogin"},
{network.MSG_SYS_LOGOUT, "*mhfpacket.MsgSysLogout"},
{network.MSG_SYS_CREATE_STAGE, "*mhfpacket.MsgSysCreateStage"},
{network.MSG_SYS_ENTER_STAGE, "*mhfpacket.MsgSysEnterStage"},
}
for _, tt := range tests {
t.Run(tt.opcode.String(), func(t *testing.T) {
pkt := FromOpcode(tt.opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", tt.opcode)
return
}
if pkt.Opcode() != tt.opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), tt.opcode)
}
})
}
}
func TestFromOpcodeUnknown(t *testing.T) {
// Test with an invalid opcode
pkt := FromOpcode(network.PacketID(0xFFFF))
if pkt != nil {
t.Error("FromOpcode(0xFFFF) should return nil for unknown opcode")
}
}
func TestMsgSysPingRoundTrip(t *testing.T) {
original := &MsgSysPing{
AckHandle: 0x12345678,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysPing{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.AckHandle != original.AckHandle {
t.Errorf("AckHandle = %d, want %d", parsed.AckHandle, original.AckHandle)
}
}
func TestMsgSysTimeRoundTrip(t *testing.T) {
tests := []struct {
name string
getRemoteTime bool
timestamp uint32
}{
{"no remote time", false, 1577105879},
{"with remote time", true, 1609459200},
{"zero timestamp", false, 0},
{"max timestamp", true, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := &MsgSysTime{
GetRemoteTime: tt.getRemoteTime,
Timestamp: tt.timestamp,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysTime{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.GetRemoteTime != original.GetRemoteTime {
t.Errorf("GetRemoteTime = %v, want %v", parsed.GetRemoteTime, original.GetRemoteTime)
}
if parsed.Timestamp != original.Timestamp {
t.Errorf("Timestamp = %d, want %d", parsed.Timestamp, original.Timestamp)
}
})
}
}
func TestMsgSysPingOpcode(t *testing.T) {
pkt := &MsgSysPing{}
if pkt.Opcode() != network.MSG_SYS_PING {
t.Errorf("Opcode() = %s, want MSG_SYS_PING", pkt.Opcode())
}
}
func TestMsgSysTimeOpcode(t *testing.T) {
pkt := &MsgSysTime{}
if pkt.Opcode() != network.MSG_SYS_TIME {
t.Errorf("Opcode() = %s, want MSG_SYS_TIME", pkt.Opcode())
}
}
func TestFromOpcodeSystemPackets(t *testing.T) {
// Test all system packet opcodes return non-nil
systemOpcodes := []network.PacketID{
network.MSG_SYS_reserve01,
network.MSG_SYS_reserve02,
network.MSG_SYS_reserve03,
network.MSG_SYS_reserve04,
network.MSG_SYS_reserve05,
network.MSG_SYS_reserve06,
network.MSG_SYS_reserve07,
network.MSG_SYS_ADD_OBJECT,
network.MSG_SYS_DEL_OBJECT,
network.MSG_SYS_DISP_OBJECT,
network.MSG_SYS_HIDE_OBJECT,
network.MSG_SYS_END,
network.MSG_SYS_NOP,
network.MSG_SYS_ACK,
network.MSG_SYS_LOGIN,
network.MSG_SYS_LOGOUT,
network.MSG_SYS_SET_STATUS,
network.MSG_SYS_PING,
network.MSG_SYS_TIME,
}
for _, opcode := range systemOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestFromOpcodeStagePackets(t *testing.T) {
stageOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_STAGE,
network.MSG_SYS_STAGE_DESTRUCT,
network.MSG_SYS_ENTER_STAGE,
network.MSG_SYS_BACK_STAGE,
network.MSG_SYS_MOVE_STAGE,
network.MSG_SYS_LEAVE_STAGE,
network.MSG_SYS_LOCK_STAGE,
network.MSG_SYS_UNLOCK_STAGE,
network.MSG_SYS_RESERVE_STAGE,
network.MSG_SYS_UNRESERVE_STAGE,
network.MSG_SYS_SET_STAGE_PASS,
}
for _, opcode := range stageOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestOpcodeMatches(t *testing.T) {
// Verify that packets return the same opcode they were created from
tests := []network.PacketID{
network.MSG_HEAD,
network.MSG_SYS_PING,
network.MSG_SYS_TIME,
network.MSG_SYS_END,
network.MSG_SYS_NOP,
network.MSG_SYS_ACK,
network.MSG_SYS_LOGIN,
network.MSG_SYS_CREATE_STAGE,
}
for _, opcode := range tests {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Skip("opcode not implemented")
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}
func TestParserInterface(t *testing.T) {
// Verify Parser interface works
var p Parser = &MsgSysPing{}
bf := byteframe.NewByteFrame()
bf.WriteUint32(123)
bf.Seek(0, io.SeekStart)
err := p.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Errorf("Parse() error = %v", err)
}
}
func TestBuilderInterface(t *testing.T) {
// Verify Builder interface works
var b Builder = &MsgSysPing{AckHandle: 456}
bf := byteframe.NewByteFrame()
err := b.Build(bf, &clientctx.ClientContext{})
if err != nil {
t.Errorf("Build() error = %v", err)
}
if len(bf.Data()) == 0 {
t.Error("Build() should write data")
}
}
func TestOpcoderInterface(t *testing.T) {
// Verify Opcoder interface works
var o Opcoder = &MsgSysPing{}
opcode := o.Opcode()
if opcode != network.MSG_SYS_PING {
t.Errorf("Opcode() = %s, want MSG_SYS_PING", opcode)
}
}
func TestClientContextNilSafe(t *testing.T) {
// Some packets may need to handle nil ClientContext
pkt := &MsgSysPing{AckHandle: 123}
bf := byteframe.NewByteFrame()
// This should not panic even with nil context (implementation dependent)
// Note: The actual behavior depends on implementation
err := pkt.Build(bf, nil)
if err != nil {
// Error is acceptable if nil context is not supported
t.Logf("Build() with nil context returned error: %v", err)
}
}
func TestMsgSysPingBuildFormat(t *testing.T) {
pkt := &MsgSysPing{AckHandle: 0x12345678}
bf := byteframe.NewByteFrame()
pkt.Build(bf, &clientctx.ClientContext{})
data := bf.Data()
if len(data) != 4 {
t.Errorf("Build() data len = %d, want 4", len(data))
}
// Verify big-endian format (default)
if data[0] != 0x12 || data[1] != 0x34 || data[2] != 0x56 || data[3] != 0x78 {
t.Errorf("Build() data = %x, want 12345678", data)
}
}
func TestMsgSysTimeBuildFormat(t *testing.T) {
pkt := &MsgSysTime{
GetRemoteTime: true,
Timestamp: 0xDEADBEEF,
}
bf := byteframe.NewByteFrame()
pkt.Build(bf, &clientctx.ClientContext{})
data := bf.Data()
if len(data) != 5 {
t.Errorf("Build() data len = %d, want 5 (1 bool + 4 uint32)", len(data))
}
// First byte is bool (1 = true)
if data[0] != 1 {
t.Errorf("GetRemoteTime byte = %d, want 1", data[0])
}
}
func TestMsgSysNop(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_NOP)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_NOP) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_NOP {
t.Errorf("Opcode() = %s, want MSG_SYS_NOP", pkt.Opcode())
}
}
func TestMsgSysEnd(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_END)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_END) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_END {
t.Errorf("Opcode() = %s, want MSG_SYS_END", pkt.Opcode())
}
}
func TestMsgHead(t *testing.T) {
pkt := FromOpcode(network.MSG_HEAD)
if pkt == nil {
t.Fatal("FromOpcode(MSG_HEAD) returned nil")
}
if pkt.Opcode() != network.MSG_HEAD {
t.Errorf("Opcode() = %s, want MSG_HEAD", pkt.Opcode())
}
}
func TestMsgSysAck(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_ACK)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_ACK) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_ACK {
t.Errorf("Opcode() = %s, want MSG_SYS_ACK", pkt.Opcode())
}
}
func TestBinaryPackets(t *testing.T) {
binaryOpcodes := []network.PacketID{
network.MSG_SYS_CAST_BINARY,
network.MSG_SYS_CASTED_BINARY,
network.MSG_SYS_SET_STAGE_BINARY,
network.MSG_SYS_GET_STAGE_BINARY,
network.MSG_SYS_WAIT_STAGE_BINARY,
}
for _, opcode := range binaryOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestEnumeratePackets(t *testing.T) {
enumOpcodes := []network.PacketID{
network.MSG_SYS_ENUMERATE_CLIENT,
network.MSG_SYS_ENUMERATE_STAGE,
}
for _, opcode := range enumOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestSemaphorePackets(t *testing.T) {
semaOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE,
network.MSG_SYS_ACQUIRE_SEMAPHORE,
network.MSG_SYS_RELEASE_SEMAPHORE,
network.MSG_SYS_CHECK_SEMAPHORE,
}
for _, opcode := range semaOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestObjectPackets(t *testing.T) {
objOpcodes := []network.PacketID{
network.MSG_SYS_ADD_OBJECT,
network.MSG_SYS_DEL_OBJECT,
network.MSG_SYS_DISP_OBJECT,
network.MSG_SYS_HIDE_OBJECT,
}
for _, opcode := range objOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestLogPackets(t *testing.T) {
logOpcodes := []network.PacketID{
network.MSG_SYS_TERMINAL_LOG,
network.MSG_SYS_ISSUE_LOGKEY,
network.MSG_SYS_RECORD_LOG,
}
for _, opcode := range logOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestMHFSaveLoad(t *testing.T) {
saveLoadOpcodes := []network.PacketID{
network.MSG_MHF_SAVEDATA,
network.MSG_MHF_LOADDATA,
}
for _, opcode := range saveLoadOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}