test: improve test coverage for mhfpacket, channelserver, and server packages

Add comprehensive tests for:
- Pure time functions in channelserver (sys_time_test.go)
- Stage-related packet parsing (msg_sys_stage_test.go)
- Acquire packet family parsing (msg_mhf_acquire_test.go)
- Extended mhfpacket tests for login, logout, and stage packets
- Entrance server makeHeader structure and checksum tests
- SignV2 server request/response JSON structure tests
This commit is contained in:
Houmgaor
2026-02-01 23:28:19 +01:00
parent f83761c0b1
commit db3e0bccc7
6 changed files with 1715 additions and 0 deletions

View File

@@ -461,3 +461,383 @@ func TestMHFSaveLoad(t *testing.T) {
})
}
}
func TestMsgSysCreateStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnk0 uint8
wantPlayers uint8
wantStageID string
}{
{
name: "simple stage",
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x05}, []byte("test")...),
wantHandle: 1,
wantUnk0: 2,
wantPlayers: 4,
wantStageID: "test",
},
{
name: "empty stage ID",
data: []byte{0x12, 0x34, 0x56, 0x78, 0x01, 0x02, 0x00},
wantHandle: 0x12345678,
wantUnk0: 1,
wantPlayers: 2,
wantStageID: "",
},
{
name: "with null terminator",
data: append([]byte{0x00, 0x00, 0x00, 0x0A, 0x01, 0x01, 0x08}, append([]byte("stage01"), 0x00)...),
wantHandle: 10,
wantUnk0: 1,
wantPlayers: 1,
wantStageID: "stage01",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.Unk0 != tt.wantUnk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
}
if pkt.PlayerCount != tt.wantPlayers {
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.wantPlayers)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysEnterStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnkBool uint8
wantStageID string
}{
{
name: "enter mezeporta",
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x0F}, []byte("sl1Ns200p0a0u0")...),
wantHandle: 1,
wantUnkBool: 0,
wantStageID: "sl1Ns200p0a0u0",
},
{
name: "with unk bool set",
data: append([]byte{0xAB, 0xCD, 0xEF, 0x12, 0x01, 0x05}, []byte("room1")...),
wantHandle: 0xABCDEF12,
wantUnkBool: 1,
wantStageID: "room1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.UnkBool != tt.wantUnkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.wantUnkBool)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysMoveStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnkBool uint8
wantStageID string
}{
{
name: "move to quest stage",
data: append([]byte{0x00, 0x00, 0x12, 0x34, 0x00, 0x06}, []byte("quest1")...),
wantHandle: 0x1234,
wantUnkBool: 0,
wantStageID: "quest1",
},
{
name: "with null in string",
data: append([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x08}, append([]byte("stage"), []byte{0x00, 0x00, 0x00}...)...),
wantHandle: 0xFFFFFFFF,
wantUnkBool: 1,
wantStageID: "stage",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysMoveStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.UnkBool != tt.wantUnkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.wantUnkBool)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysLockStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnk0 uint8
wantUnk1 uint8
wantStageID string
}{
{
name: "lock stage",
data: append([]byte{0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x06}, []byte("room01")...),
wantHandle: 5,
wantUnk0: 1,
wantUnk1: 1,
wantStageID: "room01",
},
{
name: "different unk values",
data: append([]byte{0x12, 0x34, 0x56, 0x78, 0x02, 0x03, 0x04}, []byte("test")...),
wantHandle: 0x12345678,
wantUnk0: 2,
wantUnk1: 3,
wantStageID: "test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.Unk0 != tt.wantUnk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
}
if pkt.Unk1 != tt.wantUnk1 {
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.wantUnk1)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysUnlockStageRoundTrip(t *testing.T) {
tests := []struct {
name string
unk0 uint16
}{
{"zero value", 0},
{"typical value", 1},
{"max value", 0xFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := &MsgSysUnlockStage{Unk0: tt.unk0}
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 := &MsgSysUnlockStage{}
err = parsed.Parse(bf, ctx)
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)
}
})
}
}
func TestMsgSysBackStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
}{
{"simple handle", []byte{0x00, 0x00, 0x00, 0x01}, 1},
{"large handle", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0xDEADBEEF},
{"zero handle", []byte{0x00, 0x00, 0x00, 0x00}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysBackStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
})
}
}
func TestMsgSysLogoutParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantUnk0 uint8
}{
{"typical logout", []byte{0x01}, 1},
{"zero value", []byte{0x00}, 0},
{"max value", []byte{0xFF}, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogout{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.wantUnk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
}
})
}
}
func TestMsgSysLoginParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
charID0 uint32
loginTokenNumber uint32
hardcodedZero0 uint16
requestVersion uint16
charID1 uint32
hardcodedZero1 uint16
tokenStrLen uint16
tokenString string
}{
{
name: "typical login",
ackHandle: 1,
charID0: 12345,
loginTokenNumber: 67890,
hardcodedZero0: 0,
requestVersion: 1,
charID1: 12345,
hardcodedZero1: 0,
tokenStrLen: 0x11,
tokenString: "abc123token",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.charID0)
bf.WriteUint32(tt.loginTokenNumber)
bf.WriteUint16(tt.hardcodedZero0)
bf.WriteUint16(tt.requestVersion)
bf.WriteUint32(tt.charID1)
bf.WriteUint16(tt.hardcodedZero1)
bf.WriteUint16(tt.tokenStrLen)
bf.WriteBytes(append([]byte(tt.tokenString), 0x00)) // null terminated
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogin{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.CharID0 != tt.charID0 {
t.Errorf("CharID0 = %d, want %d", pkt.CharID0, tt.charID0)
}
if pkt.LoginTokenNumber != tt.loginTokenNumber {
t.Errorf("LoginTokenNumber = %d, want %d", pkt.LoginTokenNumber, tt.loginTokenNumber)
}
if pkt.RequestVersion != tt.requestVersion {
t.Errorf("RequestVersion = %d, want %d", pkt.RequestVersion, tt.requestVersion)
}
if pkt.LoginTokenString != tt.tokenString {
t.Errorf("LoginTokenString = %q, want %q", pkt.LoginTokenString, tt.tokenString)
}
})
}
}

