mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
test: increase total coverage from 40.7% to 46.1%
Add comprehensive mhfpacket Parse/Build/Opcode tests covering nearly all packet types (78.3% -> 95.7%). Add channelserver tests for BackportQuest and GuildIcon Scan/Value round-trips.
This commit is contained in:
1512
network/mhfpacket/msg_build_test.go
Normal file
1512
network/mhfpacket/msg_build_test.go
Normal file
File diff suppressed because it is too large
Load Diff
301
network/mhfpacket/msg_opcode_coverage_test.go
Normal file
301
network/mhfpacket/msg_opcode_coverage_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1259
network/mhfpacket/msg_parse_large_test.go
Normal file
1259
network/mhfpacket/msg_parse_large_test.go
Normal file
File diff suppressed because it is too large
Load Diff
810
network/mhfpacket/msg_parse_medium_test.go
Normal file
810
network/mhfpacket/msg_parse_medium_test.go
Normal file
@@ -0,0 +1,810 @@
|
||||
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), Unk(u32), GuildID(u32), TrialID(u32)
|
||||
func TestParseMediumVoteFesta(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ack uint32
|
||||
unk 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.unk)
|
||||
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.Unk != tt.unk {
|
||||
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
|
||||
}
|
||||
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.Unk0) != 0x16A {
|
||||
t.Errorf("Unk0 length = %d, want %d", len(pkt.Unk0), 0x16A)
|
||||
}
|
||||
if !bytes.Equal(pkt.Unk0, 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.Unk0) != 0x16A {
|
||||
t.Errorf("Unk0 length = %d, want %d", len(pkt.Unk0), 0x16A)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestParseMediumRightsReload verifies Parse for MsgSysRightsReload.
|
||||
// Fields: AckHandle(u32), Unk0(byte/u8)
|
||||
func TestParseMediumRightsReload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ack uint32
|
||||
unk0 byte
|
||||
}{
|
||||
{"typical", 0x55667788, 1},
|
||||
{"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 := &MsgSysRightsReload{}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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.AckHandle != ack {
|
||||
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, 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)
|
||||
}
|
||||
238
network/mhfpacket/msg_parse_small_test.go
Normal file
238
network/mhfpacket/msg_parse_small_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
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{}},
|
||||
{"MsgMhfCaravanMyRank", &MsgMhfCaravanMyRank{}},
|
||||
{"MsgMhfCaravanMyScore", &MsgMhfCaravanMyScore{}},
|
||||
{"MsgMhfCaravanRanking", &MsgMhfCaravanRanking{}},
|
||||
{"MsgMhfDebugPostValue", &MsgMhfDebugPostValue{}},
|
||||
{"MsgMhfEnterTournamentQuest", &MsgMhfEnterTournamentQuest{}},
|
||||
{"MsgMhfGetBreakSeibatuLevelReward", &MsgMhfGetBreakSeibatuLevelReward{}},
|
||||
{"MsgMhfGetCaAchievementHist", &MsgMhfGetCaAchievementHist{}},
|
||||
{"MsgMhfGetCaUniqueID", &MsgMhfGetCaUniqueID{}},
|
||||
{"MsgMhfGetDailyMissionMaster", &MsgMhfGetDailyMissionMaster{}},
|
||||
{"MsgMhfGetDailyMissionPersonal", &MsgMhfGetDailyMissionPersonal{}},
|
||||
{"MsgMhfGetExtraInfo", &MsgMhfGetExtraInfo{}},
|
||||
{"MsgMhfGetFixedSeibatuRankingTable", &MsgMhfGetFixedSeibatuRankingTable{}},
|
||||
{"MsgMhfGetRandFromTable", &MsgMhfGetRandFromTable{}},
|
||||
{"MsgMhfGetRestrictionEvent", &MsgMhfGetRestrictionEvent{}},
|
||||
{"MsgMhfGetSenyuDailyCount", &MsgMhfGetSenyuDailyCount{}},
|
||||
{"MsgMhfKickExportForce", &MsgMhfKickExportForce{}},
|
||||
{"MsgMhfPaymentAchievement", &MsgMhfPaymentAchievement{}},
|
||||
{"MsgMhfPlayFreeGacha", &MsgMhfPlayFreeGacha{}},
|
||||
{"MsgMhfPostBoostTimeLimit", &MsgMhfPostBoostTimeLimit{}},
|
||||
{"MsgMhfPostGemInfo", &MsgMhfPostGemInfo{}},
|
||||
{"MsgMhfPostRyoudama", &MsgMhfPostRyoudama{}},
|
||||
{"MsgMhfPostSeibattle", &MsgMhfPostSeibattle{}},
|
||||
{"MsgMhfReadBeatLevelAllRanking", &MsgMhfReadBeatLevelAllRanking{}},
|
||||
{"MsgMhfReadBeatLevelMyRanking", &MsgMhfReadBeatLevelMyRanking{}},
|
||||
{"MsgMhfReadLastWeekBeatRanking", &MsgMhfReadLastWeekBeatRanking{}},
|
||||
{"MsgMhfRegistSpabiTime", &MsgMhfRegistSpabiTime{}},
|
||||
{"MsgMhfResetAchievement", &MsgMhfResetAchievement{}},
|
||||
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
|
||||
{"MsgMhfSetCaAchievement", &MsgMhfSetCaAchievement{}},
|
||||
{"MsgMhfSetDailyMissionPersonal", &MsgMhfSetDailyMissionPersonal{}},
|
||||
{"MsgMhfSetUdTacticsFollower", &MsgMhfSetUdTacticsFollower{}},
|
||||
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
|
||||
{"MsgMhfUnreserveSrg", &MsgMhfUnreserveSrg{}},
|
||||
{"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{}},
|
||||
{"MsgSysGetObjectOwner", &MsgSysGetObjectOwner{}},
|
||||
{"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.Unk != 100 {
|
||||
t.Errorf("Unk = %d, want 100", pkt.Unk)
|
||||
}
|
||||
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.Unk != 200 {
|
||||
t.Errorf("Unk = %d, want 200", pkt.Unk)
|
||||
}
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
249
server/channelserver/handlers_guild_icon_test.go
Normal file
249
server/channelserver/handlers_guild_icon_test.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGuildIconScan_Bytes(t *testing.T) {
|
||||
jsonData := []byte(`{"Parts":[{"Index":1,"ID":100,"Page":2,"Size":3,"Rotation":4,"Red":255,"Green":128,"Blue":0,"PosX":50,"PosY":60}]}`)
|
||||
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan(jsonData)
|
||||
if err != nil {
|
||||
t.Fatalf("Scan([]byte) error = %v", err)
|
||||
}
|
||||
|
||||
if len(gi.Parts) != 1 {
|
||||
t.Fatalf("Parts length = %d, want 1", len(gi.Parts))
|
||||
}
|
||||
|
||||
part := gi.Parts[0]
|
||||
if part.Index != 1 {
|
||||
t.Errorf("Index = %d, want 1", part.Index)
|
||||
}
|
||||
if part.ID != 100 {
|
||||
t.Errorf("ID = %d, want 100", part.ID)
|
||||
}
|
||||
if part.Page != 2 {
|
||||
t.Errorf("Page = %d, want 2", part.Page)
|
||||
}
|
||||
if part.Size != 3 {
|
||||
t.Errorf("Size = %d, want 3", part.Size)
|
||||
}
|
||||
if part.Rotation != 4 {
|
||||
t.Errorf("Rotation = %d, want 4", part.Rotation)
|
||||
}
|
||||
if part.Red != 255 {
|
||||
t.Errorf("Red = %d, want 255", part.Red)
|
||||
}
|
||||
if part.Green != 128 {
|
||||
t.Errorf("Green = %d, want 128", part.Green)
|
||||
}
|
||||
if part.Blue != 0 {
|
||||
t.Errorf("Blue = %d, want 0", part.Blue)
|
||||
}
|
||||
if part.PosX != 50 {
|
||||
t.Errorf("PosX = %d, want 50", part.PosX)
|
||||
}
|
||||
if part.PosY != 60 {
|
||||
t.Errorf("PosY = %d, want 60", part.PosY)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_String(t *testing.T) {
|
||||
jsonStr := `{"Parts":[{"Index":5,"ID":200,"Page":1,"Size":2,"Rotation":0,"Red":100,"Green":50,"Blue":25,"PosX":300,"PosY":400}]}`
|
||||
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan(jsonStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Scan(string) error = %v", err)
|
||||
}
|
||||
|
||||
if len(gi.Parts) != 1 {
|
||||
t.Fatalf("Parts length = %d, want 1", len(gi.Parts))
|
||||
}
|
||||
if gi.Parts[0].ID != 200 {
|
||||
t.Errorf("ID = %d, want 200", gi.Parts[0].ID)
|
||||
}
|
||||
if gi.Parts[0].PosX != 300 {
|
||||
t.Errorf("PosX = %d, want 300", gi.Parts[0].PosX)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_MultipleParts(t *testing.T) {
|
||||
jsonData := []byte(`{"Parts":[{"Index":0,"ID":1,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0},{"Index":1,"ID":2,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0},{"Index":2,"ID":3,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0}]}`)
|
||||
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan(jsonData)
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() error = %v", err)
|
||||
}
|
||||
|
||||
if len(gi.Parts) != 3 {
|
||||
t.Fatalf("Parts length = %d, want 3", len(gi.Parts))
|
||||
}
|
||||
for i, part := range gi.Parts {
|
||||
if part.Index != uint16(i) {
|
||||
t.Errorf("Parts[%d].Index = %d, want %d", i, part.Index, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_EmptyParts(t *testing.T) {
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan([]byte(`{"Parts":[]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() error = %v", err)
|
||||
}
|
||||
if len(gi.Parts) != 0 {
|
||||
t.Errorf("Parts length = %d, want 0", len(gi.Parts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_InvalidJSON(t *testing.T) {
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan([]byte(`{invalid`))
|
||||
if err == nil {
|
||||
t.Error("Scan() with invalid JSON should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_InvalidJSONString(t *testing.T) {
|
||||
gi := &GuildIcon{}
|
||||
err := gi.Scan("{invalid")
|
||||
if err == nil {
|
||||
t.Error("Scan() with invalid JSON string should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScan_UnsupportedType(t *testing.T) {
|
||||
gi := &GuildIcon{}
|
||||
// Passing an unsupported type should not error (just no-op)
|
||||
err := gi.Scan(12345)
|
||||
if err != nil {
|
||||
t.Errorf("Scan(int) unexpected error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconValue(t *testing.T) {
|
||||
gi := &GuildIcon{
|
||||
Parts: []GuildIconPart{
|
||||
{Index: 1, ID: 100, Page: 2, Size: 3, Rotation: 4, Red: 255, Green: 128, Blue: 0, PosX: 50, PosY: 60},
|
||||
},
|
||||
}
|
||||
|
||||
val, err := gi.Value()
|
||||
if err != nil {
|
||||
t.Fatalf("Value() error = %v", err)
|
||||
}
|
||||
|
||||
jsonBytes, ok := val.([]byte)
|
||||
if !ok {
|
||||
t.Fatalf("Value() returned %T, want []byte", val)
|
||||
}
|
||||
|
||||
// Verify round-trip
|
||||
gi2 := &GuildIcon{}
|
||||
err = json.Unmarshal(jsonBytes, gi2)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Unmarshal error = %v", err)
|
||||
}
|
||||
|
||||
if len(gi2.Parts) != 1 {
|
||||
t.Fatalf("round-trip Parts length = %d, want 1", len(gi2.Parts))
|
||||
}
|
||||
if gi2.Parts[0].ID != 100 {
|
||||
t.Errorf("round-trip ID = %d, want 100", gi2.Parts[0].ID)
|
||||
}
|
||||
if gi2.Parts[0].Red != 255 {
|
||||
t.Errorf("round-trip Red = %d, want 255", gi2.Parts[0].Red)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconValue_Empty(t *testing.T) {
|
||||
gi := &GuildIcon{}
|
||||
val, err := gi.Value()
|
||||
if err != nil {
|
||||
t.Fatalf("Value() error = %v", err)
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
t.Error("Value() should not return nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildIconScanValueRoundTrip(t *testing.T) {
|
||||
original := &GuildIcon{
|
||||
Parts: []GuildIconPart{
|
||||
{Index: 0, ID: 10, Page: 1, Size: 2, Rotation: 45, Red: 200, Green: 150, Blue: 100, PosX: 500, PosY: 600},
|
||||
{Index: 1, ID: 20, Page: 3, Size: 4, Rotation: 90, Red: 50, Green: 75, Blue: 255, PosX: 100, PosY: 200},
|
||||
},
|
||||
}
|
||||
|
||||
// Value -> Scan round trip
|
||||
val, err := original.Value()
|
||||
if err != nil {
|
||||
t.Fatalf("Value() error = %v", err)
|
||||
}
|
||||
|
||||
restored := &GuildIcon{}
|
||||
err = restored.Scan(val)
|
||||
if err != nil {
|
||||
t.Fatalf("Scan() error = %v", err)
|
||||
}
|
||||
|
||||
if len(restored.Parts) != len(original.Parts) {
|
||||
t.Fatalf("Parts length = %d, want %d", len(restored.Parts), len(original.Parts))
|
||||
}
|
||||
|
||||
for i := range original.Parts {
|
||||
if restored.Parts[i] != original.Parts[i] {
|
||||
t.Errorf("Parts[%d] mismatch: got %+v, want %+v", i, restored.Parts[i], original.Parts[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFestivalColourCodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
colour FestivalColour
|
||||
code uint8
|
||||
}{
|
||||
{FestivalColourBlue, 0x00},
|
||||
{FestivalColourRed, 0x01},
|
||||
{FestivalColourNone, 0xFF},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.colour), func(t *testing.T) {
|
||||
code, ok := FestivalColourCodes[tt.colour]
|
||||
if !ok {
|
||||
t.Fatalf("FestivalColourCodes missing key %s", tt.colour)
|
||||
}
|
||||
if code != tt.code {
|
||||
t.Errorf("FestivalColourCodes[%s] = %d, want %d", tt.colour, code, tt.code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFestivalColourConstants(t *testing.T) {
|
||||
if FestivalColourNone != "none" {
|
||||
t.Errorf("FestivalColourNone = %s, want none", FestivalColourNone)
|
||||
}
|
||||
if FestivalColourRed != "red" {
|
||||
t.Errorf("FestivalColourRed = %s, want red", FestivalColourRed)
|
||||
}
|
||||
if FestivalColourBlue != "blue" {
|
||||
t.Errorf("FestivalColourBlue = %s, want blue", FestivalColourBlue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuildApplicationTypeConstants(t *testing.T) {
|
||||
if GuildApplicationTypeApplied != "applied" {
|
||||
t.Errorf("GuildApplicationTypeApplied = %s, want applied", GuildApplicationTypeApplied)
|
||||
}
|
||||
if GuildApplicationTypeInvited != "invited" {
|
||||
t.Errorf("GuildApplicationTypeInvited = %s, want invited", GuildApplicationTypeInvited)
|
||||
}
|
||||
}
|
||||
128
server/channelserver/handlers_quest_backport_test.go
Normal file
128
server/channelserver/handlers_quest_backport_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
_config "erupe-ce/config"
|
||||
)
|
||||
|
||||
func TestBackportQuest_Basic(t *testing.T) {
|
||||
// Set up config for the test
|
||||
oldConfig := _config.ErupeConfig
|
||||
defer func() { _config.ErupeConfig = oldConfig }()
|
||||
|
||||
_config.ErupeConfig = &_config.Config{}
|
||||
_config.ErupeConfig.RealClientMode = _config.ZZ
|
||||
|
||||
// Create a quest data buffer large enough for BackportQuest to work with.
|
||||
// The function reads a uint32 from data[0:4] as offset, then works at offset+96.
|
||||
// We need at least offset + 96 + 108 + 6*8 bytes.
|
||||
// Set offset (wp base) = 0, so wp starts at 96, rp at 100.
|
||||
data := make([]byte, 512)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0) // offset = 0
|
||||
|
||||
// Fill some data at the rp positions so we can verify copies
|
||||
for i := 100; i < 400; i++ {
|
||||
data[i] = byte(i & 0xFF)
|
||||
}
|
||||
|
||||
result := BackportQuest(data)
|
||||
if result == nil {
|
||||
t.Fatal("BackportQuest returned nil")
|
||||
}
|
||||
if len(result) != len(data) {
|
||||
t.Errorf("BackportQuest changed data length: got %d, want %d", len(result), len(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackportQuest_S6Mode(t *testing.T) {
|
||||
oldConfig := _config.ErupeConfig
|
||||
defer func() { _config.ErupeConfig = oldConfig }()
|
||||
|
||||
_config.ErupeConfig = &_config.Config{}
|
||||
_config.ErupeConfig.RealClientMode = _config.S6
|
||||
|
||||
data := make([]byte, 512)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0)
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
data[i+4] = byte(i % 256)
|
||||
if i+4 >= len(data)-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Set some values at data[8:12] so we can check they get copied to data[16:20]
|
||||
binary.LittleEndian.PutUint32(data[8:12], 0xDEADBEEF)
|
||||
|
||||
result := BackportQuest(data)
|
||||
if result == nil {
|
||||
t.Fatal("BackportQuest returned nil")
|
||||
}
|
||||
|
||||
// In S6 mode, data[16:20] should be copied from data[8:12]
|
||||
got := binary.LittleEndian.Uint32(result[16:20])
|
||||
if got != 0xDEADBEEF {
|
||||
t.Errorf("S6 mode: data[16:20] = 0x%X, want 0xDEADBEEF", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackportQuest_G91Mode_PatternReplacement(t *testing.T) {
|
||||
oldConfig := _config.ErupeConfig
|
||||
defer func() { _config.ErupeConfig = oldConfig }()
|
||||
|
||||
_config.ErupeConfig = &_config.Config{}
|
||||
_config.ErupeConfig.RealClientMode = _config.G91
|
||||
|
||||
data := make([]byte, 512)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0)
|
||||
|
||||
// Insert an armor sphere pattern at a known location
|
||||
// Pattern: 0x0A, 0x00, 0x01, 0x33 -> should replace bytes at +2 with 0xD7, 0x00
|
||||
offset := 300
|
||||
data[offset] = 0x0A
|
||||
data[offset+1] = 0x00
|
||||
data[offset+2] = 0x01
|
||||
data[offset+3] = 0x33
|
||||
|
||||
result := BackportQuest(data)
|
||||
|
||||
// After BackportQuest, the pattern's last 2 bytes should be replaced
|
||||
if result[offset+2] != 0xD7 || result[offset+3] != 0x00 {
|
||||
t.Errorf("G91 pattern replacement failed: got [0x%X, 0x%X], want [0xD7, 0x00]",
|
||||
result[offset+2], result[offset+3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackportQuest_F5Mode(t *testing.T) {
|
||||
oldConfig := _config.ErupeConfig
|
||||
defer func() { _config.ErupeConfig = oldConfig }()
|
||||
|
||||
_config.ErupeConfig = &_config.Config{}
|
||||
_config.ErupeConfig.RealClientMode = _config.F5
|
||||
|
||||
data := make([]byte, 512)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0)
|
||||
|
||||
result := BackportQuest(data)
|
||||
if result == nil {
|
||||
t.Fatal("BackportQuest returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackportQuest_G101Mode(t *testing.T) {
|
||||
oldConfig := _config.ErupeConfig
|
||||
defer func() { _config.ErupeConfig = oldConfig }()
|
||||
|
||||
_config.ErupeConfig = &_config.Config{}
|
||||
_config.ErupeConfig.RealClientMode = _config.G101
|
||||
|
||||
data := make([]byte, 512)
|
||||
binary.LittleEndian.PutUint32(data[0:4], 0)
|
||||
|
||||
result := BackportQuest(data)
|
||||
if result == nil {
|
||||
t.Fatal("BackportQuest returned nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user