test: backport remaining test files from v9.2.x-stable

Import 18 network packet test files and 5 server infrastructure test
files, adapted for main branch APIs: fix config import alias (_config),
remove non-existent DevMode field, use global handlerTable instead of
per-server handlers map, and correct validateToken mock expectations
to include both token and tokenID arguments.

Adds go-sqlmock dependency for database mocking in signserver tests.
This commit is contained in:
Houmgaor
2026-02-17 00:09:41 +01:00
parent 4c3810df7e
commit 0bd724f74e
25 changed files with 13785 additions and 0 deletions

1
go.mod
View File

@@ -15,6 +15,7 @@ require (
)
require (
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect

3
go.sum
View File

@@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -143,6 +145,7 @@ github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Cc
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

View File

@@ -0,0 +1,430 @@
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
}{
{ChatTypeStage, 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: ChatTypeStage,
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{
ChatTypeStage,
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)
}
})
}
}
func TestMsgBinMailNotifyParsePanics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Parse() should panic with 'implement me'")
}
}()
m := MsgBinMailNotify{}
bf := byteframe.NewByteFrame()
_ = m.Parse(bf)
}
func TestMsgBinMailNotifyBuildLongName(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "ThisIsAVeryLongPlayerNameThatExceeds21Characters",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Data should still be 22 bytes (1 + 21)
if len(bf.Data()) != 22 {
t.Errorf("Data len = %d, want 22", len(bf.Data()))
}
}

View File

@@ -0,0 +1,828 @@
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)
}
})
}
}
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}, append([]byte("test"), 0x00)...),
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
wantUnk bool
wantStageID string
}{
{
name: "enter mezeporta",
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x0F}, append([]byte("sl1Ns200p0a0u0"), 0x00)...),
wantHandle: 1,
wantUnk: false,
wantStageID: "sl1Ns200p0a0u0",
},
{
name: "with unk bool set",
data: append([]byte{0xAB, 0xCD, 0xEF, 0x12, 0x01, 0x05}, append([]byte("room1"), 0x00)...),
wantHandle: 0xABCDEF12,
wantUnk: true,
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.Unk != tt.wantUnk {
t.Errorf("Unk = %v, want %v", pkt.Unk, tt.wantUnk)
}
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
wantStageID string
}{
{
name: "lock stage",
data: append([]byte{0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x06}, append([]byte("room01"), 0x00)...),
wantHandle: 5,
wantStageID: "room01",
},
{
name: "different unk values",
data: append([]byte{0x12, 0x34, 0x56, 0x78, 0x02, 0x03, 0x04}, append([]byte("test"), 0x00)...),
wantHandle: 0x12345678,
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.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) {
ctx := &clientctx.ClientContext{}
// Build (returns NOT IMPLEMENTED)
original := &MsgSysUnlockStage{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err == nil {
t.Fatal("Build() expected NOT IMPLEMENTED error")
}
// Parse should consume a uint16 without error
bf = byteframe.NewByteFrame()
bf.WriteUint16(tt.unk0)
bf.Seek(0, io.SeekStart)
parsed := &MsgSysUnlockStage{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
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)
}
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func init() {
// Initialize ErupeConfig for tests that access it
_config.ErupeConfig = &_config.Config{
RealClientMode: _config.ZZ, // Default to ZZ for tests
}
}
func TestMsgMhfAcquireCafeItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireCafeItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_CAFE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_CAFE_ITEM", pkt.Opcode())
}
}
func TestMsgMhfAcquireCafeItemParse(t *testing.T) {
// Test basic parsing with current implementation (always reads uint32 for PointCost)
// Current code: m.PointCost = bf.ReadUint32() (no client mode check)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint16(1) // ItemType
bf.WriteUint16(100) // ItemID
bf.WriteUint16(5) // Quant
bf.WriteUint32(1000) // PointCost (uint32)
bf.WriteUint16(0) // Unk0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
ctx := &clientctx.ClientContext{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.ItemType != 1 {
t.Errorf("ItemType = %d, want 1", pkt.ItemType)
}
if pkt.ItemID != 100 {
t.Errorf("ItemID = %d, want 100", pkt.ItemID)
}
if pkt.Quant != 5 {
t.Errorf("Quant = %d, want 5", pkt.Quant)
}
if pkt.PointCost != 1000 {
t.Errorf("PointCost = %d, want 1000", pkt.PointCost)
}
}
// TestMsgMhfAcquireCafeItemParseUint32PointCost documents the current behavior.
//
// CURRENT BEHAVIOR: Always reads PointCost as uint32.
//
// EXPECTED BEHAVIOR AFTER FIX (commit 3d0114c):
// - G6+: Read PointCost as uint32
// - G1-G5.2: Read PointCost as uint16
//
// This test verifies current uint32 parsing works correctly.
// After the fix is applied, this test should still pass for G6+ clients.
func TestMsgMhfAcquireCafeItemParseUint32PointCost(t *testing.T) {
tests := []struct {
name string
pointCost uint32
wantCost uint32
}{
{"small cost", 100, 100},
{"medium cost", 5000, 5000},
{"large cost exceeding uint16", 70000, 70000},
{"max uint32", 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAAAABBBB) // AckHandle
bf.WriteUint16(1) // ItemType
bf.WriteUint16(200) // ItemID
bf.WriteUint16(10) // Quant
bf.WriteUint32(tt.pointCost)
bf.WriteUint16(0) // Unk0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
ctx := &clientctx.ClientContext{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.PointCost != tt.wantCost {
t.Errorf("PointCost = %d, want %d", pkt.PointCost, tt.wantCost)
}
})
}
}
// TestMsgMhfAcquireCafeItemParseFieldOrder verifies the exact field order in parsing.
// This is important because the fix changes when PointCost is read (uint16 vs uint32).
func TestMsgMhfAcquireCafeItemParseFieldOrder(t *testing.T) {
// Build a packet with known values
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle (offset 0-3)
bf.WriteUint16(0x5566) // ItemType (offset 4-5)
bf.WriteUint16(0x7788) // ItemID (offset 6-7)
bf.WriteUint16(0x99AA) // Quant (offset 8-9)
bf.WriteUint32(0xBBCCDDEE) // PointCost (offset 10-13)
bf.WriteUint16(0xFF00) // Unk0 (offset 14-15)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.ItemType != 0x5566 {
t.Errorf("ItemType = 0x%X, want 0x5566", pkt.ItemType)
}
if pkt.ItemID != 0x7788 {
t.Errorf("ItemID = 0x%X, want 0x7788", pkt.ItemID)
}
if pkt.Quant != 0x99AA {
t.Errorf("Quant = 0x%X, want 0x99AA", pkt.Quant)
}
if pkt.PointCost != 0xBBCCDDEE {
t.Errorf("PointCost = 0x%X, want 0xBBCCDDEE", pkt.PointCost)
}
if pkt.Unk0 != 0xFF00 {
t.Errorf("Unk0 = 0x%X, want 0xFF00", pkt.Unk0)
}
}
func TestMsgMhfAcquireCafeItemBuildNotImplemented(t *testing.T) {
pkt := &MsgMhfAcquireCafeItem{
AckHandle: 123,
ItemType: 1,
ItemID: 100,
Quant: 5,
PointCost: 1000,
}
bf := byteframe.NewByteFrame()
ctx := &clientctx.ClientContext{}
err := pkt.Build(bf, ctx)
if err == nil {
t.Error("Build() should return error (NOT IMPLEMENTED)")
}
}
func TestMsgMhfAcquireCafeItemFromOpcode(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_ACQUIRE_CAFE_ITEM)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_ACQUIRE_CAFE_ITEM) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_CAFE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_CAFE_ITEM", pkt.Opcode())
}
}

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 bool
}{
{"basic acquisition", 1, 12345, false},
{"large hunt ID", 0xABCDEF12, 0xFFFFFFFF, true},
{"zero values", 0, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.huntID)
bf.WriteBool(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 = %v, want %v", pkt.Unk, tt.unk)
}
})
}
}
func TestMsgMhfAcquireTitleParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
titleIDs []uint16
}{
{"acquire title 1", 1, []uint16{1}},
{"acquire titles 100 200", 0x12345678, []uint16{100, 200}},
{"no titles", 0xFFFFFFFF, []uint16{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint16(uint16(len(tt.titleIDs))) // count
bf.WriteUint16(0) // zeroed
for _, id := range tt.titleIDs {
bf.WriteUint16(id)
}
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 len(pkt.TitleIDs) != len(tt.titleIDs) {
t.Errorf("TitleIDs len = %d, want %d", len(pkt.TitleIDs), len(tt.titleIDs))
}
for i, id := range tt.titleIDs {
if i < len(pkt.TitleIDs) && pkt.TitleIDs[i] != id {
t.Errorf("TitleIDs[%d] = %d, want %d", i, pkt.TitleIDs[i], id)
}
}
})
}
}
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 uint8
unk1 uint8
unk2 uint16
unk3 uint32
}{
{"basic", 1, 0, 0, 0, 0},
{"with values", 100, 10, 20, 30, 40},
{"max values", 0xFFFFFFFF, 0xFF, 0xFF, 0xFFFF, 0xFFFFFFFF},
}
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.unk1)
bf.WriteUint16(tt.unk2)
bf.WriteUint32(tt.unk3)
bf.WriteUint32(0) // Zeroed (consumed by Parse)
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.WriteBool(true)
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,364 @@
package mhfpacket
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
)
func TestMsgMhfUpdateGuacotOpcode_Guacot(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_GUACOT", pkt.Opcode())
}
}
func TestMsgMhfEnumerateGuacotOpcode_Guacot(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_GUACOT", pkt.Opcode())
}
}
func TestMsgMhfUpdateGuacotParse_SingleEntry(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
// Goocoo entry
bf.WriteUint32(2) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(i + 1)) // Data1
}
bf.WriteUint32(100) // Data2[0]
bf.WriteUint32(200) // Data2[1]
bf.WriteUint8(5) // Name length
bf.WriteBytes([]byte("Porky"))
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.EntryCount != 1 {
t.Errorf("EntryCount = %d, want 1", pkt.EntryCount)
}
if len(pkt.Goocoos) != 1 {
t.Fatalf("len(Goocoos) = %d, want 1", len(pkt.Goocoos))
}
g := pkt.Goocoos[0]
if g.Index != 2 {
t.Errorf("Index = %d, want 2", g.Index)
}
if len(g.Data1) != 22 {
t.Fatalf("len(Data1) = %d, want 22", len(g.Data1))
}
for i := 0; i < 22; i++ {
if g.Data1[i] != int16(i+1) {
t.Errorf("Data1[%d] = %d, want %d", i, g.Data1[i], i+1)
}
}
if len(g.Data2) != 2 {
t.Fatalf("len(Data2) = %d, want 2", len(g.Data2))
}
if g.Data2[0] != 100 {
t.Errorf("Data2[0] = %d, want 100", g.Data2[0])
}
if g.Data2[1] != 200 {
t.Errorf("Data2[1] = %d, want 200", g.Data2[1])
}
if string(g.Name) != "Porky" {
t.Errorf("Name = %q, want %q", string(g.Name), "Porky")
}
}
func TestMsgMhfUpdateGuacotParse_MultipleEntries(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(3) // EntryCount
bf.WriteUint16(0) // Zeroed
for idx := uint32(0); idx < 3; idx++ {
bf.WriteUint32(idx) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(idx*100 + uint32(i)))
}
bf.WriteUint32(idx * 10) // Data2[0]
bf.WriteUint32(idx * 20) // Data2[1]
name := []byte("Pog")
bf.WriteUint8(uint8(len(name)))
bf.WriteBytes(name)
}
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if len(pkt.Goocoos) != 3 {
t.Fatalf("len(Goocoos) = %d, want 3", len(pkt.Goocoos))
}
for idx := uint32(0); idx < 3; idx++ {
g := pkt.Goocoos[idx]
if g.Index != idx {
t.Errorf("Goocoos[%d].Index = %d, want %d", idx, g.Index, idx)
}
if g.Data1[0] != int16(idx*100) {
t.Errorf("Goocoos[%d].Data1[0] = %d, want %d", idx, g.Data1[0], idx*100)
}
if g.Data2[0] != idx*10 {
t.Errorf("Goocoos[%d].Data2[0] = %d, want %d", idx, g.Data2[0], idx*10)
}
}
}
func TestMsgMhfUpdateGuacotParse_ZeroEntries(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(42) // AckHandle
bf.WriteUint16(0) // EntryCount
bf.WriteUint16(0) // Zeroed
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.EntryCount != 0 {
t.Errorf("EntryCount = %d, want 0", pkt.EntryCount)
}
if len(pkt.Goocoos) != 0 {
t.Errorf("len(Goocoos) = %d, want 0", len(pkt.Goocoos))
}
}
func TestMsgMhfUpdateGuacotParse_DeletionEntry(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
// Data1[0] = 0 signals deletion
bf.WriteInt16(0)
for i := 1; i < 22; i++ {
bf.WriteInt16(0)
}
bf.WriteUint32(0) // Data2[0]
bf.WriteUint32(0) // Data2[1]
bf.WriteUint8(0) // Empty name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
g := pkt.Goocoos[0]
if g.Data1[0] != 0 {
t.Errorf("Data1[0] = %d, want 0 (deletion marker)", g.Data1[0])
}
}
func TestMsgMhfUpdateGuacotParse_EmptyName(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(1)
}
bf.WriteUint32(0) // Data2[0]
bf.WriteUint32(0) // Data2[1]
bf.WriteUint8(0) // Empty name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if len(pkt.Goocoos[0].Name) != 0 {
t.Errorf("Name length = %d, want 0", len(pkt.Goocoos[0].Name))
}
}
func TestMsgMhfEnumerateGuacotParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(0) // Unk0
bf.WriteUint16(0) // Zeroed
pkt := &MsgMhfEnumerateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 0 {
t.Errorf("Unk0 = %d, want 0", pkt.Unk0)
}
}
func TestMsgMhfUpdateGuacotBuild_NotImplemented(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
err := pkt.Build(byteframe.NewByteFrame(), nil)
if err == nil {
t.Error("Build() should return error (not implemented)")
}
}
func TestMsgMhfEnumerateGuacotBuild_NotImplemented(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
err := pkt.Build(byteframe.NewByteFrame(), nil)
if err == nil {
t.Error("Build() should return error (not implemented)")
}
}
func TestGoocooStruct_Data1Size(t *testing.T) {
// Verify 22 int16 entries = 44 bytes of outfit/appearance data
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(i * 3))
}
bf.WriteUint32(0xDEAD) // Data2[0]
bf.WriteUint32(0xBEEF) // Data2[1]
bf.WriteUint8(0) // No name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
_ = pkt.Parse(bf, nil)
g := pkt.Goocoos[0]
// Verify all 22 data slots are correctly read
for i := 0; i < 22; i++ {
expected := int16(i * 3)
if g.Data1[i] != expected {
t.Errorf("Data1[%d] = %d, want %d", i, g.Data1[i], expected)
}
}
if g.Data2[0] != 0xDEAD {
t.Errorf("Data2[0] = 0x%X, want 0xDEAD", g.Data2[0])
}
if g.Data2[1] != 0xBEEF {
t.Errorf("Data2[1] = 0x%X, want 0xBEEF", g.Data2[1])
}
}
func TestGoocooSerialization_Roundtrip(t *testing.T) {
// Simulate what handleMsgMhfUpdateGuacot does when saving to DB
goocoo := Goocoo{
Index: 1,
Data1: make([]int16, 22),
Data2: []uint32{0x1234, 0x5678},
Name: []byte("MyPoogie"),
}
goocoo.Data1[0] = 5 // outfit type (non-zero = exists)
goocoo.Data1[1] = 100 // some appearance data
goocoo.Data1[21] = -50 // test negative int16
// Serialize (matches handler logic)
bf := byteframe.NewByteFrame()
bf.WriteUint32(goocoo.Index)
for i := range goocoo.Data1 {
bf.WriteInt16(goocoo.Data1[i])
}
for i := range goocoo.Data2 {
bf.WriteUint32(goocoo.Data2[i])
}
bf.WriteUint8(uint8(len(goocoo.Name)))
bf.WriteBytes(goocoo.Name)
// Deserialize and verify
data := bf.Data()
rbf := byteframe.NewByteFrameFromBytes(data)
index := rbf.ReadUint32()
if index != 1 {
t.Errorf("index = %d, want 1", index)
}
data1_0 := rbf.ReadInt16()
if data1_0 != 5 {
t.Errorf("data1[0] = %d, want 5", data1_0)
}
data1_1 := rbf.ReadInt16()
if data1_1 != 100 {
t.Errorf("data1[1] = %d, want 100", data1_1)
}
// Skip to data1[21]
for i := 2; i < 21; i++ {
rbf.ReadInt16()
}
data1_21 := rbf.ReadInt16()
if data1_21 != -50 {
t.Errorf("data1[21] = %d, want -50", data1_21)
}
d2_0 := rbf.ReadUint32()
if d2_0 != 0x1234 {
t.Errorf("data2[0] = 0x%X, want 0x1234", d2_0)
}
d2_1 := rbf.ReadUint32()
if d2_1 != 0x5678 {
t.Errorf("data2[1] = 0x%X, want 0x5678", d2_1)
}
nameLen := rbf.ReadUint8()
if nameLen != 8 {
t.Errorf("nameLen = %d, want 8", nameLen)
}
name := rbf.ReadBytes(uint(nameLen))
if string(name) != "MyPoogie" {
t.Errorf("name = %q, want %q", string(name), "MyPoogie")
}
}
func TestGoocooEntrySize(t *testing.T) {
// Each goocoo entry in the packet should be:
// 4 (index) + 22*2 (data1) + 2*4 (data2) + 1 (name len) + N (name)
// = 4 + 44 + 8 + 1 + N = 57 + N bytes
name := []byte("Test")
expectedSize := 4 + 44 + 8 + 1 + len(name)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // index
for i := 0; i < 22; i++ {
bf.WriteInt16(0)
}
bf.WriteUint32(0) // data2[0]
bf.WriteUint32(0) // data2[1]
bf.WriteUint8(uint8(len(name))) // name len
bf.WriteBytes(name)
if len(bf.Data()) != expectedSize {
t.Errorf("entry size = %d bytes, want %d bytes (57 + %d name)", len(bf.Data()), expectedSize, len(name))
}
}

View File