View File

@@ -0,0 +1,263 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestAcquirePacketOpcodes(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
expect network.PacketID
}{
{"MsgMhfAcquireGuildTresure", &MsgMhfAcquireGuildTresure{}, network.MSG_MHF_ACQUIRE_GUILD_TRESURE},
{"MsgMhfAcquireTitle", &MsgMhfAcquireTitle{}, network.MSG_MHF_ACQUIRE_TITLE},
{"MsgMhfAcquireDistItem", &MsgMhfAcquireDistItem{}, network.MSG_MHF_ACQUIRE_DIST_ITEM},
{"MsgMhfAcquireMonthlyItem", &MsgMhfAcquireMonthlyItem{}, network.MSG_MHF_ACQUIRE_MONTHLY_ITEM},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.pkt.Opcode(); got != tt.expect {
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
}
})
}
}
func TestMsgMhfAcquireGuildTresureParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
huntID uint32
unk uint8
}{
{"basic acquisition", 1, 12345, 0},
{"large hunt ID", 0xABCDEF12, 0xFFFFFFFF, 1},
{"zero values", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.huntID)
bf.WriteUint8(tt.unk)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireGuildTresure{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.HuntID != tt.huntID {
t.Errorf("HuntID = %d, want %d", pkt.HuntID, tt.huntID)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
}
})
}
}
func TestMsgMhfAcquireTitleParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
unk0 uint16
unk1 uint16
titleID uint16
}{
{"acquire title 1", 1, 0, 0, 1},
{"acquire title 100", 0x12345678, 10, 20, 100},
{"max title ID", 0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint16(tt.unk0)
bf.WriteUint16(tt.unk1)
bf.WriteUint16(tt.titleID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireTitle{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.Unk1 != tt.unk1 {
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
}
if pkt.TitleID != tt.titleID {
t.Errorf("TitleID = %d, want %d", pkt.TitleID, tt.titleID)
}
})
}
}
func TestMsgMhfAcquireDistItemParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
distributionType uint8
distributionID uint32
}{
{"type 0", 1, 0, 12345},
{"type 1", 0xABCD, 1, 67890},
{"max values", 0xFFFFFFFF, 0xFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint8(tt.distributionType)
bf.WriteUint32(tt.distributionID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireDistItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.DistributionType != tt.distributionType {
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, tt.distributionType)
}
if pkt.DistributionID != tt.distributionID {
t.Errorf("DistributionID = %d, want %d", pkt.DistributionID, tt.distributionID)
}
})
}
}
func TestMsgMhfAcquireMonthlyItemParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
unk0 uint16
unk1 uint16
unk2 uint32
unk3 uint32
}{
{"basic", 1, 0, 0, 0, 0},
{"with values", 100, 10, 20, 30, 40},
{"max values", 0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint16(tt.unk0)
bf.WriteUint16(tt.unk1)
bf.WriteUint32(tt.unk2)
bf.WriteUint32(tt.unk3)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireMonthlyItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.Unk1 != tt.unk1 {
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
}
if pkt.Unk2 != tt.unk2 {
t.Errorf("Unk2 = %d, want %d", pkt.Unk2, tt.unk2)
}
if pkt.Unk3 != tt.unk3 {
t.Errorf("Unk3 = %d, want %d", pkt.Unk3, tt.unk3)
}
})
}
}
func TestAcquirePacketsFromOpcode(t *testing.T) {
acquireOpcodes := []network.PacketID{
network.MSG_MHF_ACQUIRE_GUILD_TRESURE,
network.MSG_MHF_ACQUIRE_TITLE,
network.MSG_MHF_ACQUIRE_DIST_ITEM,
network.MSG_MHF_ACQUIRE_MONTHLY_ITEM,
}
for _, opcode := range acquireOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Fatalf("FromOpcode(%s) returned nil", opcode)
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}
func TestAcquirePacketEdgeCases(t *testing.T) {
t.Run("guild tresure with max hunt ID", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint32(0xFFFFFFFF)
bf.WriteUint8(255)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireGuildTresure{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.HuntID != 0xFFFFFFFF {
t.Errorf("HuntID = %d, want %d", pkt.HuntID, 0xFFFFFFFF)
}
})
t.Run("dist item with all types", func(t *testing.T) {
for i := uint8(0); i < 5; i++ {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(i)
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireDistItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v for type %d", err, i)
}
if pkt.DistributionType != i {
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, i)
}
}
})
}

