Files
Erupe/network/mhfpacket/msg_parse_coverage_test.go
Houmgaor f17cb96b52 refactor(config): rename package _config to config with cfg alias
The config package used `package _config` with a leading underscore,
which is unconventional in Go. Rename to `package config` (matching the
directory name) and use `cfg` as the standard import alias across all
93 importing files.
2026-02-21 13:20:15 +01:00

390 lines
12 KiB
Go

package mhfpacket
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse"
cfg "erupe-ce/config"
"erupe-ce/network/clientctx"
)
// TestParseCoverage_Implemented exercises Parse() on all packet types whose Parse
// method is implemented (reads from ByteFrame) but was not yet covered by tests.
// Each test provides a ByteFrame with enough bytes for the Parse to succeed.
func TestParseCoverage_Implemented(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
tests := []struct {
name string
pkt MHFPacket
dataSize int // minimum bytes to satisfy Parse
}{
// 4-byte packets (AckHandle only)
{"MsgMhfGetSenyuDailyCount", &MsgMhfGetSenyuDailyCount{}, 4},
{"MsgMhfUnreserveSrg", &MsgMhfUnreserveSrg{}, 4},
// 1-byte packets
// MsgSysLogout reads uint8
{"MsgSysLogout", &MsgSysLogout{}, 1},
// 6-byte packets
{"MsgMhfGetRandFromTable", &MsgMhfGetRandFromTable{}, 6},
// 8-byte packets
{"MsgMhfPostBoostTimeLimit", &MsgMhfPostBoostTimeLimit{}, 8},
// 9-byte packets
{"MsgMhfPlayFreeGacha", &MsgMhfPlayFreeGacha{}, 9},
// 12-byte packets
{"MsgMhfEnumerateItem", &MsgMhfEnumerateItem{}, 12},
{"MsgMhfGetBreakSeibatuLevelReward", &MsgMhfGetBreakSeibatuLevelReward{}, 12},
{"MsgMhfReadLastWeekBeatRanking", &MsgMhfReadLastWeekBeatRanking{}, 12},
// 16-byte packets (4+1+1+4+1+2+2+1)
{"MsgMhfPostSeibattle", &MsgMhfPostSeibattle{}, 16},
// 16-byte packets
{"MsgMhfGetNotice", &MsgMhfGetNotice{}, 16},
{"MsgMhfCaravanRanking", &MsgMhfCaravanRanking{}, 16},
{"MsgMhfReadBeatLevelAllRanking", &MsgMhfReadBeatLevelAllRanking{}, 16},
{"MsgMhfCaravanMyRank", &MsgMhfCaravanMyRank{}, 16},
// 20-byte packets
{"MsgMhfPostNotice", &MsgMhfPostNotice{}, 20},
// 24-byte packets
{"MsgMhfGetFixedSeibatuRankingTable", &MsgMhfGetFixedSeibatuRankingTable{}, 24},
// 32-byte packets
{"MsgMhfCaravanMyScore", &MsgMhfCaravanMyScore{}, 32},
{"MsgMhfPostGemInfo", &MsgMhfPostGemInfo{}, 32},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrameFromBytes(make([]byte, tt.dataSize))
err := tt.pkt.Parse(bf, ctx)
if err != nil {
t.Errorf("Parse() returned error: %v", err)
}
})
}
}
// TestParseCoverage_VariableLength tests Parse for variable-length packets
// that require specific data layouts.
func TestParseCoverage_VariableLength(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgMhfAcquireItem_EmptyList", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // Unk0
bf.WriteUint16(0) // Length = 0 items
pkt := &MsgMhfAcquireItem{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfAcquireItem_WithItems", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // Unk0
bf.WriteUint16(2) // Length = 2 items
bf.WriteUint32(100) // item 1
bf.WriteUint32(200) // item 2
pkt := &MsgMhfAcquireItem{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
if len(pkt.Unk1) != 2 {
t.Errorf("expected 2 items, got %d", len(pkt.Unk1))
}
})
t.Run("MsgMhfReadBeatLevelMyRanking", func(t *testing.T) {
// 4 + 4 + 4 + 16*4 = 76 bytes
bf := byteframe.NewByteFrameFromBytes(make([]byte, 76))
pkt := &MsgMhfReadBeatLevelMyRanking{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfUpdateBeatLevel", func(t *testing.T) {
// 4 + 4 + 4 + 16*4 + 16*4 = 140 bytes
bf := byteframe.NewByteFrameFromBytes(make([]byte, 140))
pkt := &MsgMhfUpdateBeatLevel{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgSysRightsReload", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(3) // length
bf.WriteBytes([]byte{0x01, 0x02, 0x03}) // Unk0
pkt := &MsgSysRightsReload{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfCreateGuild", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // zeroed
bf.WriteUint16(4) // name length
bf.WriteBytes([]byte("Test\x00")) // null-terminated name
pkt := &MsgMhfCreateGuild{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfEnumerateGuild", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // Type
bf.WriteUint8(0) // Page
bf.WriteBool(false) // Sorting
bf.WriteUint8(0) // zero
bf.WriteBytes(make([]byte, 4)) // Data1
bf.WriteUint16(0) // zero
bf.WriteUint8(0) // dataLen = 0
bf.WriteUint8(0) // zero
pkt := &MsgMhfEnumerateGuild{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgSysCreateSemaphore", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // Unk0
bf.WriteUint8(5) // semaphore ID length
bf.WriteNullTerminatedBytes([]byte("test"))
pkt := &MsgSysCreateSemaphore{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfUpdateGuildMessageBoard_Op0", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // MessageOp = 0
bf.WriteUint32(0) // PostType
bf.WriteUint32(0) // StampID
bf.WriteUint32(0) // TitleLength = 0
bf.WriteUint32(0) // BodyLength = 0
pkt := &MsgMhfUpdateGuildMessageBoard{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfUpdateGuildMessageBoard_Op1", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(1) // MessageOp = 1
bf.WriteUint32(42) // PostID
pkt := &MsgMhfUpdateGuildMessageBoard{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfUpdateGuildMessageBoard_Op3", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(3) // MessageOp = 3
bf.WriteUint32(42) // PostID
bf.WriteBytes(make([]byte, 8)) // skip
bf.WriteUint32(0) // StampID
pkt := &MsgMhfUpdateGuildMessageBoard{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgMhfUpdateGuildMessageBoard_Op4", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(4) // MessageOp = 4
bf.WriteUint32(42) // PostID
bf.WriteBytes(make([]byte, 8)) // skip
bf.WriteBool(true) // LikeState
pkt := &MsgMhfUpdateGuildMessageBoard{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
}
// TestBuildCoverage_Implemented tests Build() on packet types whose Build method
// is implemented (writes to ByteFrame) but was not yet covered.
func TestBuildCoverage_Implemented(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgSysDeleteUser", func(t *testing.T) {
pkt := &MsgSysDeleteUser{CharID: 123}
bf := byteframe.NewByteFrame()
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
if len(bf.Data()) == 0 {
t.Error("Build() produced no data")
}
})
t.Run("MsgSysInsertUser", func(t *testing.T) {
pkt := &MsgSysInsertUser{CharID: 456}
bf := byteframe.NewByteFrame()
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
if len(bf.Data()) == 0 {
t.Error("Build() produced no data")
}
})
t.Run("MsgSysUpdateRight", func(t *testing.T) {
pkt := &MsgSysUpdateRight{
ClientRespAckHandle: 1,
Bitfield: 0xFF,
}
bf := byteframe.NewByteFrame()
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
if len(bf.Data()) == 0 {
t.Error("Build() produced no data")
}
})
t.Run("MsgSysUpdateRight_WithRights", func(t *testing.T) {
pkt := &MsgSysUpdateRight{
ClientRespAckHandle: 1,
Bitfield: 0xFF,
Rights: []mhfcourse.Course{
{ID: 1},
{ID: 2},
},
}
bf := byteframe.NewByteFrame()
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
})
// MsgSysLogout Build has a bug (calls ReadUint8 instead of WriteUint8)
// so we test it with defer/recover
t.Run("MsgSysLogout_Build", func(t *testing.T) {
defer func() {
_ = recover() // may panic due to bug
}()
pkt := &MsgSysLogout{LogoutType: 1}
bf := byteframe.NewByteFrame()
_ = pkt.Build(bf, ctx)
})
}
// TestParseCoverage_EmptyPackets tests Parse() for packets with no payload fields.
func TestParseCoverage_EmptyPackets(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgSysCleanupObject_Parse", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysCleanupObject{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgSysCleanupObject_Build", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysCleanupObject{}
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
})
t.Run("MsgSysUnreserveStage_Parse", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysUnreserveStage{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
})
t.Run("MsgSysUnreserveStage_Build", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysUnreserveStage{}
if err := pkt.Build(bf, ctx); err != nil {
t.Errorf("Build() error: %v", err)
}
})
}
// TestParseCoverage_NotImplemented2 tests Parse/Build for packets that return NOT IMPLEMENTED.
func TestParseCoverage_NotImplemented2(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgSysGetObjectOwner_Parse", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysGetObjectOwner{}
err := pkt.Parse(bf, ctx)
if err == nil {
t.Error("expected NOT IMPLEMENTED error")
}
})
t.Run("MsgSysUpdateRight_Parse", func(t *testing.T) {
bf := byteframe.NewByteFrame()
pkt := &MsgSysUpdateRight{}
err := pkt.Parse(bf, ctx)
if err == nil {
t.Error("expected NOT IMPLEMENTED error")
}
})
}
// TestParseCoverage_UpdateWarehouse tests MsgMhfUpdateWarehouse.Parse with different box types.
func TestParseCoverage_UpdateWarehouse(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("EmptyChanges", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // BoxType = 0 (items)
bf.WriteUint8(0) // BoxIndex
bf.WriteUint16(0) // changes = 0
bf.WriteUint8(0) // Zeroed
bf.WriteUint8(0) // Zeroed
pkt := &MsgMhfUpdateWarehouse{}
parsed := byteframe.NewByteFrameFromBytes(bf.Data())
if err := pkt.Parse(parsed, ctx); err != nil {
t.Errorf("Parse() error: %v", err)
}
if pkt.BoxType != 0 {
t.Errorf("BoxType = %d, want 0", pkt.BoxType)
}
})
}