@@ -0,0 +1,537 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// TestMsgMhfSavedataParse tests parsing MsgMhfSavedata
func TestMsgMhfSavedataParse(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_SAVEDATA)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_SAVEDATA) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_SAVEDATA {
t.Errorf("Opcode() = %s, want MSG_MHF_SAVEDATA", pkt.Opcode())
}
}
// TestMsgMhfLoaddataParse tests parsing MsgMhfLoaddata
func TestMsgMhfLoaddataParse(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_LOADDATA)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_LOADDATA) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_LOADDATA {
t.Errorf("Opcode() = %s, want MSG_MHF_LOADDATA", pkt.Opcode())
}
}
// TestMsgMhfListMemberOpcode tests MsgMhfListMember Opcode
func TestMsgMhfListMemberOpcode(t *testing.T) {
pkt := &MsgMhfListMember{}
if pkt.Opcode() != network.MSG_MHF_LIST_MEMBER {
t.Errorf("Opcode() = %s, want MSG_MHF_LIST_MEMBER", pkt.Opcode())
}
}
// TestMsgMhfOprMemberOpcode tests MsgMhfOprMember Opcode
func TestMsgMhfOprMemberOpcode(t *testing.T) {
pkt := &MsgMhfOprMember{}
if pkt.Opcode() != network.MSG_MHF_OPR_MEMBER {
t.Errorf("Opcode() = %s, want MSG_MHF_OPR_MEMBER", pkt.Opcode())
}
}
// TestMsgMhfEnumerateDistItemOpcode tests MsgMhfEnumerateDistItem Opcode
func TestMsgMhfEnumerateDistItemOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateDistItem{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfApplyDistItemOpcode tests MsgMhfApplyDistItem Opcode
func TestMsgMhfApplyDistItemOpcode(t *testing.T) {
pkt := &MsgMhfApplyDistItem{}
if pkt.Opcode() != network.MSG_MHF_APPLY_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireDistItemOpcode tests MsgMhfAcquireDistItem Opcode
func TestMsgMhfAcquireDistItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireDistItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfGetDistDescriptionOpcode tests MsgMhfGetDistDescription Opcode
func TestMsgMhfGetDistDescriptionOpcode(t *testing.T) {
pkt := &MsgMhfGetDistDescription{}
if pkt.Opcode() != network.MSG_MHF_GET_DIST_DESCRIPTION {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_DIST_DESCRIPTION", pkt.Opcode())
}
}
// TestMsgMhfSendMailOpcode tests MsgMhfSendMail Opcode
func TestMsgMhfSendMailOpcode(t *testing.T) {
pkt := &MsgMhfSendMail{}
if pkt.Opcode() != network.MSG_MHF_SEND_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_SEND_MAIL", pkt.Opcode())
}
}
// TestMsgMhfReadMailOpcode tests MsgMhfReadMail Opcode
func TestMsgMhfReadMailOpcode(t *testing.T) {
pkt := &MsgMhfReadMail{}
if pkt.Opcode() != network.MSG_MHF_READ_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_READ_MAIL", pkt.Opcode())
}
}
// TestMsgMhfListMailOpcode tests MsgMhfListMail Opcode
func TestMsgMhfListMailOpcode(t *testing.T) {
pkt := &MsgMhfListMail{}
if pkt.Opcode() != network.MSG_MHF_LIST_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_LIST_MAIL", pkt.Opcode())
}
}
// TestMsgMhfOprtMailOpcode tests MsgMhfOprtMail Opcode
func TestMsgMhfOprtMailOpcode(t *testing.T) {
pkt := &MsgMhfOprtMail{}
if pkt.Opcode() != network.MSG_MHF_OPRT_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_OPRT_MAIL", pkt.Opcode())
}
}
// TestMsgMhfLoadFavoriteQuestOpcode tests MsgMhfLoadFavoriteQuest Opcode
func TestMsgMhfLoadFavoriteQuestOpcode(t *testing.T) {
pkt := &MsgMhfLoadFavoriteQuest{}
if pkt.Opcode() != network.MSG_MHF_LOAD_FAVORITE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_LOAD_FAVORITE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfSaveFavoriteQuestOpcode tests MsgMhfSaveFavoriteQuest Opcode
func TestMsgMhfSaveFavoriteQuestOpcode(t *testing.T) {
pkt := &MsgMhfSaveFavoriteQuest{}
if pkt.Opcode() != network.MSG_MHF_SAVE_FAVORITE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_SAVE_FAVORITE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfRegisterEventOpcode tests MsgMhfRegisterEvent Opcode
func TestMsgMhfRegisterEventOpcode(t *testing.T) {
pkt := &MsgMhfRegisterEvent{}
if pkt.Opcode() != network.MSG_MHF_REGISTER_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_REGISTER_EVENT", pkt.Opcode())
}
}
// TestMsgMhfReleaseEventOpcode tests MsgMhfReleaseEvent Opcode
func TestMsgMhfReleaseEventOpcode(t *testing.T) {
pkt := &MsgMhfReleaseEvent{}
if pkt.Opcode() != network.MSG_MHF_RELEASE_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_RELEASE_EVENT", pkt.Opcode())
}
}
// TestMsgMhfTransitMessageOpcode tests MsgMhfTransitMessage Opcode
func TestMsgMhfTransitMessageOpcode(t *testing.T) {
pkt := &MsgMhfTransitMessage{}
if pkt.Opcode() != network.MSG_MHF_TRANSIT_MESSAGE {
t.Errorf("Opcode() = %s, want MSG_MHF_TRANSIT_MESSAGE", pkt.Opcode())
}
}
// TestMsgMhfPresentBoxOpcode tests MsgMhfPresentBox Opcode
func TestMsgMhfPresentBoxOpcode(t *testing.T) {
pkt := &MsgMhfPresentBox{}
if pkt.Opcode() != network.MSG_MHF_PRESENT_BOX {
t.Errorf("Opcode() = %s, want MSG_MHF_PRESENT_BOX", pkt.Opcode())
}
}
// TestMsgMhfServerCommandOpcode tests MsgMhfServerCommand Opcode
func TestMsgMhfServerCommandOpcode(t *testing.T) {
pkt := &MsgMhfServerCommand{}
if pkt.Opcode() != network.MSG_MHF_SERVER_COMMAND {
t.Errorf("Opcode() = %s, want MSG_MHF_SERVER_COMMAND", pkt.Opcode())
}
}
// TestMsgMhfShutClientOpcode tests MsgMhfShutClient Opcode
func TestMsgMhfShutClientOpcode(t *testing.T) {
pkt := &MsgMhfShutClient{}
if pkt.Opcode() != network.MSG_MHF_SHUT_CLIENT {
t.Errorf("Opcode() = %s, want MSG_MHF_SHUT_CLIENT", pkt.Opcode())
}
}
// TestMsgMhfAnnounceOpcode tests MsgMhfAnnounce Opcode
func TestMsgMhfAnnounceOpcode(t *testing.T) {
pkt := &MsgMhfAnnounce{}
if pkt.Opcode() != network.MSG_MHF_ANNOUNCE {
t.Errorf("Opcode() = %s, want MSG_MHF_ANNOUNCE", pkt.Opcode())
}
}
// TestMsgMhfSetLoginwindowOpcode tests MsgMhfSetLoginwindow Opcode
func TestMsgMhfSetLoginwindowOpcode(t *testing.T) {
pkt := &MsgMhfSetLoginwindow{}
if pkt.Opcode() != network.MSG_MHF_SET_LOGINWINDOW {
t.Errorf("Opcode() = %s, want MSG_MHF_SET_LOGINWINDOW", pkt.Opcode())
}
}
// TestMsgMhfGetCaUniqueIDOpcode tests MsgMhfGetCaUniqueID Opcode
func TestMsgMhfGetCaUniqueIDOpcode(t *testing.T) {
pkt := &MsgMhfGetCaUniqueID{}
if pkt.Opcode() != network.MSG_MHF_GET_CA_UNIQUE_ID {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_CA_UNIQUE_ID", pkt.Opcode())
}
}
// TestMsgMhfSetCaAchievementOpcode tests MsgMhfSetCaAchievement Opcode
func TestMsgMhfSetCaAchievementOpcode(t *testing.T) {
pkt := &MsgMhfSetCaAchievement{}
if pkt.Opcode() != network.MSG_MHF_SET_CA_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_SET_CA_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfCaravanMyScoreOpcode tests MsgMhfCaravanMyScore Opcode
func TestMsgMhfCaravanMyScoreOpcode(t *testing.T) {
pkt := &MsgMhfCaravanMyScore{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_MY_SCORE {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_MY_SCORE", pkt.Opcode())
}
}
// TestMsgMhfCaravanRankingOpcode tests MsgMhfCaravanRanking Opcode
func TestMsgMhfCaravanRankingOpcode(t *testing.T) {
pkt := &MsgMhfCaravanRanking{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_RANKING {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_RANKING", pkt.Opcode())
}
}
// TestMsgMhfCaravanMyRankOpcode tests MsgMhfCaravanMyRank Opcode
func TestMsgMhfCaravanMyRankOpcode(t *testing.T) {
pkt := &MsgMhfCaravanMyRank{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_MY_RANK {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_MY_RANK", pkt.Opcode())
}
}
// TestMsgMhfEnumerateQuestOpcode tests MsgMhfEnumerateQuest Opcode
func TestMsgMhfEnumerateQuestOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateQuest{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfEnumerateEventOpcode tests MsgMhfEnumerateEvent Opcode
func TestMsgMhfEnumerateEventOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateEvent{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_EVENT", pkt.Opcode())
}
}
// TestMsgMhfEnumeratePriceOpcode tests MsgMhfEnumeratePrice Opcode
func TestMsgMhfEnumeratePriceOpcode(t *testing.T) {
pkt := &MsgMhfEnumeratePrice{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_PRICE {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_PRICE", pkt.Opcode())
}
}
// TestMsgMhfEnumerateRankingOpcode tests MsgMhfEnumerateRanking Opcode
func TestMsgMhfEnumerateRankingOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateRanking{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_RANKING {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_RANKING", pkt.Opcode())
}
}
// TestMsgMhfEnumerateOrderOpcode tests MsgMhfEnumerateOrder Opcode
func TestMsgMhfEnumerateOrderOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateOrder{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_ORDER {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_ORDER", pkt.Opcode())
}
}
// TestMsgMhfEnumerateShopOpcode tests MsgMhfEnumerateShop Opcode
func TestMsgMhfEnumerateShopOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateShop{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_SHOP {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_SHOP", pkt.Opcode())
}
}
// TestMsgMhfGetExtraInfoOpcode tests MsgMhfGetExtraInfo Opcode
func TestMsgMhfGetExtraInfoOpcode(t *testing.T) {
pkt := &MsgMhfGetExtraInfo{}
if pkt.Opcode() != network.MSG_MHF_GET_EXTRA_INFO {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_EXTRA_INFO", pkt.Opcode())
}
}
// TestMsgMhfEnumerateItemOpcode tests MsgMhfEnumerateItem Opcode
func TestMsgMhfEnumerateItemOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateItem{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireItemOpcode tests MsgMhfAcquireItem Opcode
func TestMsgMhfAcquireItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfTransferItemOpcode tests MsgMhfTransferItem Opcode
func TestMsgMhfTransferItemOpcode(t *testing.T) {
pkt := &MsgMhfTransferItem{}
if pkt.Opcode() != network.MSG_MHF_TRANSFER_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_TRANSFER_ITEM", pkt.Opcode())
}
}
// TestMsgMhfEntryRookieGuildOpcode tests MsgMhfEntryRookieGuild Opcode
func TestMsgMhfEntryRookieGuildOpcode(t *testing.T) {
pkt := &MsgMhfEntryRookieGuild{}
if pkt.Opcode() != network.MSG_MHF_ENTRY_ROOKIE_GUILD {
t.Errorf("Opcode() = %s, want MSG_MHF_ENTRY_ROOKIE_GUILD", pkt.Opcode())
}
}
// TestMsgCaExchangeItemOpcode tests MsgCaExchangeItem Opcode
func TestMsgCaExchangeItemOpcode(t *testing.T) {
pkt := &MsgCaExchangeItem{}
if pkt.Opcode() != network.MSG_CA_EXCHANGE_ITEM {
t.Errorf("Opcode() = %s, want MSG_CA_EXCHANGE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfEnumerateCampaignOpcode tests MsgMhfEnumerateCampaign Opcode
func TestMsgMhfEnumerateCampaignOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateCampaign{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfStateCampaignOpcode tests MsgMhfStateCampaign Opcode
func TestMsgMhfStateCampaignOpcode(t *testing.T) {
pkt := &MsgMhfStateCampaign{}
if pkt.Opcode() != network.MSG_MHF_STATE_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_STATE_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfApplyCampaignOpcode tests MsgMhfApplyCampaign Opcode
func TestMsgMhfApplyCampaignOpcode(t *testing.T) {
pkt := &MsgMhfApplyCampaign{}
if pkt.Opcode() != network.MSG_MHF_APPLY_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfCreateJointOpcode tests MsgMhfCreateJoint Opcode
func TestMsgMhfCreateJointOpcode(t *testing.T) {
pkt := &MsgMhfCreateJoint{}
if pkt.Opcode() != network.MSG_MHF_CREATE_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_CREATE_JOINT", pkt.Opcode())
}
}
// TestMsgMhfOperateJointOpcode tests MsgMhfOperateJoint Opcode
func TestMsgMhfOperateJointOpcode(t *testing.T) {
pkt := &MsgMhfOperateJoint{}
if pkt.Opcode() != network.MSG_MHF_OPERATE_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_OPERATE_JOINT", pkt.Opcode())
}
}
// TestMsgMhfInfoJointOpcode tests MsgMhfInfoJoint Opcode
func TestMsgMhfInfoJointOpcode(t *testing.T) {
pkt := &MsgMhfInfoJoint{}
if pkt.Opcode() != network.MSG_MHF_INFO_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_INFO_JOINT", pkt.Opcode())
}
}
// TestMsgMhfGetCogInfoOpcode tests MsgMhfGetCogInfo Opcode
func TestMsgMhfGetCogInfoOpcode(t *testing.T) {
pkt := &MsgMhfGetCogInfo{}
if pkt.Opcode() != network.MSG_MHF_GET_COG_INFO {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_COG_INFO", pkt.Opcode())
}
}
// TestMsgMhfCheckMonthlyItemOpcode tests MsgMhfCheckMonthlyItem Opcode
func TestMsgMhfCheckMonthlyItemOpcode(t *testing.T) {
pkt := &MsgMhfCheckMonthlyItem{}
if pkt.Opcode() != network.MSG_MHF_CHECK_MONTHLY_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_CHECK_MONTHLY_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireMonthlyItemOpcode tests MsgMhfAcquireMonthlyItem Opcode
func TestMsgMhfAcquireMonthlyItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireMonthlyItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_MONTHLY_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_MONTHLY_ITEM", pkt.Opcode())
}
}
// TestMsgMhfCheckWeeklyStampOpcode tests MsgMhfCheckWeeklyStamp Opcode
func TestMsgMhfCheckWeeklyStampOpcode(t *testing.T) {
pkt := &MsgMhfCheckWeeklyStamp{}
if pkt.Opcode() != network.MSG_MHF_CHECK_WEEKLY_STAMP {
t.Errorf("Opcode() = %s, want MSG_MHF_CHECK_WEEKLY_STAMP", pkt.Opcode())
}
}
// TestMsgMhfExchangeWeeklyStampOpcode tests MsgMhfExchangeWeeklyStamp Opcode
func TestMsgMhfExchangeWeeklyStampOpcode(t *testing.T) {
pkt := &MsgMhfExchangeWeeklyStamp{}
if pkt.Opcode() != network.MSG_MHF_EXCHANGE_WEEKLY_STAMP {
t.Errorf("Opcode() = %s, want MSG_MHF_EXCHANGE_WEEKLY_STAMP", pkt.Opcode())
}
}
// TestMsgMhfCreateMercenaryOpcode tests MsgMhfCreateMercenary Opcode
func TestMsgMhfCreateMercenaryOpcode(t *testing.T) {
pkt := &MsgMhfCreateMercenary{}
if pkt.Opcode() != network.MSG_MHF_CREATE_MERCENARY {
t.Errorf("Opcode() = %s, want MSG_MHF_CREATE_MERCENARY", pkt.Opcode())
}
}
// TestMsgMhfEnumerateMercenaryLogOpcode tests MsgMhfEnumerateMercenaryLog Opcode
func TestMsgMhfEnumerateMercenaryLogOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateMercenaryLog{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_MERCENARY_LOG {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_MERCENARY_LOG", pkt.Opcode())
}
}
// TestMsgMhfEnumerateGuacotOpcode tests MsgMhfEnumerateGuacot Opcode
func TestMsgMhfEnumerateGuacotOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_GUACOT", pkt.Opcode())
}
}
// TestMsgMhfUpdateGuacotOpcode tests MsgMhfUpdateGuacot Opcode
func TestMsgMhfUpdateGuacotOpcode(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_GUACOT", pkt.Opcode())
}
}
// TestMsgMhfEnterTournamentQuestOpcode tests MsgMhfEnterTournamentQuest Opcode
func TestMsgMhfEnterTournamentQuestOpcode(t *testing.T) {
pkt := &MsgMhfEnterTournamentQuest{}
if pkt.Opcode() != network.MSG_MHF_ENTER_TOURNAMENT_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_ENTER_TOURNAMENT_QUEST", pkt.Opcode())
}
}
// TestMsgMhfResetAchievementOpcode tests MsgMhfResetAchievement Opcode
func TestMsgMhfResetAchievementOpcode(t *testing.T) {
pkt := &MsgMhfResetAchievement{}
if pkt.Opcode() != network.MSG_MHF_RESET_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_RESET_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfPaymentAchievementOpcode tests MsgMhfPaymentAchievement Opcode
func TestMsgMhfPaymentAchievementOpcode(t *testing.T) {
pkt := &MsgMhfPaymentAchievement{}
if pkt.Opcode() != network.MSG_MHF_PAYMENT_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_PAYMENT_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfDisplayedAchievementOpcode tests MsgMhfDisplayedAchievement Opcode
func TestMsgMhfDisplayedAchievementOpcode(t *testing.T) {
pkt := &MsgMhfDisplayedAchievement{}
if pkt.Opcode() != network.MSG_MHF_DISPLAYED_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_DISPLAYED_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfGetBbsSnsStatusOpcode tests MsgMhfGetBbsSnsStatus Opcode
func TestMsgMhfGetBbsSnsStatusOpcode(t *testing.T) {
pkt := &MsgMhfGetBbsSnsStatus{}
if pkt.Opcode() != network.MSG_MHF_GET_BBS_SNS_STATUS {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_BBS_SNS_STATUS", pkt.Opcode())
}
}
// TestMsgMhfApplyBbsArticleOpcode tests MsgMhfApplyBbsArticle Opcode
func TestMsgMhfApplyBbsArticleOpcode(t *testing.T) {
pkt := &MsgMhfApplyBbsArticle{}
if pkt.Opcode() != network.MSG_MHF_APPLY_BBS_ARTICLE {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_BBS_ARTICLE", pkt.Opcode())
}
}
// TestMsgMhfGetEtcPointsOpcode tests MsgMhfGetEtcPoints Opcode
func TestMsgMhfGetEtcPointsOpcode(t *testing.T) {
pkt := &MsgMhfGetEtcPoints{}
if pkt.Opcode() != network.MSG_MHF_GET_ETC_POINTS {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_ETC_POINTS", pkt.Opcode())
}
}
// TestMsgMhfUpdateEtcPointOpcode tests MsgMhfUpdateEtcPoint Opcode
func TestMsgMhfUpdateEtcPointOpcode(t *testing.T) {
pkt := &MsgMhfUpdateEtcPoint{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_ETC_POINT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_ETC_POINT", pkt.Opcode())
}
}
// TestAchievementPacketParse tests simple achievement packet parsing
func TestAchievementPacketParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(5) // AchievementID
bf.WriteUint16(100) // Unk1
bf.WriteUint16(200) // Unk2
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AchievementID != 5 {
t.Errorf("AchievementID = %d, want 5", pkt.AchievementID)
}
if pkt.Unk1 != 100 {
t.Errorf("Unk1 = %d, want 100", pkt.Unk1)
}
if pkt.Unk2 != 200 {
t.Errorf("Unk2 = %d, want 200", pkt.Unk2)
}
}

View File

@@ -0,0 +1,301 @@
package mhfpacket
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// callBuildSafe calls Build on the packet, recovering from panics.
// Returns the error from Build, or nil if it panicked (panic is acceptable
// for "Not implemented" stubs).
func callBuildSafe(pkt MHFPacket, bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) (err error, panicked bool) {
defer func() {
if r := recover(); r != nil {
panicked = true
}
}()
err = pkt.Build(bf, ctx)
return err, false
}
// callParseSafe calls Parse on the packet, recovering from panics.
func callParseSafe(pkt MHFPacket, bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) (err error, panicked bool) {
defer func() {
if r := recover(); r != nil {
panicked = true
}
}()
err = pkt.Parse(bf, ctx)
return err, false
}
// TestBuildCoverage_NotImplemented exercises Build() on packet types whose Build
// method is not yet covered. These stubs either return errors.New("NOT IMPLEMENTED")
// or panic("Not implemented"). Both are acceptable outcomes that indicate the
// method was reached.
func TestBuildCoverage_NotImplemented(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
}{
// msg_ca_exchange_item.go
{"MsgCaExchangeItem", &MsgCaExchangeItem{}},
// msg_head.go
{"MsgHead", &MsgHead{}},
// msg_mhf_acquire_cafe_item.go
{"MsgMhfAcquireCafeItem", &MsgMhfAcquireCafeItem{}},
// msg_mhf_acquire_monthly_item.go
{"MsgMhfAcquireMonthlyItem", &MsgMhfAcquireMonthlyItem{}},
// msg_mhf_acquire_ud_item.go
{"MsgMhfAcquireUdItem", &MsgMhfAcquireUdItem{}},
// msg_mhf_announce.go
{"MsgMhfAnnounce", &MsgMhfAnnounce{}},
// msg_mhf_check_monthly_item.go
{"MsgMhfCheckMonthlyItem", &MsgMhfCheckMonthlyItem{}},
// msg_mhf_check_weekly_stamp.go
{"MsgMhfCheckWeeklyStamp", &MsgMhfCheckWeeklyStamp{}},
// msg_mhf_enumerate_festa_member.go
{"MsgMhfEnumerateFestaMember", &MsgMhfEnumerateFestaMember{}},
// msg_mhf_enumerate_inv_guild.go
{"MsgMhfEnumerateInvGuild", &MsgMhfEnumerateInvGuild{}},
// msg_mhf_enumerate_item.go
{"MsgMhfEnumerateItem", &MsgMhfEnumerateItem{}},
// msg_mhf_enumerate_order.go
{"MsgMhfEnumerateOrder", &MsgMhfEnumerateOrder{}},
// msg_mhf_enumerate_quest.go
{"MsgMhfEnumerateQuest", &MsgMhfEnumerateQuest{}},
// msg_mhf_enumerate_ranking.go
{"MsgMhfEnumerateRanking", &MsgMhfEnumerateRanking{}},
// msg_mhf_enumerate_shop.go
{"MsgMhfEnumerateShop", &MsgMhfEnumerateShop{}},
// msg_mhf_enumerate_warehouse.go
{"MsgMhfEnumerateWarehouse", &MsgMhfEnumerateWarehouse{}},
// msg_mhf_exchange_fpoint_2_item.go
{"MsgMhfExchangeFpoint2Item", &MsgMhfExchangeFpoint2Item{}},
// msg_mhf_exchange_item_2_fpoint.go
{"MsgMhfExchangeItem2Fpoint", &MsgMhfExchangeItem2Fpoint{}},
// msg_mhf_exchange_weekly_stamp.go
{"MsgMhfExchangeWeeklyStamp", &MsgMhfExchangeWeeklyStamp{}},
// msg_mhf_generate_ud_guild_map.go
{"MsgMhfGenerateUdGuildMap", &MsgMhfGenerateUdGuildMap{}},
// msg_mhf_get_boost_time.go
{"MsgMhfGetBoostTime", &MsgMhfGetBoostTime{}},
// msg_mhf_get_boost_time_limit.go
{"MsgMhfGetBoostTimeLimit", &MsgMhfGetBoostTimeLimit{}},
// msg_mhf_get_cafe_duration.go
{"MsgMhfGetCafeDuration", &MsgMhfGetCafeDuration{}},
// msg_mhf_get_cafe_duration_bonus_info.go
{"MsgMhfGetCafeDurationBonusInfo", &MsgMhfGetCafeDurationBonusInfo{}},
// msg_mhf_get_cog_info.go
{"MsgMhfGetCogInfo", &MsgMhfGetCogInfo{}},
// msg_mhf_get_gacha_point.go
{"MsgMhfGetGachaPoint", &MsgMhfGetGachaPoint{}},
// msg_mhf_get_gem_info.go
{"MsgMhfGetGemInfo", &MsgMhfGetGemInfo{}},
// msg_mhf_get_kiju_info.go
{"MsgMhfGetKijuInfo", &MsgMhfGetKijuInfo{}},
// msg_mhf_get_myhouse_info.go
{"MsgMhfGetMyhouseInfo", &MsgMhfGetMyhouseInfo{}},
// msg_mhf_get_notice.go
{"MsgMhfGetNotice", &MsgMhfGetNotice{}},
// msg_mhf_get_tower_info.go
{"MsgMhfGetTowerInfo", &MsgMhfGetTowerInfo{}},
// msg_mhf_get_ud_info.go
{"MsgMhfGetUdInfo", &MsgMhfGetUdInfo{}},
// msg_mhf_get_ud_schedule.go
{"MsgMhfGetUdSchedule", &MsgMhfGetUdSchedule{}},
// msg_mhf_get_weekly_schedule.go
{"MsgMhfGetWeeklySchedule", &MsgMhfGetWeeklySchedule{}},
// msg_mhf_guild_huntdata.go
{"MsgMhfGuildHuntdata", &MsgMhfGuildHuntdata{}},
// msg_mhf_info_joint.go
{"MsgMhfInfoJoint", &MsgMhfInfoJoint{}},
// msg_mhf_load_deco_myset.go
{"MsgMhfLoadDecoMyset", &MsgMhfLoadDecoMyset{}},
// msg_mhf_load_guild_adventure.go
{"MsgMhfLoadGuildAdventure", &MsgMhfLoadGuildAdventure{}},
// msg_mhf_load_guild_cooking.go
{"MsgMhfLoadGuildCooking", &MsgMhfLoadGuildCooking{}},
// msg_mhf_load_hunter_navi.go
{"MsgMhfLoadHunterNavi", &MsgMhfLoadHunterNavi{}},
// msg_mhf_load_otomo_airou.go
{"MsgMhfLoadOtomoAirou", &MsgMhfLoadOtomoAirou{}},
// msg_mhf_load_partner.go
{"MsgMhfLoadPartner", &MsgMhfLoadPartner{}},
// msg_mhf_load_plate_box.go
{"MsgMhfLoadPlateBox", &MsgMhfLoadPlateBox{}},
// msg_mhf_load_plate_data.go
{"MsgMhfLoadPlateData", &MsgMhfLoadPlateData{}},
// msg_mhf_post_notice.go
{"MsgMhfPostNotice", &MsgMhfPostNotice{}},
// msg_mhf_post_tower_info.go
{"MsgMhfPostTowerInfo", &MsgMhfPostTowerInfo{}},
// msg_mhf_reserve10f.go
{"MsgMhfReserve10F", &MsgMhfReserve10F{}},
// msg_mhf_server_command.go
{"MsgMhfServerCommand", &MsgMhfServerCommand{}},
// msg_mhf_set_loginwindow.go
{"MsgMhfSetLoginwindow", &MsgMhfSetLoginwindow{}},
// msg_mhf_shut_client.go
{"MsgMhfShutClient", &MsgMhfShutClient{}},
// msg_mhf_stampcard_stamp.go
{"MsgMhfStampcardStamp", &MsgMhfStampcardStamp{}},
// msg_sys_add_object.go
{"MsgSysAddObject", &MsgSysAddObject{}},
// msg_sys_back_stage.go
{"MsgSysBackStage", &MsgSysBackStage{}},
// msg_sys_cast_binary.go
{"MsgSysCastBinary", &MsgSysCastBinary{}},
// msg_sys_create_semaphore.go
{"MsgSysCreateSemaphore", &MsgSysCreateSemaphore{}},
// msg_sys_create_stage.go
{"MsgSysCreateStage", &MsgSysCreateStage{}},
// msg_sys_del_object.go
{"MsgSysDelObject", &MsgSysDelObject{}},
// msg_sys_disp_object.go
{"MsgSysDispObject", &MsgSysDispObject{}},
// msg_sys_echo.go
{"MsgSysEcho", &MsgSysEcho{}},
// msg_sys_enter_stage.go
{"MsgSysEnterStage", &MsgSysEnterStage{}},
// msg_sys_enumerate_client.go
{"MsgSysEnumerateClient", &MsgSysEnumerateClient{}},
// msg_sys_extend_threshold.go
{"MsgSysExtendThreshold", &MsgSysExtendThreshold{}},
// msg_sys_get_stage_binary.go
{"MsgSysGetStageBinary", &MsgSysGetStageBinary{}},
// msg_sys_hide_object.go
{"MsgSysHideObject", &MsgSysHideObject{}},
// msg_sys_leave_stage.go
{"MsgSysLeaveStage", &MsgSysLeaveStage{}},
// msg_sys_lock_stage.go
{"MsgSysLockStage", &MsgSysLockStage{}},
// msg_sys_login.go
{"MsgSysLogin", &MsgSysLogin{}},
// msg_sys_move_stage.go
{"MsgSysMoveStage", &MsgSysMoveStage{}},
// msg_sys_set_stage_binary.go
{"MsgSysSetStageBinary", &MsgSysSetStageBinary{}},
// msg_sys_set_stage_pass.go
{"MsgSysSetStagePass", &MsgSysSetStagePass{}},
// msg_sys_set_status.go
{"MsgSysSetStatus", &MsgSysSetStatus{}},
// msg_sys_wait_stage_binary.go
{"MsgSysWaitStageBinary", &MsgSysWaitStageBinary{}},
// Reserve files - sys reserves
{"MsgSysReserve01", &MsgSysReserve01{}},
{"MsgSysReserve02", &MsgSysReserve02{}},
{"MsgSysReserve03", &MsgSysReserve03{}},
{"MsgSysReserve04", &MsgSysReserve04{}},
{"MsgSysReserve05", &MsgSysReserve05{}},
{"MsgSysReserve06", &MsgSysReserve06{}},
{"MsgSysReserve07", &MsgSysReserve07{}},
{"MsgSysReserve0C", &MsgSysReserve0C{}},
{"MsgSysReserve0D", &MsgSysReserve0D{}},
{"MsgSysReserve0E", &MsgSysReserve0E{}},
{"MsgSysReserve4A", &MsgSysReserve4A{}},
{"MsgSysReserve4B", &MsgSysReserve4B{}},
{"MsgSysReserve4C", &MsgSysReserve4C{}},
{"MsgSysReserve4D", &MsgSysReserve4D{}},
{"MsgSysReserve4E", &MsgSysReserve4E{}},
{"MsgSysReserve4F", &MsgSysReserve4F{}},
{"MsgSysReserve55", &MsgSysReserve55{}},
{"MsgSysReserve56", &MsgSysReserve56{}},
{"MsgSysReserve57", &MsgSysReserve57{}},
{"MsgSysReserve5C", &MsgSysReserve5C{}},
{"MsgSysReserve5E", &MsgSysReserve5E{}},
{"MsgSysReserve5F", &MsgSysReserve5F{}},
{"MsgSysReserve71", &MsgSysReserve71{}},
{"MsgSysReserve72", &MsgSysReserve72{}},
{"MsgSysReserve73", &MsgSysReserve73{}},
{"MsgSysReserve74", &MsgSysReserve74{}},
{"MsgSysReserve75", &MsgSysReserve75{}},
{"MsgSysReserve76", &MsgSysReserve76{}},
{"MsgSysReserve77", &MsgSysReserve77{}},
{"MsgSysReserve78", &MsgSysReserve78{}},
{"MsgSysReserve79", &MsgSysReserve79{}},
{"MsgSysReserve7A", &MsgSysReserve7A{}},
{"MsgSysReserve7B", &MsgSysReserve7B{}},
{"MsgSysReserve7C", &MsgSysReserve7C{}},
{"MsgSysReserve7E", &MsgSysReserve7E{}},
{"MsgSysReserve180", &MsgSysReserve180{}},
{"MsgSysReserve188", &MsgSysReserve188{}},
{"MsgSysReserve18B", &MsgSysReserve18B{}},
{"MsgSysReserve18E", &MsgSysReserve18E{}},
{"MsgSysReserve18F", &MsgSysReserve18F{}},
{"MsgSysReserve192", &MsgSysReserve192{}},
{"MsgSysReserve193", &MsgSysReserve193{}},
{"MsgSysReserve194", &MsgSysReserve194{}},
{"MsgSysReserve19B", &MsgSysReserve19B{}},
{"MsgSysReserve19E", &MsgSysReserve19E{}},
{"MsgSysReserve19F", &MsgSysReserve19F{}},
{"MsgSysReserve1A4", &MsgSysReserve1A4{}},
{"MsgSysReserve1A6", &MsgSysReserve1A6{}},
{"MsgSysReserve1A7", &MsgSysReserve1A7{}},
{"MsgSysReserve1A8", &MsgSysReserve1A8{}},
{"MsgSysReserve1A9", &MsgSysReserve1A9{}},
{"MsgSysReserve1AA", &MsgSysReserve1AA{}},
{"MsgSysReserve1AB", &MsgSysReserve1AB{}},
{"MsgSysReserve1AC", &MsgSysReserve1AC{}},
{"MsgSysReserve1AD", &MsgSysReserve1AD{}},
{"MsgSysReserve1AE", &MsgSysReserve1AE{}},
{"MsgSysReserve1AF", &MsgSysReserve1AF{}},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err, panicked := callBuildSafe(tt.pkt, bf, ctx)
if panicked {
// Build panicked with "Not implemented" - this is acceptable
// and still exercises the code path for coverage.
return
}
if err == nil {
// Build succeeded (some packets may have implemented Build)
return
}
// Build returned an error, which is expected for NOT IMPLEMENTED stubs
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Build() returned unexpected error: %v", err)
}
})
}
}
// TestParseCoverage_NotImplemented exercises Parse() on packet types whose Parse
// method returns "NOT IMPLEMENTED" and is not yet covered by existing tests.
func TestParseCoverage_NotImplemented(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
}{
// msg_mhf_acquire_tournament.go - Parse returns NOT IMPLEMENTED
{"MsgMhfAcquireTournament", &MsgMhfAcquireTournament{}},
// msg_mhf_entry_tournament.go - Parse returns NOT IMPLEMENTED
{"MsgMhfEntryTournament", &MsgMhfEntryTournament{}},
// msg_mhf_update_guild.go - Parse returns NOT IMPLEMENTED
{"MsgMhfUpdateGuild", &MsgMhfUpdateGuild{}},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err, panicked := callParseSafe(tt.pkt, bf, ctx)
if panicked {
return
}
if err == nil {
return
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() returned unexpected error: %v", err)
}
})
}
}