View File

@@ -0,0 +1,338 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestStagePacketOpcodes(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
expect network.PacketID
}{
{"MsgSysCreateStage", &MsgSysCreateStage{}, network.MSG_SYS_CREATE_STAGE},
{"MsgSysEnterStage", &MsgSysEnterStage{}, network.MSG_SYS_ENTER_STAGE},
{"MsgSysMoveStage", &MsgSysMoveStage{}, network.MSG_SYS_MOVE_STAGE},
{"MsgSysBackStage", &MsgSysBackStage{}, network.MSG_SYS_BACK_STAGE},
{"MsgSysLockStage", &MsgSysLockStage{}, network.MSG_SYS_LOCK_STAGE},
{"MsgSysUnlockStage", &MsgSysUnlockStage{}, network.MSG_SYS_UNLOCK_STAGE},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.pkt.Opcode(); got != tt.expect {
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
}
})
}
}
func TestMsgSysCreateStageFields(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
unk0 uint8
playerCount uint8
stageID string
}{
{"empty stage", 1, 1, 4, ""},
{"mezeporta", 0x12345678, 2, 8, "sl1Ns200p0a0u0"},
{"quest room", 100, 1, 4, "q1234"},
{"max players", 0xFFFFFFFF, 2, 16, "max_stage"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint8(tt.unk0)
bf.WriteUint8(tt.playerCount)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(stageIDBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.PlayerCount != tt.playerCount {
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.playerCount)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysEnterStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unkBool uint8
stageID string
}{
{"enter town", 1, 0, "town01"},
{"force enter", 2, 1, "quest_stage"},
{"rasta bar", 999, 0, "sl1Ns211p0a0u0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteUint8(tt.unkBool)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(stageIDBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
if pkt.UnkBool != tt.unkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.unkBool)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysMoveStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unkBool uint8
stageID string
}{
{"move to area", 1, 0, "area01"},
{"move to quest", 0xABCD, 1, "quest12345"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteUint8(tt.unkBool)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(stageIDBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysMoveStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
if pkt.UnkBool != tt.unkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.unkBool)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysLockStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unk0 uint8
unk1 uint8
stageID string
}{
{"lock room", 1, 1, 1, "room01"},
{"private party", 0x1234, 1, 1, "party_stage"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteUint8(tt.unk0)
bf.WriteUint8(tt.unk1)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(stageIDBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.Unk1 != tt.unk1 {
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysUnlockStageFields(t *testing.T) {
tests := []struct {
name string
unk0 uint16
}{
{"zero", 0},
{"typical", 1},
{"max", 0xFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysUnlockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
func TestMsgSysBackStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
}{
{"small handle", 1},
{"large handle", 0xDEADBEEF},
{"zero", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysBackStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
})
}
}
func TestStageIDEdgeCases(t *testing.T) {
t.Run("long stage ID", func(t *testing.T) {
// Stage ID with max length (255 bytes)
longID := make([]byte, 200)
for i := range longID {
longID[i] = 'a' + byte(i%26)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(1)
bf.WriteUint8(4)
bf.WriteUint8(uint8(len(longID)))
bf.WriteBytes(longID)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.StageID != string(longID) {
t.Errorf("StageID length = %d, want %d", len(pkt.StageID), len(longID))
}
})
t.Run("stage ID with null terminator", func(t *testing.T) {
// String terminated with null byte
stageID := "test\x00extra"
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(0)
bf.WriteUint8(uint8(len(stageID)))
bf.WriteBytes([]byte(stageID))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Should truncate at null
if pkt.StageID != "test" {
t.Errorf("StageID = %q, want %q (should truncate at null)", pkt.StageID, "test")
}
})
}
func TestStagePacketFromOpcode(t *testing.T) {
stageOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_STAGE,
network.MSG_SYS_ENTER_STAGE,
network.MSG_SYS_BACK_STAGE,
network.MSG_SYS_MOVE_STAGE,
network.MSG_SYS_LOCK_STAGE,
network.MSG_SYS_UNLOCK_STAGE,
}
for _, opcode := range stageOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Fatalf("FromOpcode(%s) returned nil", opcode)
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}