View File

@@ -0,0 +1,880 @@
package mhfpacket
import (
"bytes"
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestParseLargeMsgSysUpdateRightBuild tests Build for MsgSysUpdateRight (no Parse implementation).
func TestParseLargeMsgSysUpdateRightBuild(t *testing.T) {
ctx := &clientctx.ClientContext{}
original := &MsgSysUpdateRight{
ClientRespAckHandle: 0x12345678,
Bitfield: 0xDEADBEEF,
Rights: nil,
UnkSize: 0,
}
bf := byteframe.NewByteFrame()
if err := original.Build(bf, ctx); err != nil {
t.Fatalf("Build() error = %v", err)
}
// Verify binary output manually:
// uint32 ClientRespAckHandle + uint32 Bitfield + uint16 Rights count(0) + uint16 padding(0) + ps.Uint16 empty string(uint16(1) + 0x00)
data := bf.Data()
if len(data) < 12 {
t.Fatalf("Build() wrote %d bytes, want at least 12", len(data))
}
bf.Seek(0, io.SeekStart)
if bf.ReadUint32() != 0x12345678 {
t.Error("ClientRespAckHandle mismatch")
}
if bf.ReadUint32() != 0xDEADBEEF {
t.Error("Bitfield mismatch")
}
if bf.ReadUint16() != 0 {
t.Error("Rights count should be 0")
}
}
// TestParseLargeMsgMhfOperateWarehouse tests Parse for MsgMhfOperateWarehouse.
func TestParseLargeMsgMhfOperateWarehouse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(1) // Operation
bf.WriteUint8(0) // BoxType = item
bf.WriteUint8(2) // BoxIndex
bf.WriteUint8(8) // lenName (unused but read)
bf.WriteUint16(0) // Unk
bf.WriteBytes([]byte("TestBox"))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateWarehouse{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Operation != 1 {
t.Errorf("Operation = %d, want 1", pkt.Operation)
}
if pkt.BoxType != 0 {
t.Errorf("BoxType = %d, want 0", pkt.BoxType)
}
if pkt.BoxIndex != 2 {
t.Errorf("BoxIndex = %d, want 2", pkt.BoxIndex)
}
if pkt.Name != "TestBox" {
t.Errorf("Name = %q, want %q", pkt.Name, "TestBox")
}
}
// TestParseLargeMsgMhfOperateWarehouseEquip tests Parse for MsgMhfOperateWarehouse with equip box type.
func TestParseLargeMsgMhfOperateWarehouseEquip(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(42) // AckHandle
bf.WriteUint8(2) // Operation
bf.WriteUint8(1) // BoxType = equip
bf.WriteUint8(0) // BoxIndex
bf.WriteUint8(5) // lenName
bf.WriteUint16(0) // Unk
bf.WriteBytes([]byte("Arms"))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateWarehouse{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.BoxType != 1 {
t.Errorf("BoxType = %d, want 1", pkt.BoxType)
}
if pkt.Name != "Arms" {
t.Errorf("Name = %q, want %q", pkt.Name, "Arms")
}
}
// TestParseLargeMsgMhfLoadHouse tests Parse for MsgMhfLoadHouse.
func TestParseLargeMsgMhfLoadHouse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
charID uint32
destination uint8
checkPass bool
password string
}{
{"with password", 0xAABBCCDD, 12345, 1, true, "pass123"},
{"no password", 0x11111111, 0, 0, false, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.charID)
bf.WriteUint8(tt.destination)
bf.WriteBool(tt.checkPass)
bf.WriteUint16(0) // Unk (hardcoded 0)
bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
bf.WriteBytes([]byte(tt.password))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfLoadHouse{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
if pkt.CharID != tt.charID {
t.Errorf("CharID = %d, want %d", pkt.CharID, tt.charID)
}
if pkt.Destination != tt.destination {
t.Errorf("Destination = %d, want %d", pkt.Destination, tt.destination)
}
if pkt.CheckPass != tt.checkPass {
t.Errorf("CheckPass = %v, want %v", pkt.CheckPass, tt.checkPass)
}
if pkt.Password != tt.password {
t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
}
})
}
}
// TestParseLargeMsgMhfSendMail tests Parse for MsgMhfSendMail.
func TestParseLargeMsgMhfSendMail(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(99999) // RecipientID
bf.WriteUint16(6) // SubjectLength
bf.WriteUint16(12) // BodyLength
bf.WriteUint32(5) // Quantity
bf.WriteUint16(1001) // ItemID
bf.WriteBytes([]byte("Hello"))
bf.WriteUint8(0) // null terminator for Subject
bf.WriteBytes([]byte("Hello World"))
bf.WriteUint8(0) // null terminator for Body
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSendMail{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.RecipientID != 99999 {
t.Errorf("RecipientID = %d, want 99999", pkt.RecipientID)
}
if pkt.SubjectLength != 6 {
t.Errorf("SubjectLength = %d, want 6", pkt.SubjectLength)
}
if pkt.BodyLength != 12 {
t.Errorf("BodyLength = %d, want 12", pkt.BodyLength)
}
if pkt.Quantity != 5 {
t.Errorf("Quantity = %d, want 5", pkt.Quantity)
}
if pkt.ItemID != 1001 {
t.Errorf("ItemID = %d, want 1001", pkt.ItemID)
}
if pkt.Subject != "Hello" {
t.Errorf("Subject = %q, want %q", pkt.Subject, "Hello")
}
if pkt.Body != "Hello World" {
t.Errorf("Body = %q, want %q", pkt.Body, "Hello World")
}
}
// TestParseLargeMsgMhfApplyBbsArticle tests Parse for MsgMhfApplyBbsArticle.
func TestParseLargeMsgMhfApplyBbsArticle(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(42) // Unk0
// Unk1: 16 bytes
unk1 := make([]byte, 16)
for i := range unk1 {
unk1[i] = byte(i + 1)
}
bf.WriteBytes(unk1)
// Name: 32 bytes (padded with nulls) - uses bfutil.UpToNull
nameBytes := make([]byte, 32)
copy(nameBytes, "Hunter")
bf.WriteBytes(nameBytes)
// Title: 128 bytes (padded with nulls)
titleBytes := make([]byte, 128)
copy(titleBytes, "My Post Title")
bf.WriteBytes(titleBytes)
// Description: 256 bytes (padded with nulls)
descBytes := make([]byte, 256)
copy(descBytes, "This is a description")
bf.WriteBytes(descBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfApplyBbsArticle{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.Unk0 != 42 {
t.Errorf("Unk0 = %d, want 42", pkt.Unk0)
}
if !bytes.Equal(pkt.Unk1, unk1) {
t.Error("Unk1 mismatch")
}
if pkt.Name != "Hunter" {
t.Errorf("Name = %q, want %q", pkt.Name, "Hunter")
}
if pkt.Title != "My Post Title" {
t.Errorf("Title = %q, want %q", pkt.Title, "My Post Title")
}
if pkt.Description != "This is a description" {
t.Errorf("Description = %q, want %q", pkt.Description, "This is a description")
}
}
// TestParseLargeMsgMhfChargeFesta tests Parse for MsgMhfChargeFesta.
func TestParseLargeMsgMhfChargeFesta(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(3) // soul count
bf.WriteUint16(10) // soul value 1
bf.WriteUint16(20) // soul value 2
bf.WriteUint16(30) // soul value 3
bf.WriteUint8(0) // Unk
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfChargeFesta{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.FestaID != 100 {
t.Errorf("FestaID = %d, want 100", pkt.FestaID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
if len(pkt.Souls) != 3 {
t.Fatalf("Souls len = %d, want 3", len(pkt.Souls))
}
expectedSouls := []uint16{10, 20, 30}
for i, v := range expectedSouls {
if pkt.Souls[i] != v {
t.Errorf("Souls[%d] = %d, want %d", i, pkt.Souls[i], v)
}
}
}
// TestParseLargeMsgMhfChargeFestaZeroSouls tests Parse for MsgMhfChargeFesta with zero soul entries.
func TestParseLargeMsgMhfChargeFestaZeroSouls(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // FestaID
bf.WriteUint32(0) // GuildID
bf.WriteUint16(0) // soul count = 0
bf.WriteUint8(0) // Unk
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfChargeFesta{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(pkt.Souls) != 0 {
t.Errorf("Souls len = %d, want 0", len(pkt.Souls))
}
}
// TestParseLargeMsgMhfOperateJoint tests Parse for MsgMhfOperateJoint.
// Parse reads: uint32 AckHandle, uint32 AllianceID, uint32 GuildID, uint8 Action,
// uint8 dataLen, 4 bytes Data1, dataLen bytes Data2.
func TestParseLargeMsgMhfOperateJoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(100) // AllianceID
bf.WriteUint32(200) // GuildID
bf.WriteUint8(0x01) // Action = OPERATE_JOINT_DISBAND
bf.WriteUint8(3) // dataLen = 3
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC, 0xDD}) // Data1 (always 4 bytes)
bf.WriteBytes([]byte{0x01, 0x02, 0x03}) // Data2 (dataLen bytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateJoint{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.AllianceID != 100 {
t.Errorf("AllianceID = %d, want 100", pkt.AllianceID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
if pkt.Action != OPERATE_JOINT_DISBAND {
t.Errorf("Action = %d, want %d", pkt.Action, OPERATE_JOINT_DISBAND)
}
if pkt.Data1 == nil {
t.Fatal("Data1 is nil")
}
if pkt.Data2 == nil {
t.Fatal("Data2 is nil")
}
}
// TestParseLargeMsgMhfOperationInvGuild tests Parse for MsgMhfOperationInvGuild.
func TestParseLargeMsgMhfOperationInvGuild(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(1) // Operation
bf.WriteUint8(5) // ActiveHours
bf.WriteUint8(7) // DaysActive
bf.WriteUint8(3) // PlayStyle
bf.WriteUint8(2) // GuildRequest
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperationInvGuild{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Operation != 1 {
t.Errorf("Operation = %d, want 1", pkt.Operation)
}
if pkt.ActiveHours != 5 {
t.Errorf("ActiveHours = %d, want 5", pkt.ActiveHours)
}
if pkt.DaysActive != 7 {
t.Errorf("DaysActive = %d, want 7", pkt.DaysActive)
}
if pkt.PlayStyle != 3 {
t.Errorf("PlayStyle = %d, want 3", pkt.PlayStyle)
}
if pkt.GuildRequest != 2 {
t.Errorf("GuildRequest = %d, want 2", pkt.GuildRequest)
}
}
// TestParseLargeMsgMhfSaveMercenary tests Parse for MsgMhfSaveMercenary.
func TestParseLargeMsgMhfSaveMercenary(t *testing.T) {
mercData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(0) // lenData (skipped)
bf.WriteUint32(5000) // GCP
bf.WriteUint32(42) // PactMercID
bf.WriteUint32(uint32(len(mercData))) // dataSize
bf.WriteUint32(0) // Merc index (skipped)
bf.WriteBytes(mercData)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveMercenary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.GCP != 5000 {
t.Errorf("GCP = %d, want 5000", pkt.GCP)
}
if pkt.PactMercID != 42 {
t.Errorf("PactMercID = %d, want 42", pkt.PactMercID)
}
if !bytes.Equal(pkt.MercData, mercData) {
t.Errorf("MercData = %v, want %v", pkt.MercData, mercData)
}
}
// TestParseLargeMsgMhfUpdateHouse tests Parse for MsgMhfUpdateHouse.
func TestParseLargeMsgMhfUpdateHouse(t *testing.T) {
tests := []struct {
name string
state uint8
password string
}{
{"with password", 1, "secret"},
{"empty password", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint8(tt.state) // State
bf.WriteUint8(1) // Unk1
bf.WriteUint16(0) // Unk2
bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
bf.WriteBytes([]byte(tt.password))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateHouse{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.State != tt.state {
t.Errorf("State = %d, want %d", pkt.State, tt.state)
}
if pkt.Unk1 != 1 {
t.Errorf("Unk1 = %d, want 1", pkt.Unk1)
}
if pkt.Password != tt.password {
t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
}
})
}
}
// TestParseLargeMsgSysCreateAcquireSemaphore tests Parse for MsgSysCreateAcquireSemaphore.
func TestParseLargeMsgSysCreateAcquireSemaphore(t *testing.T) {
semID := "stage_001"
semBytes := make([]byte, len(semID)+1) // include space for null if needed
copy(semBytes, semID)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint16(100) // Unk0
bf.WriteUint8(4) // PlayerCount
bf.WriteUint8(uint8(len(semBytes))) // SemaphoreIDLength
bf.WriteBytes(semBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateAcquireSemaphore{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.Unk0 != 100 {
t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
}
if pkt.PlayerCount != 4 {
t.Errorf("PlayerCount = %d, want 4", pkt.PlayerCount)
}
if pkt.SemaphoreID != semID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, semID)
}
}
// TestParseLargeMsgMhfOperateGuild tests Parse for MsgMhfOperateGuild.
func TestParseLargeMsgMhfOperateGuild(t *testing.T) {
dataPayload := []byte{0x10, 0x20, 0x30, 0x40, 0x50}
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint32(999) // GuildID
bf.WriteUint8(0x09) // Action = OperateGuildUpdateComment
bf.WriteUint8(uint8(len(dataPayload))) // dataLen
bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04}) // Data1 (always 4 bytes)
bf.WriteBytes(dataPayload) // Data2 (dataLen bytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateGuild{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.GuildID != 999 {
t.Errorf("GuildID = %d, want 999", pkt.GuildID)
}
if pkt.Action != OperateGuildUpdateComment {
t.Errorf("Action = %d, want %d", pkt.Action, OperateGuildUpdateComment)
}
if pkt.Data1 == nil {
t.Fatal("Data1 is nil")
}
if pkt.Data2 == nil {
t.Fatal("Data2 is nil")
}
data2Bytes := pkt.Data2.Data()
if !bytes.Equal(data2Bytes, dataPayload) {
t.Errorf("Data2 = %v, want %v", data2Bytes, dataPayload)
}
}
// TestParseLargeMsgMhfReadBeatLevel tests Parse for MsgMhfReadBeatLevel.
func TestParseLargeMsgMhfReadBeatLevel(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(1) // Unk0
bf.WriteUint32(4) // ValidIDCount
// Write 16 uint32 IDs
ids := [16]uint32{0x74, 0x6B, 0x02, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for _, id := range ids {
bf.WriteUint32(id)
}
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReadBeatLevel{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 1 {
t.Errorf("Unk0 = %d, want 1", pkt.Unk0)
}
if pkt.ValidIDCount != 4 {
t.Errorf("ValidIDCount = %d, want 4", pkt.ValidIDCount)
}
for i, id := range ids {
if pkt.IDs[i] != id {
t.Errorf("IDs[%d] = 0x%X, want 0x%X", i, pkt.IDs[i], id)
}
}
}
// TestParseLargeMsgSysCreateObject tests Parse for MsgSysCreateObject.
func TestParseLargeMsgSysCreateObject(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
x, y, z float32
unk0 uint32
}{
{"origin", 1, 0.0, 0.0, 0.0, 0},
{"typical", 0x12345678, 1.5, 2.5, 3.5, 42},
{"negative coords", 0xFFFFFFFF, -100.25, 200.75, -300.125, 0xDEADBEEF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteFloat32(tt.x)
bf.WriteFloat32(tt.y)
bf.WriteFloat32(tt.z)
bf.WriteUint32(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateObject{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
if pkt.X != tt.x {
t.Errorf("X = %f, want %f", pkt.X, tt.x)
}
if pkt.Y != tt.y {
t.Errorf("Y = %f, want %f", pkt.Y, tt.y)
}
if pkt.Z != tt.z {
t.Errorf("Z = %f, want %f", pkt.Z, tt.z)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseLargeMsgSysLockGlobalSema tests Parse for MsgSysLockGlobalSema.
func TestParseLargeMsgSysLockGlobalSema(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint16(8) // UserIDLength
bf.WriteUint16(11) // ServerChannelIDLength
bf.WriteBytes([]byte("user123"))
bf.WriteUint8(0) // null terminator
bf.WriteBytes([]byte("channel_01"))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockGlobalSema{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.UserIDLength != 8 {
t.Errorf("UserIDLength = %d, want 8", pkt.UserIDLength)
}
if pkt.ServerChannelIDLength != 11 {
t.Errorf("ServerChannelIDLength = %d, want 11", pkt.ServerChannelIDLength)
}
if pkt.UserIDString != "user123" {
t.Errorf("UserIDString = %q, want %q", pkt.UserIDString, "user123")
}
if pkt.ServerChannelIDString != "channel_01" {
t.Errorf("ServerChannelIDString = %q, want %q", pkt.ServerChannelIDString, "channel_01")
}
}
// TestParseLargeMsgMhfCreateJoint tests Parse for MsgMhfCreateJoint.
func TestParseLargeMsgMhfCreateJoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(500) // GuildID
bf.WriteUint32(15) // len (unused)
bf.WriteBytes([]byte("Alliance01"))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfCreateJoint{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.GuildID != 500 {
t.Errorf("GuildID = %d, want 500", pkt.GuildID)
}
if pkt.Name != "Alliance01" {
t.Errorf("Name = %q, want %q", pkt.Name, "Alliance01")
}
}
// TestParseLargeMsgMhfGetUdTacticsRemainingPoint tests Parse for MsgMhfGetUdTacticsRemainingPoint.
func TestParseLargeMsgMhfGetUdTacticsRemainingPoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(100) // Unk0
bf.WriteUint32(200) // Unk1
bf.WriteUint32(300) // Unk2
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdTacticsRemainingPoint{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 100 {
t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
}
if pkt.Unk1 != 200 {
t.Errorf("Unk1 = %d, want 200", pkt.Unk1)
}
if pkt.Unk2 != 300 {
t.Errorf("Unk2 = %d, want 300", pkt.Unk2)
}
}
// TestParseLargeMsgMhfPostCafeDurationBonusReceived tests Parse for MsgMhfPostCafeDurationBonusReceived.
func TestParseLargeMsgMhfPostCafeDurationBonusReceived(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint32(3) // count
bf.WriteUint32(1001) // CafeBonusID[0]
bf.WriteUint32(1002) // CafeBonusID[1]
bf.WriteUint32(1003) // CafeBonusID[2]
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostCafeDurationBonusReceived{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if len(pkt.CafeBonusID) != 3 {
t.Fatalf("CafeBonusID len = %d, want 3", len(pkt.CafeBonusID))
}
expected := []uint32{1001, 1002, 1003}
for i, v := range expected {
if pkt.CafeBonusID[i] != v {
t.Errorf("CafeBonusID[%d] = %d, want %d", i, pkt.CafeBonusID[i], v)
}
}
}
// TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty tests Parse with zero IDs.
func TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // count = 0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostCafeDurationBonusReceived{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(pkt.CafeBonusID) != 0 {
t.Errorf("CafeBonusID len = %d, want 0", len(pkt.CafeBonusID))
}
}
// TestParseLargeMsgMhfRegistGuildAdventureDiva tests Parse for MsgMhfRegistGuildAdventureDiva.
func TestParseLargeMsgMhfRegistGuildAdventureDiva(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(5) // Destination
bf.WriteUint32(1000) // Charge
bf.WriteUint32(42) // CharID (skipped)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildAdventureDiva{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Destination != 5 {
t.Errorf("Destination = %d, want 5", pkt.Destination)
}
if pkt.Charge != 1000 {
t.Errorf("Charge = %d, want 1000", pkt.Charge)
}
}
// TestParseLargeMsgMhfStateFestaG tests Parse for MsgMhfStateFestaG.
func TestParseLargeMsgMhfStateFestaG(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(0) // Hardcoded 0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfStateFestaG{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.FestaID != 100 {
t.Errorf("FestaID = %d, want 100", pkt.FestaID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
}
// TestParseLargeMsgMhfStateFestaU tests Parse for MsgMhfStateFestaU.
func TestParseLargeMsgMhfStateFestaU(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(300) // FestaID
bf.WriteUint32(400) // GuildID
bf.WriteUint16(0) // Hardcoded 0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfStateFestaU{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.FestaID != 300 {
t.Errorf("FestaID = %d, want 300", pkt.FestaID)
}
if pkt.GuildID != 400 {
t.Errorf("GuildID = %d, want 400", pkt.GuildID)
}
}
// TestParseLargeMsgSysEnumerateStage tests Parse for MsgSysEnumerateStage.
func TestParseLargeMsgSysEnumerateStage(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint8(1) // Unk0
bf.WriteUint8(0) // skipped byte
bf.WriteBytes([]byte("quest_"))
bf.WriteUint8(0) // null terminator
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnumerateStage{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.StagePrefix != "quest_" {
t.Errorf("StagePrefix = %q, want %q", pkt.StagePrefix, "quest_")
}
}
// TestParseLargeMsgSysReserveStage tests Parse for MsgSysReserveStage.
func TestParseLargeMsgSysReserveStage(t *testing.T) {
stageID := "stage_42"
stageBytes := make([]byte, len(stageID)+1) // padded with null at end
copy(stageBytes, stageID)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(0x11) // Ready
bf.WriteUint8(uint8(len(stageBytes))) // stageIDLength
bf.WriteBytes(stageBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysReserveStage{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Ready != 0x11 {
t.Errorf("Ready = 0x%X, want 0x11", pkt.Ready)
}
if pkt.StageID != stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, stageID)
}
}

View File

@@ -0,0 +1,776 @@
package mhfpacket
import (
"bytes"
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// --- 5-stmt packets (medium complexity) ---
// TestParseMediumVoteFesta verifies Parse for MsgMhfVoteFesta.
// Fields: AckHandle(u32), FestaID(u32), GuildID(u32), TrialID(u32)
func TestParseMediumVoteFesta(t *testing.T) {
tests := []struct {
name string
ack uint32
festaID uint32
guildID uint32
trialID uint32
}{
{"typical", 0x11223344, 1, 500, 42},
{"zero", 0, 0, 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.festaID)
bf.WriteUint32(tt.guildID)
bf.WriteUint32(tt.trialID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfVoteFesta{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.FestaID != tt.festaID {
t.Errorf("FestaID = %d, want %d", pkt.FestaID, tt.festaID)
}
if pkt.GuildID != tt.guildID {
t.Errorf("GuildID = %d, want %d", pkt.GuildID, tt.guildID)
}
if pkt.TrialID != tt.trialID {
t.Errorf("TrialID = %d, want %d", pkt.TrialID, tt.trialID)
}
})
}
}
// TestParseMediumAcquireSemaphore verifies Parse for MsgSysAcquireSemaphore.
// Fields: AckHandle(u32), SemaphoreIDLength(u8), SemaphoreID(string via bfutil.UpToNull)
func TestParseMediumAcquireSemaphore(t *testing.T) {
tests := []struct {
name string
ack uint32
semaphoreID string
}{
{"typical", 0xAABBCCDD, "quest_semaphore"},
{"short", 1, "s"},
{"empty", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
// SemaphoreIDLength includes the null terminator in the read
idBytes := []byte(tt.semaphoreID)
idBytes = append(idBytes, 0x00) // null terminator
bf.WriteUint8(uint8(len(idBytes)))
bf.WriteBytes(idBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysAcquireSemaphore{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.SemaphoreID != tt.semaphoreID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, tt.semaphoreID)
}
})
}
}
// TestParseMediumCheckSemaphore verifies Parse for MsgSysCheckSemaphore.
// Fields: AckHandle(u32), semaphoreIDLength(u8), SemaphoreID(string via bfutil.UpToNull)
func TestParseMediumCheckSemaphore(t *testing.T) {
tests := []struct {
name string
ack uint32
semaphoreID string
}{
{"typical", 0x12345678, "global_semaphore"},
{"short id", 42, "x"},
{"empty id", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
idBytes := []byte(tt.semaphoreID)
idBytes = append(idBytes, 0x00)
bf.WriteUint8(uint8(len(idBytes)))
bf.WriteBytes(idBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCheckSemaphore{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.SemaphoreID != tt.semaphoreID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, tt.semaphoreID)
}
})
}
}
// TestParseMediumGetUserBinary verifies Parse for MsgSysGetUserBinary.
// Fields: AckHandle(u32), CharID(u32), BinaryType(u8)
func TestParseMediumGetUserBinary(t *testing.T) {
tests := []struct {
name string
ack uint32
charID uint32
binaryType uint8
}{
{"typical", 0xDEADBEEF, 12345, 1},
{"zero", 0, 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.charID)
bf.WriteUint8(tt.binaryType)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysGetUserBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.CharID != tt.charID {
t.Errorf("CharID = %d, want %d", pkt.CharID, tt.charID)
}
if pkt.BinaryType != tt.binaryType {
t.Errorf("BinaryType = %d, want %d", pkt.BinaryType, tt.binaryType)
}
})
}
}
// TestParseMediumSetObjectBinary verifies Parse for MsgSysSetObjectBinary.
// Fields: ObjID(u32), DataSize(u16), RawDataPayload([]byte of DataSize)
func TestParseMediumSetObjectBinary(t *testing.T) {
tests := []struct {
name string
objID uint32
payload []byte
}{
{"typical", 42, []byte{0x01, 0x02, 0x03, 0x04}},
{"empty", 0, []byte{}},
{"large", 0xCAFEBABE, []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.objID)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetObjectBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.ObjID != tt.objID {
t.Errorf("ObjID = %d, want %d", pkt.ObjID, tt.objID)
}
if pkt.DataSize != uint16(len(tt.payload)) {
t.Errorf("DataSize = %d, want %d", pkt.DataSize, len(tt.payload))
}
if !bytes.Equal(pkt.RawDataPayload, tt.payload) {
t.Errorf("RawDataPayload = %v, want %v", pkt.RawDataPayload, tt.payload)
}
})
}
}
// TestParseMediumSetUserBinary verifies Parse for MsgSysSetUserBinary.
// Fields: BinaryType(u8), DataSize(u16), RawDataPayload([]byte of DataSize)
func TestParseMediumSetUserBinary(t *testing.T) {
tests := []struct {
name string
binaryType uint8
payload []byte
}{
{"typical", 1, []byte{0xDE, 0xAD, 0xBE, 0xEF}},
{"empty", 0, []byte{}},
{"max type", 255, []byte{0x01}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.binaryType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetUserBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.BinaryType != tt.binaryType {
t.Errorf("BinaryType = %d, want %d", pkt.BinaryType, tt.binaryType)
}
if pkt.DataSize != uint16(len(tt.payload)) {
t.Errorf("DataSize = %d, want %d", pkt.DataSize, len(tt.payload))
}
if !bytes.Equal(pkt.RawDataPayload, tt.payload) {
t.Errorf("RawDataPayload = %v, want %v", pkt.RawDataPayload, tt.payload)
}
})
}
}
// --- 4-stmt packets ---
// TestParseMediumGetUdRanking verifies Parse for MsgMhfGetUdRanking.
// Fields: AckHandle(u32), Unk0(u8)
func TestParseMediumGetUdRanking(t *testing.T) {
tests := []struct {
name string
ack uint32
unk0 uint8
}{
{"typical", 0x11223344, 5},
{"zero", 0, 0},
{"max", 0xFFFFFFFF, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint8(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdRanking{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseMediumGetUdTacticsRanking verifies Parse for MsgMhfGetUdTacticsRanking.
// Fields: AckHandle(u32), GuildID(u32)
func TestParseMediumGetUdTacticsRanking(t *testing.T) {
tests := []struct {
name string
ack uint32
guildID uint32
}{
{"typical", 0xAABBCCDD, 500},
{"zero", 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.guildID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdTacticsRanking{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.GuildID != tt.guildID {
t.Errorf("GuildID = %d, want %d", pkt.GuildID, tt.guildID)
}
})
}
}
// TestParseMediumRegistGuildTresure verifies Parse for MsgMhfRegistGuildTresure.
// Fields: AckHandle(u32), DataLen(u16), Data([]byte), trailing u32 (discarded)
func TestParseMediumRegistGuildTresure(t *testing.T) {
tests := []struct {
name string
ack uint32
data []byte
}{
{"typical", 0x12345678, []byte{0x01, 0x02, 0x03}},
{"empty data", 1, []byte{}},
{"larger data", 0xDEADBEEF, []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint16(uint16(len(tt.data)))
bf.WriteBytes(tt.data)
bf.WriteUint32(0) // trailing uint32 that is read and discarded
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildTresure{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if !bytes.Equal(pkt.Data, tt.data) {
t.Errorf("Data = %v, want %v", pkt.Data, tt.data)
}
})
}
}
// TestParseMediumUpdateMyhouseInfo verifies Parse for MsgMhfUpdateMyhouseInfo.
// Fields: AckHandle(u32), Unk0([]byte of 0x16A bytes)
func TestParseMediumUpdateMyhouseInfo(t *testing.T) {
t.Run("typical", func(t *testing.T) {
bf := byteframe.NewByteFrame()
ack := uint32(0xCAFEBABE)
bf.WriteUint32(ack)
// 0x16A = 362 bytes
payload := make([]byte, 0x16A)
for i := range payload {
payload[i] = byte(i % 256)
}
bf.WriteBytes(payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateMyhouseInfo{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
if len(pkt.Data) != 0x16A {
t.Errorf("Unk0 length = %d, want %d", len(pkt.Data), 0x16A)
}
if !bytes.Equal(pkt.Data, payload) {
t.Error("Unk0 content mismatch")
}
})
t.Run("zero values", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
bf.WriteBytes(make([]byte, 0x16A))
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateMyhouseInfo{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0 {
t.Errorf("AckHandle = 0x%X, want 0", pkt.AckHandle)
}
if len(pkt.Data) != 0x16A {
t.Errorf("Unk0 length = %d, want %d", len(pkt.Data), 0x16A)
}
})
}
// --- 3-stmt packets (AckHandle-only Parse) ---
// TestParseMediumAckHandleOnlyBatch tests Parse for all 3-stmt packets that only
// read a single AckHandle uint32. These are verified to parse correctly and
// return the expected AckHandle value.
func TestParseMediumAckHandleOnlyBatch(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
// getAck extracts the AckHandle from the parsed packet
getAck func() uint32
}{
{
"MsgMhfGetUdBonusQuestInfo",
&MsgMhfGetUdBonusQuestInfo{},
nil,
},
{
"MsgMhfGetUdDailyPresentList",
&MsgMhfGetUdDailyPresentList{},
nil,
},
{
"MsgMhfGetUdGuildMapInfo",
&MsgMhfGetUdGuildMapInfo{},
nil,
},
{
"MsgMhfGetUdMonsterPoint",
&MsgMhfGetUdMonsterPoint{},
nil,
},
{
"MsgMhfGetUdMyRanking",
&MsgMhfGetUdMyRanking{},
nil,
},
{
"MsgMhfGetUdNormaPresentList",
&MsgMhfGetUdNormaPresentList{},
nil,
},
{
"MsgMhfGetUdRankingRewardList",
&MsgMhfGetUdRankingRewardList{},
nil,
},
{
"MsgMhfGetUdSelectedColorInfo",
&MsgMhfGetUdSelectedColorInfo{},
nil,
},
{
"MsgMhfGetUdShopCoin",
&MsgMhfGetUdShopCoin{},
nil,
},
{
"MsgMhfGetUdTacticsBonusQuest",
&MsgMhfGetUdTacticsBonusQuest{},
nil,
},
{
"MsgMhfGetUdTacticsFirstQuestBonus",
&MsgMhfGetUdTacticsFirstQuestBonus{},
nil,
},
{
"MsgMhfGetUdTacticsFollower",
&MsgMhfGetUdTacticsFollower{},
nil,
},
{
"MsgMhfGetUdTacticsLog",
&MsgMhfGetUdTacticsLog{},
nil,
},
{
"MsgMhfGetUdTacticsPoint",
&MsgMhfGetUdTacticsPoint{},
nil,
},
{
"MsgMhfGetUdTacticsRewardList",
&MsgMhfGetUdTacticsRewardList{},
nil,
},
{
"MsgMhfReceiveCafeDurationBonus",
&MsgMhfReceiveCafeDurationBonus{},
nil,
},
{
"MsgSysDeleteSemaphore",
&MsgSysDeleteSemaphore{},
nil,
},
{
"MsgSysReleaseSemaphore",
&MsgSysReleaseSemaphore{},
nil,
},
}
ctx := &clientctx.ClientContext{}
ackValues := []uint32{0x12345678, 0, 0xFFFFFFFF, 0xDEADBEEF}
for _, tc := range packets {
for _, ackVal := range ackValues {
t.Run(tc.name+"/ack_"+ackHex(ackVal), func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(ackVal)
bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
}
// TestParseMediumAckHandleOnlyVerifyValues tests each 3-stmt AckHandle-only
// packet individually, verifying that the AckHandle field is correctly populated.
func TestParseMediumAckHandleOnlyVerifyValues(t *testing.T) {
ctx := &clientctx.ClientContext{}
ack := uint32(0xCAFEBABE)
makeFrame := func() *byteframe.ByteFrame {
bf := byteframe.NewByteFrame()
bf.WriteUint32(ack)
bf.Seek(0, io.SeekStart)
return bf
}
t.Run("MsgMhfGetUdBonusQuestInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdBonusQuestInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdDailyPresentList", func(t *testing.T) {
pkt := &MsgMhfGetUdDailyPresentList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdGuildMapInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdGuildMapInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdMonsterPoint", func(t *testing.T) {
pkt := &MsgMhfGetUdMonsterPoint{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdMyRanking", func(t *testing.T) {
pkt := &MsgMhfGetUdMyRanking{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdNormaPresentList", func(t *testing.T) {
pkt := &MsgMhfGetUdNormaPresentList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdRankingRewardList", func(t *testing.T) {
pkt := &MsgMhfGetUdRankingRewardList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdSelectedColorInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdSelectedColorInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdShopCoin", func(t *testing.T) {
pkt := &MsgMhfGetUdShopCoin{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsBonusQuest", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsBonusQuest{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsFirstQuestBonus", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsFirstQuestBonus{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsFollower", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsFollower{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsLog", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsLog{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsPoint", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsPoint{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsRewardList", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsRewardList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfReceiveCafeDurationBonus", func(t *testing.T) {
pkt := &MsgMhfReceiveCafeDurationBonus{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgSysDeleteSemaphore", func(t *testing.T) {
pkt := &MsgSysDeleteSemaphore{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.SemaphoreID != ack {
t.Errorf("SemaphoreID = 0x%X, want 0x%X", pkt.SemaphoreID, ack)
}
})
t.Run("MsgSysReleaseSemaphore", func(t *testing.T) {
pkt := &MsgSysReleaseSemaphore{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
}
// TestParseMediumDeleteUser verifies that MsgSysDeleteUser.Parse returns
// NOT IMPLEMENTED error (Parse is not implemented, only Build is).
func TestParseMediumDeleteUser(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysDeleteUser{}
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("Parse() should return error for NOT IMPLEMENTED")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
}
// TestParseMediumInsertUser verifies that MsgSysInsertUser.Parse returns
// NOT IMPLEMENTED error (Parse is not implemented, only Build is).
func TestParseMediumInsertUser(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysInsertUser{}
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("Parse() should return error for NOT IMPLEMENTED")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
}
// ackHex returns a hex string for a uint32 ack value, used for test naming.
func ackHex(v uint32) string {
const hex = "0123456789ABCDEF"
buf := make([]byte, 8)
for i := 7; i >= 0; i-- {
buf[i] = hex[v&0xF]
v >>= 4
}
return string(buf)
}

View File

@@ -0,0 +1,216 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestParseSmallNotImplemented tests Parse for packets whose Parse method returns
// "NOT IMPLEMENTED". We verify that Parse returns a non-nil error and does not panic.
func TestParseSmallNotImplemented(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
// MHF packets - NOT IMPLEMENTED
{"MsgMhfAcceptReadReward", &MsgMhfAcceptReadReward{}},
{"MsgMhfAddRewardSongCount", &MsgMhfAddRewardSongCount{}},
{"MsgMhfDebugPostValue", &MsgMhfDebugPostValue{}},
{"MsgMhfEnterTournamentQuest", &MsgMhfEnterTournamentQuest{}},
{"MsgMhfGetCaAchievementHist", &MsgMhfGetCaAchievementHist{}},
{"MsgMhfGetCaUniqueID", &MsgMhfGetCaUniqueID{}},
{"MsgMhfGetDailyMissionMaster", &MsgMhfGetDailyMissionMaster{}},
{"MsgMhfGetDailyMissionPersonal", &MsgMhfGetDailyMissionPersonal{}},
{"MsgMhfGetExtraInfo", &MsgMhfGetExtraInfo{}},
{"MsgMhfGetRestrictionEvent", &MsgMhfGetRestrictionEvent{}},
{"MsgMhfKickExportForce", &MsgMhfKickExportForce{}},
{"MsgMhfPaymentAchievement", &MsgMhfPaymentAchievement{}},
{"MsgMhfPostRyoudama", &MsgMhfPostRyoudama{}},
{"MsgMhfRegistSpabiTime", &MsgMhfRegistSpabiTime{}},
{"MsgMhfResetAchievement", &MsgMhfResetAchievement{}},
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
{"MsgMhfSetCaAchievement", &MsgMhfSetCaAchievement{}},
{"MsgMhfSetDailyMissionPersonal", &MsgMhfSetDailyMissionPersonal{}},
{"MsgMhfSetUdTacticsFollower", &MsgMhfSetUdTacticsFollower{}},
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
{"MsgMhfUpdateForceGuildRank", &MsgMhfUpdateForceGuildRank{}},
{"MsgMhfUseUdShopCoin", &MsgMhfUseUdShopCoin{}},
// SYS packets - NOT IMPLEMENTED
{"MsgSysAuthData", &MsgSysAuthData{}},
{"MsgSysAuthQuery", &MsgSysAuthQuery{}},
{"MsgSysAuthTerminal", &MsgSysAuthTerminal{}},
{"MsgSysCloseMutex", &MsgSysCloseMutex{}},
{"MsgSysCollectBinary", &MsgSysCollectBinary{}},
{"MsgSysCreateMutex", &MsgSysCreateMutex{}},
{"MsgSysCreateOpenMutex", &MsgSysCreateOpenMutex{}},
{"MsgSysDeleteMutex", &MsgSysDeleteMutex{}},
{"MsgSysEnumlobby", &MsgSysEnumlobby{}},
{"MsgSysEnumuser", &MsgSysEnumuser{}},
{"MsgSysGetObjectBinary", &MsgSysGetObjectBinary{}},
{"MsgSysGetState", &MsgSysGetState{}},
{"MsgSysInfokyserver", &MsgSysInfokyserver{}},
{"MsgSysOpenMutex", &MsgSysOpenMutex{}},
{"MsgSysRotateObject", &MsgSysRotateObject{}},
{"MsgSysSerialize", &MsgSysSerialize{}},
{"MsgSysTransBinary", &MsgSysTransBinary{}},
}
ctx := &clientctx.ClientContext{}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
// Write some padding bytes so Parse has data available if it tries to read.
bf.WriteUint32(0)
bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err == nil {
t.Fatalf("Parse() expected error for NOT IMPLEMENTED packet, got nil")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Fatalf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
})
}
}
// TestParseSmallNoData tests Parse for packets with no fields that return nil.
func TestParseSmallNoData(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
{"MsgSysCleanupObject", &MsgSysCleanupObject{}},
{"MsgSysUnreserveStage", &MsgSysUnreserveStage{}},
}
ctx := &clientctx.ClientContext{}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v, want nil", err)
}
})
}
}
// TestParseSmallLogout tests Parse for MsgSysLogout which reads a single uint8 field.
func TestParseSmallLogout(t *testing.T) {
tests := []struct {
name string
unk0 uint8
}{
{"hardcoded 1", 1},
{"zero", 0},
{"max", 255},
}
ctx := &clientctx.ClientContext{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogout{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseSmallEnumerateHouse tests Parse for MsgMhfEnumerateHouse which reads
// AckHandle, CharID, Method, Unk, lenName, and optional Name.
func TestParseSmallEnumerateHouse(t *testing.T) {
ctx := &clientctx.ClientContext{}
t.Run("no name", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint32(0xDEADBEEF) // CharID
bf.WriteUint8(2) // Method
bf.WriteUint16(100) // Unk
bf.WriteUint8(0) // lenName = 0 (no name)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateHouse{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.CharID != 0xDEADBEEF {
t.Errorf("CharID = 0x%X, want 0xDEADBEEF", pkt.CharID)
}
if pkt.Method != 2 {
t.Errorf("Method = %d, want 2", pkt.Method)
}
if pkt.Name != "" {
t.Errorf("Name = %q, want empty", pkt.Name)
}
})
t.Run("with name", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(42) // CharID
bf.WriteUint8(1) // Method
bf.WriteUint16(200) // Unk
// The name is SJIS null-terminated bytes. Use ASCII-compatible bytes.
nameBytes := []byte("Test\x00")
bf.WriteUint8(uint8(len(nameBytes))) // lenName > 0
bf.WriteBytes(nameBytes) // null-terminated name
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateHouse{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 1 {
t.Errorf("AckHandle = %d, want 1", pkt.AckHandle)
}
if pkt.CharID != 42 {
t.Errorf("CharID = %d, want 42", pkt.CharID)
}
if pkt.Method != 1 {
t.Errorf("Method = %d, want 1", pkt.Method)
}
if pkt.Name != "Test" {
t.Errorf("Name = %q, want %q", pkt.Name, "Test")
}
})
}
// TestParseSmallNotImplementedDoesNotPanic ensures that calling Parse on NOT IMPLEMENTED
// packets with a nil ClientContext does not cause a nil pointer dereference panic.
func TestParseSmallNotImplementedDoesNotPanic(t *testing.T) {
packets := []MHFPacket{
&MsgMhfAcceptReadReward{},
&MsgSysAuthData{},
&MsgSysSerialize{},
}
for _, pkt := range packets {
t.Run("nil_ctx", func(t *testing.T) {
bf := byteframe.NewByteFrame()
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("expected error, got nil")
}
})
}
}

View File

@@ -0,0 +1,218 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestMsgMhfGetAchievementParse tests MsgMhfGetAchievement parsing
func TestMsgMhfGetAchievementDetailedParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(54321) // CharID
bf.WriteUint32(99999) // Unk1
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.CharID != 54321 {
t.Errorf("CharID = %d, want 54321", pkt.CharID)
}
}
// TestMsgMhfAddAchievementDetailedParse tests MsgMhfAddAchievement parsing
func TestMsgMhfAddAchievementDetailedParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(42) // AchievementID
bf.WriteUint16(12345) // Unk1
bf.WriteUint16(0xFFFF) // Unk2 - max value
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AchievementID != 42 {
t.Errorf("AchievementID = %d, want 42", pkt.AchievementID)
}
if pkt.Unk1 != 12345 {
t.Errorf("Unk1 = %d, want 12345", pkt.Unk1)
}
if pkt.Unk2 != 0xFFFF {
t.Errorf("Unk2 = %d, want 65535", pkt.Unk2)
}
}
// TestMsgSysCastBinaryDetailedParse tests MsgSysCastBinary parsing with various payloads
func TestMsgSysCastBinaryDetailedParse(t *testing.T) {
tests := []struct {
name string
unk uint32
broadcastType uint8
messageType uint8
payload []byte
}{
{"empty payload", 0, 1, 2, []byte{}},
{"typical payload", 0x006400C8, 0x10, 0x20, []byte{0x01, 0x02, 0x03}},
{"chat message", 0, 0x01, 0x01, []byte("Hello, World!")},
{"binary data", 0xFFFFFFFF, 0xFF, 0xFF, []byte{0xDE, 0xAD, 0xBE, 0xEF}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.unk)
bf.WriteUint8(tt.broadcastType)
bf.WriteUint8(tt.messageType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCastBinary{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
}
if pkt.BroadcastType != tt.broadcastType {
t.Errorf("BroadcastType = %d, want %d", pkt.BroadcastType, tt.broadcastType)
}
if pkt.MessageType != tt.messageType {
t.Errorf("MessageType = %d, want %d", pkt.MessageType, tt.messageType)
}
if len(pkt.RawDataPayload) != len(tt.payload) {
t.Errorf("RawDataPayload len = %d, want %d", len(pkt.RawDataPayload), len(tt.payload))
}
})
}
}
// TestMsgSysLogoutParse tests MsgSysLogout parsing
func TestMsgSysLogoutDetailedParse(t *testing.T) {
tests := []struct {
unk0 uint8
}{
{0},
{1},
{100},
{255},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.unk0)
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.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
}
}
// TestMsgSysBackStageParse tests MsgSysBackStage parsing
func TestMsgSysBackStageDetailedParse(t *testing.T) {
tests := []struct {
ackHandle uint32
}{
{0},
{1},
{0x12345678},
{0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
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.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
}
}
// TestMsgSysPingParse tests MsgSysPing parsing
func TestMsgSysPingDetailedParse(t *testing.T) {
tests := []struct {
ackHandle uint32
}{
{0},
{0xABCDEF12},
{0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysPing{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
}
}
// TestMsgSysTimeParse tests MsgSysTime parsing
func TestMsgSysTimeDetailedParse(t *testing.T) {
tests := []struct {
getRemoteTime bool
timestamp uint32
}{
{false, 0},
{true, 1577836800}, // 2020-01-01 00:00:00
{false, 0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteBool(tt.getRemoteTime)
bf.WriteUint32(tt.timestamp)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysTime{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.GetRemoteTime != tt.getRemoteTime {
t.Errorf("GetRemoteTime = %v, want %v", pkt.GetRemoteTime, tt.getRemoteTime)
}
if pkt.Timestamp != tt.timestamp {
t.Errorf("Timestamp = %d, want %d", pkt.Timestamp, tt.timestamp)
}
}
}

View File

@@ -0,0 +1,310 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestMsgSysAckRoundTrip(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
isBufferResponse bool
errorCode uint8
ackData []byte
}{
{
name: "simple non-buffer response",
ackHandle: 1,
isBufferResponse: false,
errorCode: 0,
ackData: []byte{0x00, 0x00, 0x00, 0x00},
},
{
name: "buffer response with small data",
ackHandle: 0x12345678,
isBufferResponse: true,
errorCode: 0,
ackData: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
},
{
name: "error response",
ackHandle: 100,
isBufferResponse: false,
errorCode: 1,
ackData: []byte{0xDE, 0xAD, 0xBE, 0xEF},
},
{
name: "empty buffer response",
ackHandle: 999,
isBufferResponse: true,
errorCode: 0,
ackData: []byte{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := &MsgSysAck{
AckHandle: tt.ackHandle,
IsBufferResponse: tt.isBufferResponse,
ErrorCode: tt.errorCode,
AckData: tt.ackData,
}
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 := &MsgSysAck{}
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)
}
if parsed.IsBufferResponse != original.IsBufferResponse {
t.Errorf("IsBufferResponse = %v, want %v", parsed.IsBufferResponse, original.IsBufferResponse)
}
if parsed.ErrorCode != original.ErrorCode {
t.Errorf("ErrorCode = %d, want %d", parsed.ErrorCode, original.ErrorCode)
}
})
}
}
func TestMsgSysAckLargePayload(t *testing.T) {
// Test with payload larger than 0xFFFF to trigger extended size field
largeData := make([]byte, 0x10000) // 65536 bytes
for i := range largeData {
largeData[i] = byte(i % 256)
}
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: true,
ErrorCode: 0,
AckData: largeData,
}
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 := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(parsed.AckData) != len(largeData) {
t.Errorf("AckData len = %d, want %d", len(parsed.AckData), len(largeData))
}
}
func TestMsgSysAckOpcode(t *testing.T) {
pkt := &MsgSysAck{}
if pkt.Opcode() != network.MSG_SYS_ACK {
t.Errorf("Opcode() = %s, want MSG_SYS_ACK", pkt.Opcode())
}
}
func TestMsgSysNopRoundTrip(t *testing.T) {
original := &MsgSysNop{}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Should write no data
if len(bf.Data()) != 0 {
t.Errorf("MsgSysNop.Build() wrote %d bytes, want 0", len(bf.Data()))
}
// Parse (from empty buffer)
parsed := &MsgSysNop{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
}
func TestMsgSysNopOpcode(t *testing.T) {
pkt := &MsgSysNop{}
if pkt.Opcode() != network.MSG_SYS_NOP {
t.Errorf("Opcode() = %s, want MSG_SYS_NOP", pkt.Opcode())
}
}
func TestMsgSysEndRoundTrip(t *testing.T) {
original := &MsgSysEnd{}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Should write no data
if len(bf.Data()) != 0 {
t.Errorf("MsgSysEnd.Build() wrote %d bytes, want 0", len(bf.Data()))
}
// Parse (from empty buffer)
parsed := &MsgSysEnd{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
}
func TestMsgSysEndOpcode(t *testing.T) {
pkt := &MsgSysEnd{}
if pkt.Opcode() != network.MSG_SYS_END {
t.Errorf("Opcode() = %s, want MSG_SYS_END", pkt.Opcode())
}
}
func TestMsgSysAckNonBufferResponse(t *testing.T) {
// Non-buffer response should always read/write 4 bytes of data
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: false,
ErrorCode: 0,
AckData: []byte{0xAA, 0xBB, 0xCC, 0xDD},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Non-buffer response should have exactly 4 bytes of data
if len(parsed.AckData) != 4 {
t.Errorf("Non-buffer AckData len = %d, want 4", len(parsed.AckData))
}
}
func TestMsgSysAckNonBufferShortData(t *testing.T) {
// Non-buffer response with short data should pad to 4 bytes
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: false,
ErrorCode: 0,
AckData: []byte{0x01}, // Only 1 byte
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Should still read 4 bytes
if len(parsed.AckData) != 4 {
t.Errorf("AckData len = %d, want 4", len(parsed.AckData))
}
}
func TestMsgSysAckBuildFormat(t *testing.T) {
pkt := &MsgSysAck{
AckHandle: 0x12345678,
IsBufferResponse: true,
ErrorCode: 0x55,
AckData: []byte{0xAA, 0xBB},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
pkt.Build(bf, ctx)
data := bf.Data()
// Check AckHandle (big-endian)
if data[0] != 0x12 || data[1] != 0x34 || data[2] != 0x56 || data[3] != 0x78 {
t.Errorf("AckHandle bytes = %X, want 12345678", data[:4])
}
// Check IsBufferResponse (1 = true)
if data[4] != 1 {
t.Errorf("IsBufferResponse byte = %d, want 1", data[4])
}
// Check ErrorCode
if data[5] != 0x55 {
t.Errorf("ErrorCode byte = %X, want 55", data[5])
}
// Check payload size (2 bytes, big-endian)
if data[6] != 0x00 || data[7] != 0x02 {
t.Errorf("PayloadSize bytes = %X %X, want 00 02", data[6], data[7])
}
// Check actual data
if data[8] != 0xAA || data[9] != 0xBB {
t.Errorf("AckData bytes = %X %X, want AA BB", data[8], data[9])
}
}
func TestCorePacketsFromOpcode(t *testing.T) {
coreOpcodes := []network.PacketID{
network.MSG_SYS_NOP,
network.MSG_SYS_END,
network.MSG_SYS_ACK,
network.MSG_SYS_PING,
}
for _, opcode := range coreOpcodes {
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)
}
})
}
}

View File

@@ -0,0 +1,592 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// TestMsgSysCastBinaryParse tests parsing MsgSysCastBinary
func TestMsgSysCastBinaryParse(t *testing.T) {
tests := []struct {
name string
unk uint32
broadcastType uint8
messageType uint8
payload []byte
}{
{"empty payload", 0, 1, 2, []byte{}},
{"small payload", 0x006400C8, 3, 4, []byte{0xAA, 0xBB, 0xCC}},
{"large payload", 0xFFFFFFFF, 0xFF, 0xFF, make([]byte, 100)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.unk)
bf.WriteUint8(tt.broadcastType)
bf.WriteUint8(tt.messageType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCastBinary{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
}
if pkt.BroadcastType != tt.broadcastType {
t.Errorf("BroadcastType = %d, want %d", pkt.BroadcastType, tt.broadcastType)
}
if pkt.MessageType != tt.messageType {
t.Errorf("MessageType = %d, want %d", pkt.MessageType, tt.messageType)
}
if len(pkt.RawDataPayload) != len(tt.payload) {
t.Errorf("RawDataPayload len = %d, want %d", len(pkt.RawDataPayload), len(tt.payload))
}
})
}
}
// TestMsgSysCastBinaryOpcode tests Opcode method
func TestMsgSysCastBinaryOpcode(t *testing.T) {
pkt := &MsgSysCastBinary{}
if pkt.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_CAST_BINARY", pkt.Opcode())
}
}
// TestMsgSysCreateSemaphoreOpcode tests Opcode method
func TestMsgSysCreateSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCreateSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CREATE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCastedBinaryOpcode tests Opcode method
func TestMsgSysCastedBinaryOpcode(t *testing.T) {
pkt := &MsgSysCastedBinary{}
if pkt.Opcode() != network.MSG_SYS_CASTED_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_CASTED_BINARY", pkt.Opcode())
}
}
// TestMsgSysSetStageBinaryOpcode tests Opcode method
func TestMsgSysSetStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetStageBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetStageBinaryOpcode tests Opcode method
func TestMsgSysGetStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetStageBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysWaitStageBinaryOpcode tests Opcode method
func TestMsgSysWaitStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysWaitStageBinary{}
if pkt.Opcode() != network.MSG_SYS_WAIT_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_WAIT_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysEnumerateClientOpcode tests Opcode method
func TestMsgSysEnumerateClientOpcode(t *testing.T) {
pkt := &MsgSysEnumerateClient{}
if pkt.Opcode() != network.MSG_SYS_ENUMERATE_CLIENT {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMERATE_CLIENT", pkt.Opcode())
}
}
// TestMsgSysEnumerateStageOpcode tests Opcode method
func TestMsgSysEnumerateStageOpcode(t *testing.T) {
pkt := &MsgSysEnumerateStage{}
if pkt.Opcode() != network.MSG_SYS_ENUMERATE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMERATE_STAGE", pkt.Opcode())
}
}
// TestMsgSysCreateMutexOpcode tests Opcode method
func TestMsgSysCreateMutexOpcode(t *testing.T) {
pkt := &MsgSysCreateMutex{}
if pkt.Opcode() != network.MSG_SYS_CREATE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysCreateOpenMutexOpcode tests Opcode method
func TestMsgSysCreateOpenMutexOpcode(t *testing.T) {
pkt := &MsgSysCreateOpenMutex{}
if pkt.Opcode() != network.MSG_SYS_CREATE_OPEN_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_OPEN_MUTEX", pkt.Opcode())
}
}
// TestMsgSysDeleteMutexOpcode tests Opcode method
func TestMsgSysDeleteMutexOpcode(t *testing.T) {
pkt := &MsgSysDeleteMutex{}
if pkt.Opcode() != network.MSG_SYS_DELETE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysOpenMutexOpcode tests Opcode method
func TestMsgSysOpenMutexOpcode(t *testing.T) {
pkt := &MsgSysOpenMutex{}
if pkt.Opcode() != network.MSG_SYS_OPEN_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_OPEN_MUTEX", pkt.Opcode())
}
}
// TestMsgSysCloseMutexOpcode tests Opcode method
func TestMsgSysCloseMutexOpcode(t *testing.T) {
pkt := &MsgSysCloseMutex{}
if pkt.Opcode() != network.MSG_SYS_CLOSE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CLOSE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysDeleteSemaphoreOpcode tests Opcode method
func TestMsgSysDeleteSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysDeleteSemaphore{}
if pkt.Opcode() != network.MSG_SYS_DELETE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysAcquireSemaphoreOpcode tests Opcode method
func TestMsgSysAcquireSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysAcquireSemaphore{}
if pkt.Opcode() != network.MSG_SYS_ACQUIRE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_ACQUIRE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysReleaseSemaphoreOpcode tests Opcode method
func TestMsgSysReleaseSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysReleaseSemaphore{}
if pkt.Opcode() != network.MSG_SYS_RELEASE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_RELEASE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCheckSemaphoreOpcode tests Opcode method
func TestMsgSysCheckSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCheckSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CHECK_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CHECK_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCreateAcquireSemaphoreOpcode tests Opcode method
func TestMsgSysCreateAcquireSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCreateAcquireSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_ACQUIRE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysOperateRegisterOpcode tests Opcode method
func TestMsgSysOperateRegisterOpcode(t *testing.T) {
pkt := &MsgSysOperateRegister{}
if pkt.Opcode() != network.MSG_SYS_OPERATE_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_OPERATE_REGISTER", pkt.Opcode())
}
}
// TestMsgSysLoadRegisterOpcode tests Opcode method
func TestMsgSysLoadRegisterOpcode(t *testing.T) {
pkt := &MsgSysLoadRegister{}
if pkt.Opcode() != network.MSG_SYS_LOAD_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_LOAD_REGISTER", pkt.Opcode())
}
}
// TestMsgSysNotifyRegisterOpcode tests Opcode method
func TestMsgSysNotifyRegisterOpcode(t *testing.T) {
pkt := &MsgSysNotifyRegister{}
if pkt.Opcode() != network.MSG_SYS_NOTIFY_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_NOTIFY_REGISTER", pkt.Opcode())
}
}
// TestMsgSysCreateObjectOpcode tests Opcode method
func TestMsgSysCreateObjectOpcode(t *testing.T) {
pkt := &MsgSysCreateObject{}
if pkt.Opcode() != network.MSG_SYS_CREATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDeleteObjectOpcode tests Opcode method
func TestMsgSysDeleteObjectOpcode(t *testing.T) {
pkt := &MsgSysDeleteObject{}
if pkt.Opcode() != network.MSG_SYS_DELETE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysPositionObjectOpcode tests Opcode method
func TestMsgSysPositionObjectOpcode(t *testing.T) {
pkt := &MsgSysPositionObject{}
if pkt.Opcode() != network.MSG_SYS_POSITION_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_POSITION_OBJECT", pkt.Opcode())
}
}
// TestMsgSysRotateObjectOpcode tests Opcode method
func TestMsgSysRotateObjectOpcode(t *testing.T) {
pkt := &MsgSysRotateObject{}
if pkt.Opcode() != network.MSG_SYS_ROTATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_ROTATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDuplicateObjectOpcode tests Opcode method
func TestMsgSysDuplicateObjectOpcode(t *testing.T) {
pkt := &MsgSysDuplicateObject{}
if pkt.Opcode() != network.MSG_SYS_DUPLICATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DUPLICATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysSetObjectBinaryOpcode tests Opcode method
func TestMsgSysSetObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetObjectBinaryOpcode tests Opcode method
func TestMsgSysGetObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetObjectOwnerOpcode tests Opcode method
func TestMsgSysGetObjectOwnerOpcode(t *testing.T) {
pkt := &MsgSysGetObjectOwner{}
if pkt.Opcode() != network.MSG_SYS_GET_OBJECT_OWNER {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_OBJECT_OWNER", pkt.Opcode())
}
}
// TestMsgSysUpdateObjectBinaryOpcode tests Opcode method
func TestMsgSysUpdateObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysUpdateObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_UPDATE_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_UPDATE_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysCleanupObjectOpcode tests Opcode method
func TestMsgSysCleanupObjectOpcode(t *testing.T) {
pkt := &MsgSysCleanupObject{}
if pkt.Opcode() != network.MSG_SYS_CLEANUP_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_CLEANUP_OBJECT", pkt.Opcode())
}
}
// TestMsgSysInsertUserOpcode tests Opcode method
func TestMsgSysInsertUserOpcode(t *testing.T) {
pkt := &MsgSysInsertUser{}
if pkt.Opcode() != network.MSG_SYS_INSERT_USER {
t.Errorf("Opcode() = %s, want MSG_SYS_INSERT_USER", pkt.Opcode())
}
}
// TestMsgSysDeleteUserOpcode tests Opcode method
func TestMsgSysDeleteUserOpcode(t *testing.T) {
pkt := &MsgSysDeleteUser{}
if pkt.Opcode() != network.MSG_SYS_DELETE_USER {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_USER", pkt.Opcode())
}
}
// TestMsgSysSetUserBinaryOpcode tests Opcode method
func TestMsgSysSetUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetUserBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetUserBinaryOpcode tests Opcode method
func TestMsgSysGetUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetUserBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysNotifyUserBinaryOpcode tests Opcode method
func TestMsgSysNotifyUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysNotifyUserBinary{}
if pkt.Opcode() != network.MSG_SYS_NOTIFY_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_NOTIFY_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysUpdateRightOpcode tests Opcode method
func TestMsgSysUpdateRightOpcode(t *testing.T) {
pkt := &MsgSysUpdateRight{}
if pkt.Opcode() != network.MSG_SYS_UPDATE_RIGHT {
t.Errorf("Opcode() = %s, want MSG_SYS_UPDATE_RIGHT", pkt.Opcode())
}
}
// TestMsgSysAuthQueryOpcode tests Opcode method
func TestMsgSysAuthQueryOpcode(t *testing.T) {
pkt := &MsgSysAuthQuery{}
if pkt.Opcode() != network.MSG_SYS_AUTH_QUERY {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_QUERY", pkt.Opcode())
}
}
// TestMsgSysAuthDataOpcode tests Opcode method
func TestMsgSysAuthDataOpcode(t *testing.T) {
pkt := &MsgSysAuthData{}
if pkt.Opcode() != network.MSG_SYS_AUTH_DATA {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_DATA", pkt.Opcode())
}
}
// TestMsgSysAuthTerminalOpcode tests Opcode method
func TestMsgSysAuthTerminalOpcode(t *testing.T) {
pkt := &MsgSysAuthTerminal{}
if pkt.Opcode() != network.MSG_SYS_AUTH_TERMINAL {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_TERMINAL", pkt.Opcode())
}
}
// TestMsgSysRightsReloadOpcode tests Opcode method
func TestMsgSysRightsReloadOpcode(t *testing.T) {
pkt := &MsgSysRightsReload{}
if pkt.Opcode() != network.MSG_SYS_RIGHTS_RELOAD {
t.Errorf("Opcode() = %s, want MSG_SYS_RIGHTS_RELOAD", pkt.Opcode())
}
}
// TestMsgSysTerminalLogOpcode tests Opcode method
func TestMsgSysTerminalLogOpcode(t *testing.T) {
pkt := &MsgSysTerminalLog{}
if pkt.Opcode() != network.MSG_SYS_TERMINAL_LOG {
t.Errorf("Opcode() = %s, want MSG_SYS_TERMINAL_LOG", pkt.Opcode())
}
}
// TestMsgSysIssueLogkeyOpcode tests Opcode method
func TestMsgSysIssueLogkeyOpcode(t *testing.T) {
pkt := &MsgSysIssueLogkey{}
if pkt.Opcode() != network.MSG_SYS_ISSUE_LOGKEY {
t.Errorf("Opcode() = %s, want MSG_SYS_ISSUE_LOGKEY", pkt.Opcode())
}
}
// TestMsgSysRecordLogOpcode tests Opcode method
func TestMsgSysRecordLogOpcode(t *testing.T) {
pkt := &MsgSysRecordLog{}
if pkt.Opcode() != network.MSG_SYS_RECORD_LOG {
t.Errorf("Opcode() = %s, want MSG_SYS_RECORD_LOG", pkt.Opcode())
}
}
// TestMsgSysEchoOpcode tests Opcode method
func TestMsgSysEchoOpcode(t *testing.T) {
pkt := &MsgSysEcho{}
if pkt.Opcode() != network.MSG_SYS_ECHO {
t.Errorf("Opcode() = %s, want MSG_SYS_ECHO", pkt.Opcode())
}
}
// TestMsgSysGetFileOpcode tests Opcode method
func TestMsgSysGetFileOpcode(t *testing.T) {
pkt := &MsgSysGetFile{}
if pkt.Opcode() != network.MSG_SYS_GET_FILE {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_FILE", pkt.Opcode())
}
}
// TestMsgSysHideClientOpcode tests Opcode method
func TestMsgSysHideClientOpcode(t *testing.T) {
pkt := &MsgSysHideClient{}
if pkt.Opcode() != network.MSG_SYS_HIDE_CLIENT {
t.Errorf("Opcode() = %s, want MSG_SYS_HIDE_CLIENT", pkt.Opcode())
}
}
// TestMsgSysSetStatusOpcode tests Opcode method
func TestMsgSysSetStatusOpcode(t *testing.T) {
pkt := &MsgSysSetStatus{}
if pkt.Opcode() != network.MSG_SYS_SET_STATUS {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STATUS", pkt.Opcode())
}
}
// TestMsgSysStageDestructOpcode tests Opcode method
func TestMsgSysStageDestructOpcode(t *testing.T) {
pkt := &MsgSysStageDestruct{}
if pkt.Opcode() != network.MSG_SYS_STAGE_DESTRUCT {
t.Errorf("Opcode() = %s, want MSG_SYS_STAGE_DESTRUCT", pkt.Opcode())
}
}
// TestMsgSysLeaveStageOpcode tests Opcode method
func TestMsgSysLeaveStageOpcode(t *testing.T) {
pkt := &MsgSysLeaveStage{}
if pkt.Opcode() != network.MSG_SYS_LEAVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_LEAVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysReserveStageOpcode tests Opcode method
func TestMsgSysReserveStageOpcode(t *testing.T) {
pkt := &MsgSysReserveStage{}
if pkt.Opcode() != network.MSG_SYS_RESERVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_RESERVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysUnreserveStageOpcode tests Opcode method
func TestMsgSysUnreserveStageOpcode(t *testing.T) {
pkt := &MsgSysUnreserveStage{}
if pkt.Opcode() != network.MSG_SYS_UNRESERVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_UNRESERVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysSetStagePassOpcode tests Opcode method
func TestMsgSysSetStagePassOpcode(t *testing.T) {
pkt := &MsgSysSetStagePass{}
if pkt.Opcode() != network.MSG_SYS_SET_STAGE_PASS {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STAGE_PASS", pkt.Opcode())
}
}
// TestMsgSysLockGlobalSemaOpcode tests Opcode method
func TestMsgSysLockGlobalSemaOpcode(t *testing.T) {
pkt := &MsgSysLockGlobalSema{}
if pkt.Opcode() != network.MSG_SYS_LOCK_GLOBAL_SEMA {
t.Errorf("Opcode() = %s, want MSG_SYS_LOCK_GLOBAL_SEMA", pkt.Opcode())
}
}
// TestMsgSysUnlockGlobalSemaOpcode tests Opcode method
func TestMsgSysUnlockGlobalSemaOpcode(t *testing.T) {
pkt := &MsgSysUnlockGlobalSema{}
if pkt.Opcode() != network.MSG_SYS_UNLOCK_GLOBAL_SEMA {
t.Errorf("Opcode() = %s, want MSG_SYS_UNLOCK_GLOBAL_SEMA", pkt.Opcode())
}
}
// TestMsgSysTransBinaryOpcode tests Opcode method
func TestMsgSysTransBinaryOpcode(t *testing.T) {
pkt := &MsgSysTransBinary{}
if pkt.Opcode() != network.MSG_SYS_TRANS_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_TRANS_BINARY", pkt.Opcode())
}
}
// TestMsgSysCollectBinaryOpcode tests Opcode method
func TestMsgSysCollectBinaryOpcode(t *testing.T) {
pkt := &MsgSysCollectBinary{}
if pkt.Opcode() != network.MSG_SYS_COLLECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_COLLECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetStateOpcode tests Opcode method
func TestMsgSysGetStateOpcode(t *testing.T) {
pkt := &MsgSysGetState{}
if pkt.Opcode() != network.MSG_SYS_GET_STATE {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_STATE", pkt.Opcode())
}
}
// TestMsgSysSerializeOpcode tests Opcode method
func TestMsgSysSerializeOpcode(t *testing.T) {
pkt := &MsgSysSerialize{}
if pkt.Opcode() != network.MSG_SYS_SERIALIZE {
t.Errorf("Opcode() = %s, want MSG_SYS_SERIALIZE", pkt.Opcode())
}
}
// TestMsgSysEnumlobbyOpcode tests Opcode method
func TestMsgSysEnumlobbyOpcode(t *testing.T) {
pkt := &MsgSysEnumlobby{}
if pkt.Opcode() != network.MSG_SYS_ENUMLOBBY {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMLOBBY", pkt.Opcode())
}
}
// TestMsgSysEnumuserOpcode tests Opcode method
func TestMsgSysEnumuserOpcode(t *testing.T) {
pkt := &MsgSysEnumuser{}
if pkt.Opcode() != network.MSG_SYS_ENUMUSER {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMUSER", pkt.Opcode())
}
}
// TestMsgSysInfokyserverOpcode tests Opcode method
func TestMsgSysInfokyserverOpcode(t *testing.T) {
pkt := &MsgSysInfokyserver{}
if pkt.Opcode() != network.MSG_SYS_INFOKYSERVER {
t.Errorf("Opcode() = %s, want MSG_SYS_INFOKYSERVER", pkt.Opcode())
}
}
// TestMsgSysExtendThresholdOpcode tests Opcode method
func TestMsgSysExtendThresholdOpcode(t *testing.T) {
pkt := &MsgSysExtendThreshold{}
if pkt.Opcode() != network.MSG_SYS_EXTEND_THRESHOLD {
t.Errorf("Opcode() = %s, want MSG_SYS_EXTEND_THRESHOLD", pkt.Opcode())
}
}
// TestMsgSysAddObjectOpcode tests Opcode method
func TestMsgSysAddObjectOpcode(t *testing.T) {
pkt := &MsgSysAddObject{}
if pkt.Opcode() != network.MSG_SYS_ADD_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_ADD_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDelObjectOpcode tests Opcode method
func TestMsgSysDelObjectOpcode(t *testing.T) {
pkt := &MsgSysDelObject{}
if pkt.Opcode() != network.MSG_SYS_DEL_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DEL_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDispObjectOpcode tests Opcode method
func TestMsgSysDispObjectOpcode(t *testing.T) {
pkt := &MsgSysDispObject{}
if pkt.Opcode() != network.MSG_SYS_DISP_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DISP_OBJECT", pkt.Opcode())
}
}
// TestMsgSysHideObjectOpcode tests Opcode method
func TestMsgSysHideObjectOpcode(t *testing.T) {
pkt := &MsgSysHideObject{}
if pkt.Opcode() != network.MSG_SYS_HIDE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_HIDE_OBJECT", pkt.Opcode())
}
}

View File

@@ -0,0 +1,332 @@
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(append(stageIDBytes, 0x00))
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
unk bool
stageID string
}{
{"enter town", 1, false, "town01"},
{"force enter", 2, true, "quest_stage"},
{"rasta bar", 999, false, "sl1Ns211p0a0u0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteBool(tt.unk)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(append(stageIDBytes, 0x00))
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.Unk != tt.unk {
t.Errorf("Unk = %v, want %v", pkt.Unk, tt.unk)
}
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(append(stageIDBytes, 0x00))
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)
}
// Unk0 and Unk1 are read but discarded by Parse, so we only verify
// that Parse consumed the bytes without error
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)
}
// MsgSysUnlockStage is an empty struct; Parse reads and discards a uint16.
// We just verify Parse doesn't error.
})
}
}
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(append(longID, 0x00))
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)
}
})
}
}

211
network/packetid_test.go Normal file
View File

@@ -0,0 +1,211 @@
package network
import (
"testing"
)
func TestPacketIDType(t *testing.T) {
// PacketID is based on uint16
var p PacketID = 0xFFFF
if uint16(p) != 0xFFFF {
t.Errorf("PacketID max value = %d, want %d", uint16(p), 0xFFFF)
}
}
func TestPacketIDConstants(t *testing.T) {
// Test critical packet IDs are correct
tests := []struct {
name string
id PacketID
expect uint16
}{
{"MSG_HEAD", MSG_HEAD, 0},
{"MSG_SYS_END", MSG_SYS_END, 0x10},
{"MSG_SYS_NOP", MSG_SYS_NOP, 0x11},
{"MSG_SYS_ACK", MSG_SYS_ACK, 0x12},
{"MSG_SYS_LOGIN", MSG_SYS_LOGIN, 0x14},
{"MSG_SYS_LOGOUT", MSG_SYS_LOGOUT, 0x15},
{"MSG_SYS_PING", MSG_SYS_PING, 0x17},
{"MSG_SYS_TIME", MSG_SYS_TIME, 0x1A},
{"MSG_SYS_CREATE_STAGE", MSG_SYS_CREATE_STAGE, 0x20},
{"MSG_SYS_ENTER_STAGE", MSG_SYS_ENTER_STAGE, 0x22},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if uint16(tt.id) != tt.expect {
t.Errorf("%s = 0x%X, want 0x%X", tt.name, uint16(tt.id), tt.expect)
}
})
}
}
func TestPacketIDString(t *testing.T) {
// Test that String() method works for known packet IDs
tests := []struct {
id PacketID
contains string
}{
{MSG_HEAD, "MSG_HEAD"},
{MSG_SYS_PING, "MSG_SYS_PING"},
{MSG_SYS_END, "MSG_SYS_END"},
{MSG_SYS_NOP, "MSG_SYS_NOP"},
{MSG_SYS_ACK, "MSG_SYS_ACK"},
{MSG_SYS_LOGIN, "MSG_SYS_LOGIN"},
{MSG_SYS_LOGOUT, "MSG_SYS_LOGOUT"},
}
for _, tt := range tests {
t.Run(tt.contains, func(t *testing.T) {
got := tt.id.String()
if got != tt.contains {
t.Errorf("String() = %q, want %q", got, tt.contains)
}
})
}
}
func TestPacketIDUnknown(t *testing.T) {
// Unknown packet ID should still have a valid string representation
unknown := PacketID(0xFFFF)
str := unknown.String()
if str == "" {
t.Error("String() for unknown PacketID should not be empty")
}
}
func TestPacketIDZero(t *testing.T) {
// MSG_HEAD should be 0
if MSG_HEAD != 0 {
t.Errorf("MSG_HEAD = %d, want 0", MSG_HEAD)
}
}
func TestSystemPacketIDRange(t *testing.T) {
// System packets should be in a specific range
systemPackets := []PacketID{
MSG_SYS_reserve01,
MSG_SYS_reserve02,
MSG_SYS_reserve03,
MSG_SYS_ADD_OBJECT,
MSG_SYS_DEL_OBJECT,
MSG_SYS_END,
MSG_SYS_NOP,
MSG_SYS_ACK,
MSG_SYS_LOGIN,
MSG_SYS_LOGOUT,
MSG_SYS_PING,
MSG_SYS_TIME,
}
for _, pkt := range systemPackets {
// System packets should have IDs > 0 (MSG_HEAD is 0)
if pkt < MSG_SYS_reserve01 {
t.Errorf("System packet %s has ID %d, should be >= MSG_SYS_reserve01", pkt, pkt)
}
}
}
func TestMHFPacketIDRange(t *testing.T) {
// MHF packets start at MSG_MHF_SAVEDATA (0x60)
mhfPackets := []PacketID{
MSG_MHF_SAVEDATA,
MSG_MHF_LOADDATA,
MSG_MHF_ENUMERATE_QUEST,
MSG_MHF_ACQUIRE_TITLE,
MSG_MHF_ACQUIRE_DIST_ITEM,
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
}
for _, pkt := range mhfPackets {
// MHF packets should be >= MSG_MHF_SAVEDATA
if pkt < MSG_MHF_SAVEDATA {
t.Errorf("MHF packet %s has ID %d, should be >= MSG_MHF_SAVEDATA (%d)", pkt, pkt, MSG_MHF_SAVEDATA)
}
}
}
func TestStagePacketIDsSequential(t *testing.T) {
// Stage-related packets should be sequential
stagePackets := []PacketID{
MSG_SYS_CREATE_STAGE,
MSG_SYS_STAGE_DESTRUCT,
MSG_SYS_ENTER_STAGE,
MSG_SYS_BACK_STAGE,
MSG_SYS_MOVE_STAGE,
MSG_SYS_LEAVE_STAGE,
MSG_SYS_LOCK_STAGE,
MSG_SYS_UNLOCK_STAGE,
}
for i := 1; i < len(stagePackets); i++ {
if stagePackets[i] != stagePackets[i-1]+1 {
t.Errorf("Stage packets not sequential: %s (%d) should follow %s (%d)",
stagePackets[i], stagePackets[i], stagePackets[i-1], stagePackets[i-1])
}
}
}
func TestPacketIDUniqueness(t *testing.T) {
// Sample of important packet IDs should be unique
packets := []PacketID{
MSG_HEAD,
MSG_SYS_END,
MSG_SYS_NOP,
MSG_SYS_ACK,
MSG_SYS_LOGIN,
MSG_SYS_LOGOUT,
MSG_SYS_PING,
MSG_SYS_TIME,
MSG_SYS_CREATE_STAGE,
MSG_SYS_ENTER_STAGE,
MSG_MHF_SAVEDATA,
MSG_MHF_LOADDATA,
}
seen := make(map[PacketID]bool)
for _, pkt := range packets {
if seen[pkt] {
t.Errorf("Duplicate PacketID: %s (%d)", pkt, pkt)
}
seen[pkt] = true
}
}
func TestAcquirePacketIDs(t *testing.T) {
// Verify acquire-related packet IDs exist and are correct type
acquirePackets := []PacketID{
MSG_MHF_ACQUIRE_DIST_ITEM,
MSG_MHF_ACQUIRE_TITLE,
MSG_MHF_ACQUIRE_ITEM,
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
MSG_MHF_ACQUIRE_CAFE_ITEM,
MSG_MHF_ACQUIRE_GUILD_TRESURE,
}
for _, pkt := range acquirePackets {
str := pkt.String()
if str == "" {
t.Errorf("PacketID %d should have a string representation", pkt)
}
}
}
func TestGuildPacketIDs(t *testing.T) {
// Verify guild-related packet IDs
guildPackets := []PacketID{
MSG_MHF_CREATE_GUILD,
MSG_MHF_OPERATE_GUILD,
MSG_MHF_OPERATE_GUILD_MEMBER,
MSG_MHF_INFO_GUILD,
MSG_MHF_ENUMERATE_GUILD,
MSG_MHF_UPDATE_GUILD,
}
for _, pkt := range guildPackets {
// All guild packets should be MHF packets
if pkt < MSG_MHF_SAVEDATA {
t.Errorf("Guild packet %s should be an MHF packet (>= 0x60)", pkt)
}
}
}

View File

@@ -0,0 +1,229 @@
package channelserver
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
// createMockServerWithRaviente creates a mock server with raviente and semaphore
// initialized, which the base createMockServer() does not do.
func createMockServerWithRaviente() *Server {
s := createMockServer()
s.raviente = &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
}
s.semaphore = make(map[string]*Semaphore)
return s
}
func TestRavienteInitialization(t *testing.T) {
r := &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
}
if r == nil {
t.Fatal("Raviente is nil")
}
if len(r.register) != 30 {
t.Errorf("register length = %d, want 30", len(r.register))
}
if len(r.state) != 30 {
t.Errorf("state length = %d, want 30", len(r.state))
}
if len(r.support) != 30 {
t.Errorf("support length = %d, want 30", len(r.support))
}
// All values should be zero-initialized
for i, v := range r.register {
if v != 0 {
t.Errorf("register[%d] = %d, want 0", i, v)
}
}
for i, v := range r.state {
if v != 0 {
t.Errorf("state[%d] = %d, want 0", i, v)
}
}
for i, v := range r.support {
if v != 0 {
t.Errorf("support[%d] = %d, want 0", i, v)
}
}
if r.id != 0 {
t.Errorf("id = %d, want 0", r.id)
}
}
func TestRavienteMutex(t *testing.T) {
r := &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
}
// Test that we can lock and unlock without deadlock
r.Lock()
r.register[0] = 42
r.Unlock()
r.Lock()
val := r.register[0]
r.Unlock()
if val != 42 {
t.Errorf("register[0] = %d, want 42", val)
}
}
func TestRavienteDataAccess(t *testing.T) {
r := &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
}
// Write and verify register data
r.register[0] = 100
r.register[4] = 200
r.register[29] = 300
if r.register[0] != 100 {
t.Errorf("register[0] = %d, want 100", r.register[0])
}
if r.register[4] != 200 {
t.Errorf("register[4] = %d, want 200", r.register[4])
}
if r.register[29] != 300 {
t.Errorf("register[29] = %d, want 300", r.register[29])
}
// Write and verify state data
r.state[0] = 500
r.state[28] = 600
if r.state[0] != 500 {
t.Errorf("state[0] = %d, want 500", r.state[0])
}
if r.state[28] != 600 {
t.Errorf("state[28] = %d, want 600", r.state[28])
}
// Write and verify support data
r.support[0] = 700
r.support[24] = 800
if r.support[0] != 700 {
t.Errorf("support[0] = %d, want 700", r.support[0])
}
if r.support[24] != 800 {
t.Errorf("support[24] = %d, want 800", r.support[24])
}
}
func TestRavienteID(t *testing.T) {
r := &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
}
r.id = 12345
if r.id != 12345 {
t.Errorf("id = %d, want 12345", r.id)
}
r.id = 0xFFFF
if r.id != 0xFFFF {
t.Errorf("id = %d, want %d", r.id, uint16(0xFFFF))
}
}
func TestCreateMockServerWithRaviente(t *testing.T) {
s := createMockServerWithRaviente()
if s == nil {
t.Fatal("createMockServerWithRaviente() returned nil")
}
if s.raviente == nil {
t.Fatal("raviente should not be nil")
}
if s.semaphore == nil {
t.Fatal("semaphore should not be nil")
}
if len(s.raviente.register) != 30 {
t.Errorf("raviente register length = %d, want 30", len(s.raviente.register))
}
if len(s.raviente.state) != 30 {
t.Errorf("raviente state length = %d, want 30", len(s.raviente.state))
}
if len(s.raviente.support) != 30 {
t.Errorf("raviente support length = %d, want 30", len(s.raviente.support))
}
}
func TestHandlerTableRegistered(t *testing.T) {
s := createMockServer()
if s == nil {
t.Fatal("createMockServer() returned nil")
}
// Verify handler table is populated
if len(handlerTable) == 0 {
t.Error("handlers table should not be empty")
}
// Check that key handler types are registered
// (these are critical handlers that must always be present)
criticalHandlers := []string{
"handleMsgSysCreateStage",
"handleMsgSysStageDestruct",
}
_ = criticalHandlers // We just verify the table is non-empty since handler function names aren't directly accessible
// Verify minimum handler count
if len(handlerTable) < 50 {
t.Errorf("handlers count = %d, expected at least 50", len(handlerTable))
}
}
func TestHandlerTableNilSession(t *testing.T) {
// This test verifies that the handler table exists and has entries
// but doesn't call handlers (which would require a real session)
_ = createMockServer()
count := 0
for range handlerTable {
count++
}
if count == 0 {
t.Error("No handlers registered")
}
}
func TestMockServerPacketHandling(t *testing.T) {
s := createMockServerWithRaviente()
session := createMockSession(1, s)
// Verify the session and server are properly linked
if session.server != s {
t.Error("Session server reference mismatch")
}
// Verify byteframe can be created for packet construction
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // AckHandle
if len(bf.Data()) != 4 {
t.Errorf("ByteFrame length = %d, want 4", len(bf.Data()))
}
// Verify packet types can be instantiated
pkt := &mhfpacket.MsgSysAck{}
if pkt == nil {
t.Error("Failed to create MsgSysAck")
}
}

View File

@@ -0,0 +1,522 @@
package entranceserver
import (
"net"
"testing"
"time"
_config "erupe-ce/config"
"go.uber.org/zap"
)
func TestNewServer(t *testing.T) {
cfg := &Config{
Logger: nil,
DB: nil,
ErupeConfig: &_config.Config{},
}
s := NewServer(cfg)
if s == nil {
t.Fatal("NewServer() returned nil")
}
if s.isShuttingDown {
t.Error("New server should not be shutting down")
}
if s.erupeConfig == nil {
t.Error("erupeConfig should not be nil")
}
}
func TestNewServerWithNilConfig(t *testing.T) {
cfg := &Config{}
s := NewServer(cfg)
if s == nil {
t.Fatal("NewServer() returned nil for empty config")
}
}
func TestServerType(t *testing.T) {
s := &Server{}
if s.isShuttingDown {
t.Error("Zero value server should not be shutting down")
}
if s.listener != nil {
t.Error("Zero value server should have nil listener")
}
}
func TestConfigFields(t *testing.T) {
cfg := &Config{
Logger: nil,
DB: nil,
ErupeConfig: nil,
}
if cfg.Logger != nil {
t.Error("Config Logger should be nil")
}
if cfg.DB != nil {
t.Error("Config DB should be nil")
}
if cfg.ErupeConfig != nil {
t.Error("Config ErupeConfig should be nil")
}
}
func TestServerShutdownFlag(t *testing.T) {
cfg := &Config{
ErupeConfig: &_config.Config{},
}
s := NewServer(cfg)
if s.isShuttingDown {
t.Error("New server should not be shutting down")
}
s.Lock()
s.isShuttingDown = true
s.Unlock()
if !s.isShuttingDown {
t.Error("Server should be shutting down after flag is set")
}
}
func TestServerConfigStorage(t *testing.T) {
erupeConfig := &_config.Config{
Host: "192.168.1.100",
Entrance: _config.Entrance{
Enabled: true,
Port: 53310,
Entries: []_config.EntranceServerInfo{
{
Name: "Test Server",
IP: "127.0.0.1",
Type: 1,
},
},
},
}
cfg := &Config{
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
if s.erupeConfig.Host != "192.168.1.100" {
t.Errorf("Host = %s, want 192.168.1.100", s.erupeConfig.Host)
}
if s.erupeConfig.Entrance.Port != 53310 {
t.Errorf("Entrance.Port = %d, want 53310", s.erupeConfig.Entrance.Port)
}
}
func TestServerEntranceEntries(t *testing.T) {
entries := []_config.EntranceServerInfo{
{
Name: "World 1",
IP: "10.0.0.1",
Type: 1,
Recommended: 1,
Channels: []_config.EntranceChannelInfo{
{Port: 54001, MaxPlayers: 100},
{Port: 54002, MaxPlayers: 100},
},
},
{
Name: "World 2",
IP: "10.0.0.2",
Type: 2,
Recommended: 0,
Channels: []_config.EntranceChannelInfo{
{Port: 54003, MaxPlayers: 50},
},
},
}
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 53310,
Entries: entries,
},
}
cfg := &Config{ErupeConfig: erupeConfig}
s := NewServer(cfg)
if len(s.erupeConfig.Entrance.Entries) != 2 {
t.Errorf("Entries count = %d, want 2", len(s.erupeConfig.Entrance.Entries))
}
if s.erupeConfig.Entrance.Entries[0].Name != "World 1" {
t.Errorf("First entry name = %s, want World 1", s.erupeConfig.Entrance.Entries[0].Name)
}
if len(s.erupeConfig.Entrance.Entries[0].Channels) != 2 {
t.Errorf("First entry channels = %d, want 2", len(s.erupeConfig.Entrance.Entries[0].Channels))
}
}
func TestEncryptDecryptRoundTrip(t *testing.T) {
tests := []struct {
name string
data []byte
key byte
}{
{"empty", []byte{}, 0x00},
{"single byte", []byte{0x42}, 0x00},
{"multiple bytes", []byte{0x01, 0x02, 0x03, 0x04}, 0x00},
{"with key", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0x55},
{"max key", []byte{0x01, 0x02}, 0xFF},
{"long data", make([]byte, 100), 0x42},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
encrypted := EncryptBin8(tt.data, tt.key)
decrypted := DecryptBin8(encrypted, tt.key)
if len(decrypted) != len(tt.data) {
t.Errorf("decrypted length = %d, want %d", len(decrypted), len(tt.data))
return
}
for i := range tt.data {
if decrypted[i] != tt.data[i] {
t.Errorf("decrypted[%d] = 0x%X, want 0x%X", i, decrypted[i], tt.data[i])
}
}
})
}
}
func TestCalcSum32Deterministic(t *testing.T) {
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
sum1 := CalcSum32(data)
sum2 := CalcSum32(data)
if sum1 != sum2 {
t.Errorf("CalcSum32 not deterministic: got 0x%X and 0x%X", sum1, sum2)
}
}
func TestCalcSum32DifferentInputs(t *testing.T) {
data1 := []byte{0x01, 0x02, 0x03}
data2 := []byte{0x01, 0x02, 0x04}
sum1 := CalcSum32(data1)
sum2 := CalcSum32(data2)
if sum1 == sum2 {
t.Error("Different inputs should produce different checksums")
}
}
func TestEncryptBin8KeyVariation(t *testing.T) {
data := []byte{0x01, 0x02, 0x03, 0x04}
enc1 := EncryptBin8(data, 0x00)
enc2 := EncryptBin8(data, 0x01)
enc3 := EncryptBin8(data, 0xFF)
if bytesEqual(enc1, enc2) {
t.Error("Different keys should produce different encrypted data (0x00 vs 0x01)")
}
if bytesEqual(enc2, enc3) {
t.Error("Different keys should produce different encrypted data (0x01 vs 0xFF)")
}
}
func bytesEqual(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestEncryptBin8LengthPreservation(t *testing.T) {
lengths := []int{0, 1, 7, 8, 9, 100, 1000}
for _, length := range lengths {
data := make([]byte, length)
for i := range data {
data[i] = byte(i % 256)
}
encrypted := EncryptBin8(data, 0x42)
if len(encrypted) != length {
t.Errorf("EncryptBin8 length %d changed to %d", length, len(encrypted))
}
}
}
func TestCalcSum32LargeInput(t *testing.T) {
data := make([]byte, 10000)
for i := range data {
data[i] = byte(i % 256)
}
sum := CalcSum32(data)
sum2 := CalcSum32(data)
if sum != sum2 {
t.Errorf("CalcSum32 inconsistent for large input: 0x%X vs 0x%X", sum, sum2)
}
}
func TestServerMutexLocking(t *testing.T) {
cfg := &Config{ErupeConfig: &_config.Config{}}
s := NewServer(cfg)
s.Lock()
s.isShuttingDown = true
s.Unlock()
s.Lock()
result := s.isShuttingDown
s.Unlock()
if !result {
t.Error("Mutex should protect isShuttingDown flag")
}
}
func TestServerStartAndShutdown(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
if s.listener == nil {
t.Error("Server listener should not be nil after Start()")
}
s.Lock()
if s.isShuttingDown {
t.Error("Server should not be shutting down after Start()")
}
s.Unlock()
s.Shutdown()
s.Lock()
if !s.isShuttingDown {
t.Error("Server should be shutting down after Shutdown()")
}
s.Unlock()
}
func TestServerStartWithInvalidPort(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Port: 1,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err == nil {
s.Shutdown()
t.Error("Start() should fail with invalid port")
}
}
func TestServerListenerAddress(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr()
if addr == nil {
t.Error("Listener address should not be nil")
}
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
t.Error("Listener address should be a TCP address")
}
if tcpAddr.Port == 0 {
t.Error("Listener port should be assigned")
}
}
func TestServerAcceptClientsExitsOnShutdown(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
time.Sleep(10 * time.Millisecond)
s.Shutdown()
time.Sleep(10 * time.Millisecond)
s.Lock()
if !s.isShuttingDown {
t.Error("Server should be marked as shutting down")
}
s.Unlock()
}
func TestServerHandleConnectionImmediateClose(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() error: %v", err)
}
conn.Close()
time.Sleep(50 * time.Millisecond)
}
func TestServerHandleConnectionShortInit(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() error: %v", err)
}
_, _ = conn.Write([]byte{0, 0, 0, 0})
conn.Close()
time.Sleep(50 * time.Millisecond)
}
func TestServerMultipleConnections(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Entrance: _config.Entrance{
Enabled: true,
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conns := make([]net.Conn, 3)
for i := range conns {
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() %d error: %v", i, err)
}
conns[i] = conn
}
time.Sleep(50 * time.Millisecond)
for _, conn := range conns {
conn.Close()
}
}

View File

@@ -0,0 +1,825 @@
package signserver
import (
"database/sql"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
func TestCharacterStruct(t *testing.T) {
c := character{
ID: 12345,
IsFemale: true,
IsNewCharacter: false,
Name: "TestHunter",
UnkDescString: "Test description",
HR: 999,
GR: 300,
WeaponType: 5,
LastLogin: 1700000000,
}
if c.ID != 12345 {
t.Errorf("ID = %d, want 12345", c.ID)
}
if c.IsFemale != true {
t.Error("IsFemale should be true")
}
if c.IsNewCharacter != false {
t.Error("IsNewCharacter should be false")
}
if c.Name != "TestHunter" {
t.Errorf("Name = %s, want TestHunter", c.Name)
}
if c.UnkDescString != "Test description" {
t.Errorf("UnkDescString = %s, want Test description", c.UnkDescString)
}
if c.HR != 999 {
t.Errorf("HR = %d, want 999", c.HR)
}
if c.GR != 300 {
t.Errorf("GR = %d, want 300", c.GR)
}
if c.WeaponType != 5 {
t.Errorf("WeaponType = %d, want 5", c.WeaponType)
}
if c.LastLogin != 1700000000 {
t.Errorf("LastLogin = %d, want 1700000000", c.LastLogin)
}
}
func TestCharacterStructDefaults(t *testing.T) {
c := character{}
if c.ID != 0 {
t.Errorf("default ID = %d, want 0", c.ID)
}
if c.IsFemale != false {
t.Error("default IsFemale should be false")
}
if c.IsNewCharacter != false {
t.Error("default IsNewCharacter should be false")
}
if c.Name != "" {
t.Errorf("default Name = %s, want empty", c.Name)
}
if c.HR != 0 {
t.Errorf("default HR = %d, want 0", c.HR)
}
if c.GR != 0 {
t.Errorf("default GR = %d, want 0", c.GR)
}
if c.WeaponType != 0 {
t.Errorf("default WeaponType = %d, want 0", c.WeaponType)
}
}
func TestMembersStruct(t *testing.T) {
m := members{
CID: 100,
ID: 200,
Name: "FriendName",
}
if m.CID != 100 {
t.Errorf("CID = %d, want 100", m.CID)
}
if m.ID != 200 {
t.Errorf("ID = %d, want 200", m.ID)
}
if m.Name != "FriendName" {
t.Errorf("Name = %s, want FriendName", m.Name)
}
}
func TestMembersStructDefaults(t *testing.T) {
m := members{}
if m.CID != 0 {
t.Errorf("default CID = %d, want 0", m.CID)
}
if m.ID != 0 {
t.Errorf("default ID = %d, want 0", m.ID)
}
if m.Name != "" {
t.Errorf("default Name = %s, want empty", m.Name)
}
}
func TestCharacterWeaponTypes(t *testing.T) {
weaponTypes := []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
for _, wt := range weaponTypes {
c := character{WeaponType: wt}
if c.WeaponType != wt {
t.Errorf("WeaponType = %d, want %d", c.WeaponType, wt)
}
}
}
func TestCharacterHRRange(t *testing.T) {
tests := []struct {
name string
hr uint16
}{
{"min", 0},
{"beginner", 1},
{"hr30", 30},
{"hr50", 50},
{"hr99", 99},
{"hr299", 299},
{"hr998", 998},
{"hr999", 999},
{"max uint16", 65535},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := character{HR: tt.hr}
if c.HR != tt.hr {
t.Errorf("HR = %d, want %d", c.HR, tt.hr)
}
})
}
}
func TestCharacterGRRange(t *testing.T) {
tests := []struct {
name string
gr uint16
}{
{"min", 0},
{"gr1", 1},
{"gr100", 100},
{"gr300", 300},
{"gr999", 999},
{"max uint16", 65535},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := character{GR: tt.gr}
if c.GR != tt.gr {
t.Errorf("GR = %d, want %d", c.GR, tt.gr)
}
})
}
}
func TestCharacterIDRange(t *testing.T) {
tests := []struct {
name string
id uint32
}{
{"min", 0},
{"small", 1},
{"medium", 1000000},
{"large", 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := character{ID: tt.id}
if c.ID != tt.id {
t.Errorf("ID = %d, want %d", c.ID, tt.id)
}
})
}
}
func TestCharacterGender(t *testing.T) {
male := character{IsFemale: false}
if male.IsFemale != false {
t.Error("Male character should have IsFemale = false")
}
female := character{IsFemale: true}
if female.IsFemale != true {
t.Error("Female character should have IsFemale = true")
}
}
func TestCharacterNewStatus(t *testing.T) {
newChar := character{IsNewCharacter: true}
if newChar.IsNewCharacter != true {
t.Error("New character should have IsNewCharacter = true")
}
existingChar := character{IsNewCharacter: false}
if existingChar.IsNewCharacter != false {
t.Error("Existing character should have IsNewCharacter = false")
}
}
func TestCharacterNameLength(t *testing.T) {
names := []string{
"",
"A",
"Hunter",
"LongHunterName123",
}
for _, name := range names {
c := character{Name: name}
if c.Name != name {
t.Errorf("Name = %s, want %s", c.Name, name)
}
}
}
func TestCharacterLastLogin(t *testing.T) {
tests := []struct {
name string
lastLogin uint32
}{
{"zero", 0},
{"past", 1600000000},
{"present", 1700000000},
{"future", 1800000000},
{"max", 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := character{LastLogin: tt.lastLogin}
if c.LastLogin != tt.lastLogin {
t.Errorf("LastLogin = %d, want %d", c.LastLogin, tt.lastLogin)
}
})
}
}
func TestMembersCIDAssignment(t *testing.T) {
m := members{CID: 12345}
if m.CID != 12345 {
t.Errorf("CID = %d, want 12345", m.CID)
}
}
func TestMultipleCharacters(t *testing.T) {
chars := []character{
{ID: 1, Name: "Char1", HR: 100},
{ID: 2, Name: "Char2", HR: 200},
{ID: 3, Name: "Char3", HR: 300},
}
for i, c := range chars {
expectedID := uint32(i + 1)
if c.ID != expectedID {
t.Errorf("chars[%d].ID = %d, want %d", i, c.ID, expectedID)
}
}
}
func TestMultipleMembers(t *testing.T) {
membersList := []members{
{CID: 1, ID: 10, Name: "Friend1"},
{CID: 1, ID: 20, Name: "Friend2"},
{CID: 2, ID: 30, Name: "Friend3"},
}
if membersList[0].CID != membersList[1].CID {
t.Error("First two members should share the same CID")
}
if membersList[1].CID == membersList[2].CID {
t.Error("Third member should have different CID")
}
}
// Helper to create a test server with mocked database
func newTestServerWithMock(t *testing.T) (*Server, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: zap.NewNop(),
db: sqlxDB,
}
return server, mock
}
func TestGetCharactersForUser(t *testing.T) {
server, mock := newTestServerWithMock(t)
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"}).
AddRow(1, false, false, "Hunter1", "desc1", 100, 50, 3, 1700000000).
AddRow(2, true, false, "Hunter2", "desc2", 200, 100, 7, 1700000001)
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
WithArgs(uint32(1)).
WillReturnRows(rows)
chars, err := server.getCharactersForUser(1)
if err != nil {
t.Errorf("getCharactersForUser() error: %v", err)
}
if len(chars) != 2 {
t.Errorf("getCharactersForUser() returned %d characters, want 2", len(chars))
}
if chars[0].Name != "Hunter1" {
t.Errorf("First character name = %s, want Hunter1", chars[0].Name)
}
if chars[1].IsFemale != true {
t.Error("Second character should be female")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetCharactersForUserNoCharacters(t *testing.T) {
server, mock := newTestServerWithMock(t)
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"})
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
WithArgs(uint32(1)).
WillReturnRows(rows)
chars, err := server.getCharactersForUser(1)
if err != nil {
t.Errorf("getCharactersForUser() error: %v", err)
}
if len(chars) != 0 {
t.Errorf("getCharactersForUser() returned %d characters, want 0", len(chars))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetCharactersForUserDBError(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
WithArgs(uint32(1)).
WillReturnError(sql.ErrConnDone)
_, err := server.getCharactersForUser(1)
if err == nil {
t.Error("getCharactersForUser() should return error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetLastCID(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(12345))
lastCID := server.getLastCID(1)
if lastCID != 12345 {
t.Errorf("getLastCID() = %d, want 12345", lastCID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetLastCIDNoResult(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnError(sql.ErrNoRows)
lastCID := server.getLastCID(1)
if lastCID != 0 {
t.Errorf("getLastCID() with no result = %d, want 0", lastCID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetUserRights(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(30))
rights := server.getUserRights(1)
if rights == 0 {
t.Error("getUserRights() should return non-zero value")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetReturnExpiry(t *testing.T) {
server, mock := newTestServerWithMock(t)
recentLogin := time.Now().Add(-time.Hour * 24)
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"return_expires"}).AddRow(time.Now().Add(time.Hour * 24 * 30)))
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), uint32(1)).
WillReturnResult(sqlmock.NewResult(0, 1))
expiry := server.getReturnExpiry(1)
if expiry.Before(time.Now()) {
t.Error("getReturnExpiry() should return future date")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetReturnExpiryInactiveUser(t *testing.T) {
server, mock := newTestServerWithMock(t)
oldLogin := time.Now().Add(-time.Hour * 24 * 100)
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(oldLogin))
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), uint32(1)).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), uint32(1)).
WillReturnResult(sqlmock.NewResult(0, 1))
expiry := server.getReturnExpiry(1)
if expiry.Before(time.Now()) {
t.Error("getReturnExpiry() should return future date for inactive user")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetReturnExpiryDBError(t *testing.T) {
server, mock := newTestServerWithMock(t)
recentLogin := time.Now().Add(-time.Hour * 24)
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnError(sql.ErrNoRows)
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), uint32(1)).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), uint32(1)).
WillReturnResult(sqlmock.NewResult(0, 1))
expiry := server.getReturnExpiry(1)
if expiry.IsZero() {
t.Error("getReturnExpiry() should return non-zero time even on error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestNewUserChara(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
mock.ExpectExec("INSERT INTO characters").
WithArgs(uint32(1), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
err := server.newUserChara(1)
if err != nil {
t.Errorf("newUserChara() error: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestNewUserCharaAlreadyHasNewChar(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
err := server.newUserChara(1)
if err != nil {
t.Errorf("newUserChara() should return nil when user already has new char: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestNewUserCharaCountError(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(uint32(1)).
WillReturnError(sql.ErrConnDone)
err := server.newUserChara(1)
if err == nil {
t.Error("newUserChara() should return error when count query fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestNewUserCharaInsertError(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
mock.ExpectExec("INSERT INTO characters").
WithArgs(uint32(1), sqlmock.AnyArg()).
WillReturnError(sql.ErrConnDone)
err := server.newUserChara(1)
if err == nil {
t.Error("newUserChara() should return error when insert fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestRegisterDBAccount(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
uid, err := server.registerDBAccount("newuser", "password123")
if err != nil {
t.Errorf("registerDBAccount() error: %v", err)
}
if uid != 1 {
t.Errorf("registerDBAccount() uid = %d, want 1", uid)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestRegisterDBAccountDuplicateUser(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
WithArgs("existinguser", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnError(sql.ErrNoRows)
_, err := server.registerDBAccount("existinguser", "password123")
if err == nil {
t.Error("registerDBAccount() should return error for duplicate user")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestDeleteCharacter(t *testing.T) {
server, mock := newTestServerWithMock(t)
// validateToken: SELECT count(*) FROM sign_sessions WHERE token = $1
// When tokenID=0, query has no AND clause but both args are still passed to QueryRow
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("validtoken", uint32(0)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
WithArgs(123).
WillReturnResult(sqlmock.NewResult(0, 1))
err := server.deleteCharacter(123, "validtoken", 0)
if err != nil {
t.Errorf("deleteCharacter() error: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestDeleteNewCharacter(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("validtoken", uint32(0)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(true))
mock.ExpectExec("DELETE FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnResult(sqlmock.NewResult(0, 1))
err := server.deleteCharacter(123, "validtoken", 0)
if err != nil {
t.Errorf("deleteCharacter() error: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestDeleteCharacterInvalidToken(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("invalidtoken", uint32(0)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
err := server.deleteCharacter(123, "invalidtoken", 0)
if err == nil {
t.Error("deleteCharacter() should return error for invalid token")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestDeleteCharacterDeleteError(t *testing.T) {
server, mock := newTestServerWithMock(t)
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("validtoken", uint32(0)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
WithArgs(123).
WillReturnError(sql.ErrConnDone)
err := server.deleteCharacter(123, "validtoken", 0)
if err == nil {
t.Error("deleteCharacter() should return error when update fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetFriendsForCharactersEmpty(t *testing.T) {
server, _ := newTestServerWithMock(t)
chars := []character{}
friends := server.getFriendsForCharacters(chars)
if len(friends) != 0 {
t.Errorf("getFriendsForCharacters() for empty chars = %d, want 0", len(friends))
}
}
func TestGetGuildmatesForCharactersEmpty(t *testing.T) {
server, _ := newTestServerWithMock(t)
chars := []character{}
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 0 {
t.Errorf("getGuildmatesForCharacters() for empty chars = %d, want 0", len(guildmates))
}
}
func TestGetFriendsForCharacters(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"friends"}).AddRow("2,3"))
mock.ExpectQuery("SELECT id, name FROM characters WHERE id=2 OR id=3").
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
AddRow(2, "Friend1").
AddRow(3, "Friend2"))
friends := server.getFriendsForCharacters(chars)
if len(friends) != 2 {
t.Errorf("getFriendsForCharacters() = %d, want 2", len(friends))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetGuildmatesForCharacters(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
mock.ExpectQuery("SELECT guild_id FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"guild_id"}).AddRow(100))
mock.ExpectQuery("SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=\\$1 AND character_id!=\\$2").
WithArgs(100, uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
AddRow(2, "Guildmate1").
AddRow(3, "Guildmate2"))
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 2 {
t.Errorf("getGuildmatesForCharacters() = %d, want 2", len(guildmates))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
func TestGetGuildmatesNotInGuild(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 0 {
t.Errorf("getGuildmatesForCharacters() for non-guild member = %d, want 0", len(guildmates))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}

View File

@@ -0,0 +1,393 @@
package signserver
import (
"bytes"
"io"
"net"
"sync"
"testing"
"time"
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network"
"go.uber.org/zap"
)
// mockConn implements net.Conn for testing
type mockConn struct {
readBuf *bytes.Buffer
writeBuf *bytes.Buffer
closed bool
mu sync.Mutex
}
func newMockConn() *mockConn {
return &mockConn{
readBuf: new(bytes.Buffer),
writeBuf: new(bytes.Buffer),
}
}
func (m *mockConn) Read(b []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return 0, io.EOF
}
return m.readBuf.Read(b)
}
func (m *mockConn) Write(b []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return 0, io.ErrClosedPipe
}
return m.writeBuf.Write(b)
}
func (m *mockConn) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
m.closed = true
return nil
}
func (m *mockConn) LocalAddr() net.Addr {
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 53312}
}
func (m *mockConn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345}
}
func (m *mockConn) SetDeadline(t time.Time) error { return nil }
func (m *mockConn) SetReadDeadline(t time.Time) error { return nil }
func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
func TestSessionStruct(t *testing.T) {
logger := zap.NewNop()
conn := newMockConn()
s := &Session{
logger: logger,
server: nil,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
}
if s.logger != logger {
t.Error("Session logger not set correctly")
}
if s.rawConn != conn {
t.Error("Session rawConn not set correctly")
}
if s.cryptConn == nil {
t.Error("Session cryptConn should not be nil")
}
}
func TestSessionStructDefaults(t *testing.T) {
s := &Session{}
if s.logger != nil {
t.Error("Default Session logger should be nil")
}
if s.server != nil {
t.Error("Default Session server should be nil")
}
if s.rawConn != nil {
t.Error("Default Session rawConn should be nil")
}
if s.cryptConn != nil {
t.Error("Default Session cryptConn should be nil")
}
}
func TestSessionMutex(t *testing.T) {
s := &Session{}
s.Lock()
s.Unlock()
done := make(chan bool)
go func() {
s.Lock()
time.Sleep(10 * time.Millisecond)
s.Unlock()
done <- true
}()
time.Sleep(5 * time.Millisecond)
s.Lock()
s.Unlock()
<-done
}
func TestHandlePacketUnknownRequest(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
}
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
}
conn := newMockConn()
session := &Session{
logger: logger,
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("UNKNOWN:100"))
bf.WriteNullTerminatedBytes([]byte("data"))
err := session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
}
func TestHandlePacketWithDevModeLogging(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
DebugOptions: _config.DebugOptions{
LogInboundMessages: true,
},
}
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
}
conn := newMockConn()
session := &Session{
logger: logger,
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("TEST:100"))
err := session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() with dev mode returned error: %v", err)
}
}
func TestHandlePacketRequestTypes(t *testing.T) {
tests := []struct {
name string
reqType string
}{
{"unknown", "UNKNOWN:100"},
{"invalid", "INVALID"},
{"empty_version", "TEST:"},
{"no_version", "NOVERSION"},
{"special_chars", "TEST@#$:100"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{}
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
}
conn := newMockConn()
session := &Session{
logger: logger,
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte(tt.reqType))
err := session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket(%s) returned error: %v", tt.reqType, err)
}
})
}
}
func TestMockConnImplementsNetConn(t *testing.T) {
var _ net.Conn = (*mockConn)(nil)
}
func TestMockConnReadWrite(t *testing.T) {
conn := newMockConn()
testData := []byte("hello")
conn.readBuf.Write(testData)
buf := make([]byte, len(testData))
n, err := conn.Read(buf)
if err != nil {
t.Errorf("Read() error: %v", err)
}
if n != len(testData) {
t.Errorf("Read() n = %d, want %d", n, len(testData))
}
if !bytes.Equal(buf, testData) {
t.Errorf("Read() data = %v, want %v", buf, testData)
}
outData := []byte("world")
n, err = conn.Write(outData)
if err != nil {
t.Errorf("Write() error: %v", err)
}
if n != len(outData) {
t.Errorf("Write() n = %d, want %d", n, len(outData))
}
if !bytes.Equal(conn.writeBuf.Bytes(), outData) {
t.Errorf("Write() buffer = %v, want %v", conn.writeBuf.Bytes(), outData)
}
}
func TestMockConnClose(t *testing.T) {
conn := newMockConn()
err := conn.Close()
if err != nil {
t.Errorf("Close() error: %v", err)
}
if !conn.closed {
t.Error("conn.closed should be true after Close()")
}
buf := make([]byte, 10)
_, err = conn.Read(buf)
if err != io.EOF {
t.Errorf("Read() after close should return EOF, got: %v", err)
}
_, err = conn.Write([]byte("test"))
if err != io.ErrClosedPipe {
t.Errorf("Write() after close should return ErrClosedPipe, got: %v", err)
}
}
func TestMockConnAddresses(t *testing.T) {
conn := newMockConn()
local := conn.LocalAddr()
if local == nil {
t.Error("LocalAddr() should not be nil")
}
if local.String() != "127.0.0.1:53312" {
t.Errorf("LocalAddr() = %s, want 127.0.0.1:53312", local.String())
}
remote := conn.RemoteAddr()
if remote == nil {
t.Error("RemoteAddr() should not be nil")
}
if remote.String() != "127.0.0.1:12345" {
t.Errorf("RemoteAddr() = %s, want 127.0.0.1:12345", remote.String())
}
}
func TestMockConnDeadlines(t *testing.T) {
conn := newMockConn()
deadline := time.Now().Add(time.Second)
if err := conn.SetDeadline(deadline); err != nil {
t.Errorf("SetDeadline() error: %v", err)
}
if err := conn.SetReadDeadline(deadline); err != nil {
t.Errorf("SetReadDeadline() error: %v", err)
}
if err := conn.SetWriteDeadline(deadline); err != nil {
t.Errorf("SetWriteDeadline() error: %v", err)
}
}
func TestSessionWithCryptConn(t *testing.T) {
conn := newMockConn()
cryptConn := network.NewCryptConn(conn)
if cryptConn == nil {
t.Fatal("NewCryptConn() returned nil")
}
session := &Session{
rawConn: conn,
cryptConn: cryptConn,
}
if session.cryptConn != cryptConn {
t.Error("Session cryptConn not set correctly")
}
}
func TestSessionWorkWithDevModeLogging(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
DebugOptions: _config.DebugOptions{
LogInboundMessages: true,
},
}
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
clientConn.Close()
session.work()
}
func TestSessionWorkWithEmptyRead(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
}
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
}
clientConn, serverConn := net.Pipe()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
clientConn.Close()
session.work()
}

View File

@@ -0,0 +1,582 @@
package signserver
import (
"fmt"
"net"
"testing"
"time"
_config "erupe-ce/config"
"go.uber.org/zap"
)
// makeSignInFailureResp creates a 1-byte failure response for the given RespID.
func makeSignInFailureResp(id RespID) []byte {
return []byte{uint8(id)}
}
func TestRespIDConstants(t *testing.T) {
tests := []struct {
respID RespID
value uint8
}{
{SIGN_UNKNOWN, 0},
{SIGN_SUCCESS, 1},
{SIGN_EFAILED, 2},
{SIGN_EILLEGAL, 3},
{SIGN_EALERT, 4},
{SIGN_EABORT, 5},
{SIGN_ERESPONSE, 6},
{SIGN_EDATABASE, 7},
{SIGN_EABSENCE, 8},
{SIGN_ERESIGN, 9},
{SIGN_ESUSPEND_D, 10},
{SIGN_ELOCK, 11},
{SIGN_EPASS, 12},
{SIGN_ERIGHT, 13},
{SIGN_EAUTH, 14},
{SIGN_ESUSPEND, 15},
{SIGN_EELIMINATE, 16},
{SIGN_ECLOSE, 17},
{SIGN_ECLOSE_EX, 18},
{SIGN_EINTERVAL, 19},
{SIGN_EMOVED, 20},
{SIGN_ENOTREADY, 21},
{SIGN_EALREADY, 22},
{SIGN_EIPADDR, 23},
{SIGN_EHANGAME, 24},
{SIGN_UPD_ONLY, 25},
{SIGN_EMBID, 26},
{SIGN_ECOGCODE, 27},
{SIGN_ETOKEN, 28},
{SIGN_ECOGLINK, 29},
{SIGN_EMAINTE, 30},
{SIGN_EMAINTE_NOUPDATE, 31},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("RespID_%d", tt.value), func(t *testing.T) {
if uint8(tt.respID) != tt.value {
t.Errorf("RespID = %d, want %d", uint8(tt.respID), tt.value)
}
})
}
}
func TestRespIDType(t *testing.T) {
var r RespID = 0xFF
if uint8(r) != 0xFF {
t.Errorf("RespID max value = %d, want %d", uint8(r), 0xFF)
}
}
func TestMakeSignInFailureResp(t *testing.T) {
tests := []RespID{
SIGN_UNKNOWN,
SIGN_EFAILED,
SIGN_EILLEGAL,
SIGN_ESUSPEND,
SIGN_EELIMINATE,
SIGN_EIPADDR,
}
for _, respID := range tests {
t.Run(fmt.Sprintf("RespID_%d", respID), func(t *testing.T) {
resp := makeSignInFailureResp(respID)
if len(resp) != 1 {
t.Errorf("makeSignInFailureResp() len = %d, want 1", len(resp))
}
if resp[0] != uint8(respID) {
t.Errorf("makeSignInFailureResp() = %d, want %d", resp[0], uint8(respID))
}
})
}
}
func TestMakeSignInFailureRespAllCodes(t *testing.T) {
for i := uint8(0); i <= 40; i++ {
resp := makeSignInFailureResp(RespID(i))
if len(resp) != 1 {
t.Errorf("makeSignInFailureResp(%d) len = %d, want 1", i, len(resp))
}
if resp[0] != i {
t.Errorf("makeSignInFailureResp(%d) = %d", i, resp[0])
}
}
}
func TestSignSuccessIsOne(t *testing.T) {
if SIGN_SUCCESS != 1 {
t.Errorf("SIGN_SUCCESS = %d, must be 1", SIGN_SUCCESS)
}
}
func TestSignUnknownIsZero(t *testing.T) {
if SIGN_UNKNOWN != 0 {
t.Errorf("SIGN_UNKNOWN = %d, must be 0", SIGN_UNKNOWN)
}
}
func TestRespIDValues(t *testing.T) {
tests := []struct {
name string
respID RespID
value uint8
}{
{"SIGN_UNKNOWN", SIGN_UNKNOWN, 0},
{"SIGN_SUCCESS", SIGN_SUCCESS, 1},
{"SIGN_EFAILED", SIGN_EFAILED, 2},
{"SIGN_EILLEGAL", SIGN_EILLEGAL, 3},
{"SIGN_ESUSPEND", SIGN_ESUSPEND, 15},
{"SIGN_EELIMINATE", SIGN_EELIMINATE, 16},
{"SIGN_EIPADDR", SIGN_EIPADDR, 23},
{"SIGN_EMAINTE", SIGN_EMAINTE, 30},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if uint8(tt.respID) != tt.value {
t.Errorf("%s = %d, want %d", tt.name, uint8(tt.respID), tt.value)
}
})
}
}
func TestUnknownRespIDRange(t *testing.T) {
unknownIDs := []RespID{UNK_32, UNK_33, UNK_34, UNK_35}
expectedValues := []uint8{32, 33, 34, 35}
for i, id := range unknownIDs {
if uint8(id) != expectedValues[i] {
t.Errorf("Unknown ID %d = %d, want %d", i, uint8(id), expectedValues[i])
}
}
}
func TestSpecialRespIDs(t *testing.T) {
if SIGN_XBRESPONSE != 36 {
t.Errorf("SIGN_XBRESPONSE = %d, want 36", SIGN_XBRESPONSE)
}
if SIGN_EPSI != 37 {
t.Errorf("SIGN_EPSI = %d, want 37", SIGN_EPSI)
}
if SIGN_EMBID_PSI != 38 {
t.Errorf("SIGN_EMBID_PSI = %d, want 38", SIGN_EMBID_PSI)
}
}
func TestMakeSignInFailureRespBoundary(t *testing.T) {
resp := makeSignInFailureResp(RespID(0))
if resp[0] != 0 {
t.Errorf("makeSignInFailureResp(0) = %d, want 0", resp[0])
}
resp = makeSignInFailureResp(RespID(255))
if resp[0] != 255 {
t.Errorf("makeSignInFailureResp(255) = %d, want 255", resp[0])
}
}
func TestErrorRespIDsAreDifferent(t *testing.T) {
seen := make(map[RespID]bool)
errorCodes := []RespID{
SIGN_UNKNOWN, SIGN_SUCCESS, SIGN_EFAILED, SIGN_EILLEGAL,
SIGN_EALERT, SIGN_EABORT, SIGN_ERESPONSE, SIGN_EDATABASE,
SIGN_EABSENCE, SIGN_ERESIGN, SIGN_ESUSPEND_D, SIGN_ELOCK,
SIGN_EPASS, SIGN_ERIGHT, SIGN_EAUTH, SIGN_ESUSPEND,
SIGN_EELIMINATE, SIGN_ECLOSE, SIGN_ECLOSE_EX, SIGN_EINTERVAL,
SIGN_EMOVED, SIGN_ENOTREADY, SIGN_EALREADY, SIGN_EIPADDR,
SIGN_EHANGAME, SIGN_UPD_ONLY, SIGN_EMBID, SIGN_ECOGCODE,
SIGN_ETOKEN, SIGN_ECOGLINK, SIGN_EMAINTE, SIGN_EMAINTE_NOUPDATE,
}
for _, code := range errorCodes {
if seen[code] {
t.Errorf("Duplicate RespID value: %d", code)
}
seen[code] = true
}
}
func TestFailureRespIsMinimal(t *testing.T) {
for i := RespID(0); i <= SIGN_EMBID_PSI; i++ {
if i == SIGN_SUCCESS {
continue
}
resp := makeSignInFailureResp(i)
if len(resp) != 1 {
t.Errorf("makeSignInFailureResp(%d) should be 1 byte, got %d", i, len(resp))
}
}
}
func TestNewServer(t *testing.T) {
cfg := &Config{
Logger: nil,
DB: nil,
ErupeConfig: nil,
}
s := NewServer(cfg)
if s == nil {
t.Fatal("NewServer() returned nil")
}
if s.isShuttingDown {
t.Error("New server should not be shutting down")
}
}
func TestNewServerWithNilConfig(t *testing.T) {
cfg := &Config{}
s := NewServer(cfg)
if s == nil {
t.Fatal("NewServer() returned nil for empty config")
}
}
func TestServerType(t *testing.T) {
s := &Server{}
if s.isShuttingDown {
t.Error("Zero value server should not be shutting down")
}
}
func TestConfigFields(t *testing.T) {
cfg := &Config{
Logger: nil,
DB: nil,
ErupeConfig: nil,
}
if cfg.Logger != nil {
t.Error("Config Logger should be nil")
}
if cfg.DB != nil {
t.Error("Config DB should be nil")
}
if cfg.ErupeConfig != nil {
t.Error("Config ErupeConfig should be nil")
}
}
func TestServerStartAndShutdown(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
if s == nil {
t.Fatal("NewServer() returned nil")
}
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
if s.listener == nil {
t.Error("Server listener should not be nil after Start()")
}
s.Lock()
if s.isShuttingDown {
t.Error("Server should not be shutting down after Start()")
}
s.Unlock()
s.Shutdown()
s.Lock()
if !s.isShuttingDown {
t.Error("Server should be shutting down after Shutdown()")
}
s.Unlock()
}
func TestServerStartWithInvalidPort(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: -1,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err == nil {
s.Shutdown()
t.Error("Start() should fail with invalid port")
}
}
func TestServerMutex(t *testing.T) {
s := &Server{}
s.Lock()
s.Unlock()
done := make(chan bool)
go func() {
s.Lock()
time.Sleep(10 * time.Millisecond)
s.Unlock()
done <- true
}()
time.Sleep(5 * time.Millisecond)
s.Lock()
s.Unlock()
<-done
}
func TestServerShutdownIdempotent(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
s.Shutdown()
s.Lock()
if !s.isShuttingDown {
t.Error("Server should be shutting down")
}
s.Unlock()
}
func TestServerAcceptClientsExitsOnShutdown(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
time.Sleep(10 * time.Millisecond)
s.Shutdown()
time.Sleep(10 * time.Millisecond)
s.Lock()
if !s.isShuttingDown {
t.Error("Server should be marked as shutting down")
}
s.Unlock()
}
func TestServerHandleConnection(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() error: %v", err)
}
defer conn.Close()
nullInit := make([]byte, 8)
_, err = conn.Write(nullInit)
if err != nil {
t.Fatalf("Write() error: %v", err)
}
time.Sleep(50 * time.Millisecond)
}
func TestServerHandleConnectionWithShortInit(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() error: %v", err)
}
_, _ = conn.Write([]byte{0, 0, 0, 0})
conn.Close()
time.Sleep(50 * time.Millisecond)
}
func TestServerHandleConnectionImmediateClose(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() error: %v", err)
}
conn.Close()
time.Sleep(50 * time.Millisecond)
}
func TestServerMultipleConnections(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr().String()
conns := make([]net.Conn, 3)
for i := range conns {
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("Dial() %d error: %v", i, err)
}
conns[i] = conn
nullInit := make([]byte, 8)
_, _ = conn.Write(nullInit)
}
time.Sleep(50 * time.Millisecond)
for _, conn := range conns {
conn.Close()
}
}
func TestServerListenerAddress(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &_config.Config{
Sign: _config.Sign{
Port: 0,
},
}
cfg := &Config{
Logger: logger,
ErupeConfig: erupeConfig,
}
s := NewServer(cfg)
err := s.Start()
if err != nil {
t.Fatalf("Start() error: %v", err)
}
defer s.Shutdown()
addr := s.listener.Addr()
if addr == nil {
t.Error("Listener address should not be nil")
}
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
t.Error("Listener address should be a TCP address")
}
if tcpAddr.Port == 0 {
t.Error("Listener port should be assigned")
}
}