mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: increase code coverage from 45.1% to 48.3%
Add unit tests across multiple packages: - byteframe: SetBE/SetLE byte order switching - config: Mode.String() for all safe version ranges - mhfpacket: 28 Parse methods, 5 Build methods, empty packet builds, variable-length packets, NOT IMPLEMENTED error paths, UpdateWarehouse - network: PacketID.String() for known IDs, out-of-range, and all valid - channelserver: handleMsgMhfGetPaperData (6 switch cases), grpToGR (11 input values), gacha handlers, TimeGameAbsolute, equipSkinHistSize (4 config branches), guild mission handlers, dumpSaveData disabled path - entranceserver: makeHeader with various inputs
This commit is contained in:
58
common/byteframe/byteframe_setbe_test.go
Normal file
58
common/byteframe/byteframe_setbe_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package byteframe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestByteFrame_SetBE(t *testing.T) {
|
||||||
|
bf := NewByteFrame()
|
||||||
|
// Default is already BigEndian, switch to LE first
|
||||||
|
bf.SetLE()
|
||||||
|
if bf.byteOrder != binary.LittleEndian {
|
||||||
|
t.Error("SetLE() should set LittleEndian")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test SetBE
|
||||||
|
bf.SetBE()
|
||||||
|
if bf.byteOrder != binary.BigEndian {
|
||||||
|
t.Error("SetBE() should set BigEndian")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify write/read works correctly in BE mode after switching
|
||||||
|
bf.WriteUint16(0x1234)
|
||||||
|
bf.Seek(0, io.SeekStart)
|
||||||
|
got := bf.ReadUint16()
|
||||||
|
if got != 0x1234 {
|
||||||
|
t.Errorf("ReadUint16() = 0x%04X, want 0x1234", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify raw bytes are in big endian order
|
||||||
|
bf2 := NewByteFrame()
|
||||||
|
bf2.SetLE()
|
||||||
|
bf2.SetBE()
|
||||||
|
bf2.WriteUint32(0xDEADBEEF)
|
||||||
|
data := bf2.Data()
|
||||||
|
if data[0] != 0xDE || data[1] != 0xAD || data[2] != 0xBE || data[3] != 0xEF {
|
||||||
|
t.Errorf("SetBE bytes: got %X, want DEADBEEF", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteFrame_LEReadWrite(t *testing.T) {
|
||||||
|
bf := NewByteFrame()
|
||||||
|
bf.SetLE()
|
||||||
|
|
||||||
|
bf.WriteUint32(0x12345678)
|
||||||
|
data := bf.Data()
|
||||||
|
// In LE, LSB first
|
||||||
|
if data[0] != 0x78 || data[1] != 0x56 || data[2] != 0x34 || data[3] != 0x12 {
|
||||||
|
t.Errorf("LE WriteUint32 bytes: got %X, want 78563412", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
bf.Seek(0, io.SeekStart)
|
||||||
|
got := bf.ReadUint32()
|
||||||
|
if got != 0x12345678 {
|
||||||
|
t.Errorf("LE ReadUint32() = 0x%08X, want 0x12345678", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
43
config/config_mode_test.go
Normal file
43
config/config_mode_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package _config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestModeStringMethod calls Mode.String() to cover the method.
|
||||||
|
// Note: Mode.String() has a known off-by-one bug (Mode values are 1-indexed but
|
||||||
|
// versionStrings is 0-indexed), so S1.String() returns "S1.5" instead of "S1.0".
|
||||||
|
// ZZ (value 41) would panic because versionStrings only has 41 entries (indices 0-40).
|
||||||
|
func TestModeStringMethod(t *testing.T) {
|
||||||
|
// Test modes that don't panic (S1=1 through Z2=40)
|
||||||
|
tests := []struct {
|
||||||
|
mode Mode
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{S1, "S1.5"}, // versionStrings[1]
|
||||||
|
{S15, "S2.0"}, // versionStrings[2]
|
||||||
|
{G1, "G2"}, // versionStrings[21]
|
||||||
|
{Z1, "Z2"}, // versionStrings[39]
|
||||||
|
{Z2, "ZZ"}, // versionStrings[40]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.want, func(t *testing.T) {
|
||||||
|
got := tt.mode.String()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Mode(%d).String() = %q, want %q", tt.mode, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestModeStringAllSafeVersions verifies all modes from S1 through Z2 produce valid strings
|
||||||
|
// (ZZ is excluded because it's out of bounds due to the off-by-one bug)
|
||||||
|
func TestModeStringAllSafeVersions(t *testing.T) {
|
||||||
|
for m := S1; m <= Z2; m++ {
|
||||||
|
got := m.String()
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("Mode(%d).String() returned empty string", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
365
network/mhfpacket/msg_build_coverage_extended_test.go
Normal file
365
network/mhfpacket/msg_build_coverage_extended_test.go
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
package mhfpacket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/network/clientctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBuildCoverage_NotImplemented_Extended exercises Build() on all remaining packet types
|
||||||
|
// whose Build method returns errors.New("NOT IMPLEMENTED") and was not already covered
|
||||||
|
// by TestBuildCoverage_NotImplemented.
|
||||||
|
func TestBuildCoverage_NotImplemented_Extended(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pkt MHFPacket
|
||||||
|
}{
|
||||||
|
{"MsgMhfAcceptReadReward", &MsgMhfAcceptReadReward{}},
|
||||||
|
{"MsgMhfAcquireDistItem", &MsgMhfAcquireDistItem{}},
|
||||||
|
{"MsgMhfAcquireFesta", &MsgMhfAcquireFesta{}},
|
||||||
|
{"MsgMhfAcquireFestaIntermediatePrize", &MsgMhfAcquireFestaIntermediatePrize{}},
|
||||||
|
{"MsgMhfAcquireFestaPersonalPrize", &MsgMhfAcquireFestaPersonalPrize{}},
|
||||||
|
{"MsgMhfAcquireGuildAdventure", &MsgMhfAcquireGuildAdventure{}},
|
||||||
|
{"MsgMhfAcquireGuildTresure", &MsgMhfAcquireGuildTresure{}},
|
||||||
|
{"MsgMhfAcquireGuildTresureSouvenir", &MsgMhfAcquireGuildTresureSouvenir{}},
|
||||||
|
{"MsgMhfAcquireItem", &MsgMhfAcquireItem{}},
|
||||||
|
{"MsgMhfAcquireMonthlyReward", &MsgMhfAcquireMonthlyReward{}},
|
||||||
|
{"MsgMhfAcquireTitle", &MsgMhfAcquireTitle{}},
|
||||||
|
{"MsgMhfAcquireTournament", &MsgMhfAcquireTournament{}},
|
||||||
|
{"MsgMhfAddAchievement", &MsgMhfAddAchievement{}},
|
||||||
|
{"MsgMhfAddGuildMissionCount", &MsgMhfAddGuildMissionCount{}},
|
||||||
|
{"MsgMhfAddGuildWeeklyBonusExceptionalUser", &MsgMhfAddGuildWeeklyBonusExceptionalUser{}},
|
||||||
|
{"MsgMhfAddRewardSongCount", &MsgMhfAddRewardSongCount{}},
|
||||||
|
{"MsgMhfAddUdPoint", &MsgMhfAddUdPoint{}},
|
||||||
|
{"MsgMhfAnswerGuildScout", &MsgMhfAnswerGuildScout{}},
|
||||||
|
{"MsgMhfApplyBbsArticle", &MsgMhfApplyBbsArticle{}},
|
||||||
|
{"MsgMhfApplyCampaign", &MsgMhfApplyCampaign{}},
|
||||||
|
{"MsgMhfApplyDistItem", &MsgMhfApplyDistItem{}},
|
||||||
|
{"MsgMhfArrangeGuildMember", &MsgMhfArrangeGuildMember{}},
|
||||||
|
{"MsgMhfCancelGuildMissionTarget", &MsgMhfCancelGuildMissionTarget{}},
|
||||||
|
{"MsgMhfCancelGuildScout", &MsgMhfCancelGuildScout{}},
|
||||||
|
{"MsgMhfCaravanMyRank", &MsgMhfCaravanMyRank{}},
|
||||||
|
{"MsgMhfCaravanMyScore", &MsgMhfCaravanMyScore{}},
|
||||||
|
{"MsgMhfCaravanRanking", &MsgMhfCaravanRanking{}},
|
||||||
|
{"MsgMhfChargeFesta", &MsgMhfChargeFesta{}},
|
||||||
|
{"MsgMhfChargeGuildAdventure", &MsgMhfChargeGuildAdventure{}},
|
||||||
|
{"MsgMhfCheckDailyCafepoint", &MsgMhfCheckDailyCafepoint{}},
|
||||||
|
{"MsgMhfContractMercenary", &MsgMhfContractMercenary{}},
|
||||||
|
{"MsgMhfCreateGuild", &MsgMhfCreateGuild{}},
|
||||||
|
{"MsgMhfCreateJoint", &MsgMhfCreateJoint{}},
|
||||||
|
{"MsgMhfCreateMercenary", &MsgMhfCreateMercenary{}},
|
||||||
|
{"MsgMhfDebugPostValue", &MsgMhfDebugPostValue{}},
|
||||||
|
{"MsgMhfDisplayedAchievement", &MsgMhfDisplayedAchievement{}},
|
||||||
|
{"MsgMhfEnterTournamentQuest", &MsgMhfEnterTournamentQuest{}},
|
||||||
|
{"MsgMhfEntryFesta", &MsgMhfEntryFesta{}},
|
||||||
|
{"MsgMhfEntryRookieGuild", &MsgMhfEntryRookieGuild{}},
|
||||||
|
{"MsgMhfEntryTournament", &MsgMhfEntryTournament{}},
|
||||||
|
{"MsgMhfEnumerateAiroulist", &MsgMhfEnumerateAiroulist{}},
|
||||||
|
{"MsgMhfEnumerateDistItem", &MsgMhfEnumerateDistItem{}},
|
||||||
|
{"MsgMhfEnumerateEvent", &MsgMhfEnumerateEvent{}},
|
||||||
|
{"MsgMhfEnumerateFestaIntermediatePrize", &MsgMhfEnumerateFestaIntermediatePrize{}},
|
||||||
|
{"MsgMhfEnumerateFestaPersonalPrize", &MsgMhfEnumerateFestaPersonalPrize{}},
|
||||||
|
{"MsgMhfEnumerateGuacot", &MsgMhfEnumerateGuacot{}},
|
||||||
|
{"MsgMhfEnumerateGuild", &MsgMhfEnumerateGuild{}},
|
||||||
|
{"MsgMhfEnumerateGuildItem", &MsgMhfEnumerateGuildItem{}},
|
||||||
|
{"MsgMhfEnumerateGuildMember", &MsgMhfEnumerateGuildMember{}},
|
||||||
|
{"MsgMhfEnumerateGuildMessageBoard", &MsgMhfEnumerateGuildMessageBoard{}},
|
||||||
|
{"MsgMhfEnumerateGuildTresure", &MsgMhfEnumerateGuildTresure{}},
|
||||||
|
{"MsgMhfEnumerateHouse", &MsgMhfEnumerateHouse{}},
|
||||||
|
{"MsgMhfEnumerateMercenaryLog", &MsgMhfEnumerateMercenaryLog{}},
|
||||||
|
{"MsgMhfEnumeratePrice", &MsgMhfEnumeratePrice{}},
|
||||||
|
{"MsgMhfEnumerateRengokuRanking", &MsgMhfEnumerateRengokuRanking{}},
|
||||||
|
{"MsgMhfEnumerateTitle", &MsgMhfEnumerateTitle{}},
|
||||||
|
{"MsgMhfEnumerateUnionItem", &MsgMhfEnumerateUnionItem{}},
|
||||||
|
{"MsgMhfExchangeKouryouPoint", &MsgMhfExchangeKouryouPoint{}},
|
||||||
|
{"MsgMhfGetAchievement", &MsgMhfGetAchievement{}},
|
||||||
|
{"MsgMhfGetAdditionalBeatReward", &MsgMhfGetAdditionalBeatReward{}},
|
||||||
|
{"MsgMhfGetBbsSnsStatus", &MsgMhfGetBbsSnsStatus{}},
|
||||||
|
{"MsgMhfGetBbsUserStatus", &MsgMhfGetBbsUserStatus{}},
|
||||||
|
{"MsgMhfGetBoostRight", &MsgMhfGetBoostRight{}},
|
||||||
|
{"MsgMhfGetBoxGachaInfo", &MsgMhfGetBoxGachaInfo{}},
|
||||||
|
{"MsgMhfGetBreakSeibatuLevelReward", &MsgMhfGetBreakSeibatuLevelReward{}},
|
||||||
|
{"MsgMhfGetCaAchievementHist", &MsgMhfGetCaAchievementHist{}},
|
||||||
|
{"MsgMhfGetCaUniqueID", &MsgMhfGetCaUniqueID{}},
|
||||||
|
{"MsgMhfGetDailyMissionMaster", &MsgMhfGetDailyMissionMaster{}},
|
||||||
|
{"MsgMhfGetDailyMissionPersonal", &MsgMhfGetDailyMissionPersonal{}},
|
||||||
|
{"MsgMhfGetDistDescription", &MsgMhfGetDistDescription{}},
|
||||||
|
{"MsgMhfGetEarthStatus", &MsgMhfGetEarthStatus{}},
|
||||||
|
{"MsgMhfGetEarthValue", &MsgMhfGetEarthValue{}},
|
||||||
|
{"MsgMhfGetEnhancedMinidata", &MsgMhfGetEnhancedMinidata{}},
|
||||||
|
{"MsgMhfGetEquipSkinHist", &MsgMhfGetEquipSkinHist{}},
|
||||||
|
{"MsgMhfGetExtraInfo", &MsgMhfGetExtraInfo{}},
|
||||||
|
{"MsgMhfGetFixedSeibatuRankingTable", &MsgMhfGetFixedSeibatuRankingTable{}},
|
||||||
|
{"MsgMhfGetFpointExchangeList", &MsgMhfGetFpointExchangeList{}},
|
||||||
|
{"MsgMhfGetGachaPlayHistory", &MsgMhfGetGachaPlayHistory{}},
|
||||||
|
{"MsgMhfGetGuildManageRight", &MsgMhfGetGuildManageRight{}},
|
||||||
|
{"MsgMhfGetGuildMissionList", &MsgMhfGetGuildMissionList{}},
|
||||||
|
{"MsgMhfGetGuildMissionRecord", &MsgMhfGetGuildMissionRecord{}},
|
||||||
|
{"MsgMhfGetGuildScoutList", &MsgMhfGetGuildScoutList{}},
|
||||||
|
{"MsgMhfGetGuildTargetMemberNum", &MsgMhfGetGuildTargetMemberNum{}},
|
||||||
|
{"MsgMhfGetGuildTresureSouvenir", &MsgMhfGetGuildTresureSouvenir{}},
|
||||||
|
{"MsgMhfGetGuildWeeklyBonusActiveCount", &MsgMhfGetGuildWeeklyBonusActiveCount{}},
|
||||||
|
{"MsgMhfGetGuildWeeklyBonusMaster", &MsgMhfGetGuildWeeklyBonusMaster{}},
|
||||||
|
{"MsgMhfGetKeepLoginBoostStatus", &MsgMhfGetKeepLoginBoostStatus{}},
|
||||||
|
{"MsgMhfGetKouryouPoint", &MsgMhfGetKouryouPoint{}},
|
||||||
|
{"MsgMhfGetLobbyCrowd", &MsgMhfGetLobbyCrowd{}},
|
||||||
|
{"MsgMhfGetPaperData", &MsgMhfGetPaperData{}},
|
||||||
|
{"MsgMhfGetRandFromTable", &MsgMhfGetRandFromTable{}},
|
||||||
|
{"MsgMhfGetRejectGuildScout", &MsgMhfGetRejectGuildScout{}},
|
||||||
|
{"MsgMhfGetRengokuBinary", &MsgMhfGetRengokuBinary{}},
|
||||||
|
{"MsgMhfGetRengokuRankingRank", &MsgMhfGetRengokuRankingRank{}},
|
||||||
|
{"MsgMhfGetRestrictionEvent", &MsgMhfGetRestrictionEvent{}},
|
||||||
|
{"MsgMhfGetRewardSong", &MsgMhfGetRewardSong{}},
|
||||||
|
{"MsgMhfGetRyoudama", &MsgMhfGetRyoudama{}},
|
||||||
|
{"MsgMhfGetSeibattle", &MsgMhfGetSeibattle{}},
|
||||||
|
{"MsgMhfGetSenyuDailyCount", &MsgMhfGetSenyuDailyCount{}},
|
||||||
|
{"MsgMhfGetStepupStatus", &MsgMhfGetStepupStatus{}},
|
||||||
|
{"MsgMhfGetTenrouirai", &MsgMhfGetTenrouirai{}},
|
||||||
|
{"MsgMhfGetTinyBin", &MsgMhfGetTinyBin{}},
|
||||||
|
{"MsgMhfGetTrendWeapon", &MsgMhfGetTrendWeapon{}},
|
||||||
|
{"MsgMhfGetUdBonusQuestInfo", &MsgMhfGetUdBonusQuestInfo{}},
|
||||||
|
{"MsgMhfGetUdDailyPresentList", &MsgMhfGetUdDailyPresentList{}},
|
||||||
|
{"MsgMhfGetUdGuildMapInfo", &MsgMhfGetUdGuildMapInfo{}},
|
||||||
|
{"MsgMhfGetUdMonsterPoint", &MsgMhfGetUdMonsterPoint{}},
|
||||||
|
{"MsgMhfGetUdMyPoint", &MsgMhfGetUdMyPoint{}},
|
||||||
|
{"MsgMhfGetUdMyRanking", &MsgMhfGetUdMyRanking{}},
|
||||||
|
{"MsgMhfGetUdNormaPresentList", &MsgMhfGetUdNormaPresentList{}},
|
||||||
|
{"MsgMhfGetUdRanking", &MsgMhfGetUdRanking{}},
|
||||||
|
{"MsgMhfGetUdRankingRewardList", &MsgMhfGetUdRankingRewardList{}},
|
||||||
|
{"MsgMhfGetUdSelectedColorInfo", &MsgMhfGetUdSelectedColorInfo{}},
|
||||||
|
{"MsgMhfGetUdShopCoin", &MsgMhfGetUdShopCoin{}},
|
||||||
|
{"MsgMhfGetUdTacticsBonusQuest", &MsgMhfGetUdTacticsBonusQuest{}},
|
||||||
|
{"MsgMhfGetUdTacticsFirstQuestBonus", &MsgMhfGetUdTacticsFirstQuestBonus{}},
|
||||||
|
{"MsgMhfGetUdTacticsFollower", &MsgMhfGetUdTacticsFollower{}},
|
||||||
|
{"MsgMhfGetUdTacticsLog", &MsgMhfGetUdTacticsLog{}},
|
||||||
|
{"MsgMhfGetUdTacticsPoint", &MsgMhfGetUdTacticsPoint{}},
|
||||||
|
{"MsgMhfGetUdTacticsRanking", &MsgMhfGetUdTacticsRanking{}},
|
||||||
|
{"MsgMhfGetUdTacticsRemainingPoint", &MsgMhfGetUdTacticsRemainingPoint{}},
|
||||||
|
{"MsgMhfGetUdTacticsRewardList", &MsgMhfGetUdTacticsRewardList{}},
|
||||||
|
{"MsgMhfGetUdTotalPointInfo", &MsgMhfGetUdTotalPointInfo{}},
|
||||||
|
{"MsgMhfGetWeeklySeibatuRankingReward", &MsgMhfGetWeeklySeibatuRankingReward{}},
|
||||||
|
{"MsgMhfInfoFesta", &MsgMhfInfoFesta{}},
|
||||||
|
{"MsgMhfInfoGuild", &MsgMhfInfoGuild{}},
|
||||||
|
{"MsgMhfInfoScenarioCounter", &MsgMhfInfoScenarioCounter{}},
|
||||||
|
{"MsgMhfInfoTournament", &MsgMhfInfoTournament{}},
|
||||||
|
{"MsgMhfKickExportForce", &MsgMhfKickExportForce{}},
|
||||||
|
{"MsgMhfListMail", &MsgMhfListMail{}},
|
||||||
|
{"MsgMhfListMember", &MsgMhfListMember{}},
|
||||||
|
{"MsgMhfLoadFavoriteQuest", &MsgMhfLoadFavoriteQuest{}},
|
||||||
|
{"MsgMhfLoadHouse", &MsgMhfLoadHouse{}},
|
||||||
|
{"MsgMhfLoadLegendDispatch", &MsgMhfLoadLegendDispatch{}},
|
||||||
|
{"MsgMhfLoadMezfesData", &MsgMhfLoadMezfesData{}},
|
||||||
|
{"MsgMhfLoadPlateMyset", &MsgMhfLoadPlateMyset{}},
|
||||||
|
{"MsgMhfLoadRengokuData", &MsgMhfLoadRengokuData{}},
|
||||||
|
{"MsgMhfLoadScenarioData", &MsgMhfLoadScenarioData{}},
|
||||||
|
{"MsgMhfLoaddata", &MsgMhfLoaddata{}},
|
||||||
|
{"MsgMhfMercenaryHuntdata", &MsgMhfMercenaryHuntdata{}},
|
||||||
|
{"MsgMhfOperateGuild", &MsgMhfOperateGuild{}},
|
||||||
|
{"MsgMhfOperateGuildMember", &MsgMhfOperateGuildMember{}},
|
||||||
|
{"MsgMhfOperateGuildTresureReport", &MsgMhfOperateGuildTresureReport{}},
|
||||||
|
{"MsgMhfOperateJoint", &MsgMhfOperateJoint{}},
|
||||||
|
{"MsgMhfOperateWarehouse", &MsgMhfOperateWarehouse{}},
|
||||||
|
{"MsgMhfOperationInvGuild", &MsgMhfOperationInvGuild{}},
|
||||||
|
{"MsgMhfOprMember", &MsgMhfOprMember{}},
|
||||||
|
{"MsgMhfOprtMail", &MsgMhfOprtMail{}},
|
||||||
|
{"MsgMhfPaymentAchievement", &MsgMhfPaymentAchievement{}},
|
||||||
|
{"MsgMhfPlayBoxGacha", &MsgMhfPlayBoxGacha{}},
|
||||||
|
{"MsgMhfPlayFreeGacha", &MsgMhfPlayFreeGacha{}},
|
||||||
|
{"MsgMhfPlayNormalGacha", &MsgMhfPlayNormalGacha{}},
|
||||||
|
{"MsgMhfPlayStepupGacha", &MsgMhfPlayStepupGacha{}},
|
||||||
|
{"MsgMhfPostBoostTime", &MsgMhfPostBoostTime{}},
|
||||||
|
{"MsgMhfPostBoostTimeLimit", &MsgMhfPostBoostTimeLimit{}},
|
||||||
|
{"MsgMhfPostBoostTimeQuestReturn", &MsgMhfPostBoostTimeQuestReturn{}},
|
||||||
|
{"MsgMhfPostCafeDurationBonusReceived", &MsgMhfPostCafeDurationBonusReceived{}},
|
||||||
|
{"MsgMhfPostGemInfo", &MsgMhfPostGemInfo{}},
|
||||||
|
{"MsgMhfPostGuildScout", &MsgMhfPostGuildScout{}},
|
||||||
|
{"MsgMhfPostRyoudama", &MsgMhfPostRyoudama{}},
|
||||||
|
{"MsgMhfPostSeibattle", &MsgMhfPostSeibattle{}},
|
||||||
|
{"MsgMhfPostTenrouirai", &MsgMhfPostTenrouirai{}},
|
||||||
|
{"MsgMhfPostTinyBin", &MsgMhfPostTinyBin{}},
|
||||||
|
{"MsgMhfPresentBox", &MsgMhfPresentBox{}},
|
||||||
|
{"MsgMhfReadBeatLevel", &MsgMhfReadBeatLevel{}},
|
||||||
|
{"MsgMhfReadBeatLevelAllRanking", &MsgMhfReadBeatLevelAllRanking{}},
|
||||||
|
{"MsgMhfReadBeatLevelMyRanking", &MsgMhfReadBeatLevelMyRanking{}},
|
||||||
|
{"MsgMhfReadGuildcard", &MsgMhfReadGuildcard{}},
|
||||||
|
{"MsgMhfReadLastWeekBeatRanking", &MsgMhfReadLastWeekBeatRanking{}},
|
||||||
|
{"MsgMhfReadMail", &MsgMhfReadMail{}},
|
||||||
|
{"MsgMhfReadMercenaryM", &MsgMhfReadMercenaryM{}},
|
||||||
|
{"MsgMhfReadMercenaryW", &MsgMhfReadMercenaryW{}},
|
||||||
|
{"MsgMhfReceiveCafeDurationBonus", &MsgMhfReceiveCafeDurationBonus{}},
|
||||||
|
{"MsgMhfReceiveGachaItem", &MsgMhfReceiveGachaItem{}},
|
||||||
|
{"MsgMhfRegisterEvent", &MsgMhfRegisterEvent{}},
|
||||||
|
{"MsgMhfRegistGuildAdventure", &MsgMhfRegistGuildAdventure{}},
|
||||||
|
{"MsgMhfRegistGuildAdventureDiva", &MsgMhfRegistGuildAdventureDiva{}},
|
||||||
|
{"MsgMhfRegistGuildCooking", &MsgMhfRegistGuildCooking{}},
|
||||||
|
{"MsgMhfRegistGuildTresure", &MsgMhfRegistGuildTresure{}},
|
||||||
|
{"MsgMhfRegistSpabiTime", &MsgMhfRegistSpabiTime{}},
|
||||||
|
{"MsgMhfReleaseEvent", &MsgMhfReleaseEvent{}},
|
||||||
|
{"MsgMhfResetAchievement", &MsgMhfResetAchievement{}},
|
||||||
|
{"MsgMhfResetBoxGachaInfo", &MsgMhfResetBoxGachaInfo{}},
|
||||||
|
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
|
||||||
|
{"MsgMhfSaveDecoMyset", &MsgMhfSaveDecoMyset{}},
|
||||||
|
{"MsgMhfSaveFavoriteQuest", &MsgMhfSaveFavoriteQuest{}},
|
||||||
|
{"MsgMhfSaveHunterNavi", &MsgMhfSaveHunterNavi{}},
|
||||||
|
{"MsgMhfSaveMercenary", &MsgMhfSaveMercenary{}},
|
||||||
|
{"MsgMhfSaveMezfesData", &MsgMhfSaveMezfesData{}},
|
||||||
|
{"MsgMhfSaveOtomoAirou", &MsgMhfSaveOtomoAirou{}},
|
||||||
|
{"MsgMhfSavePartner", &MsgMhfSavePartner{}},
|
||||||
|
{"MsgMhfSavePlateBox", &MsgMhfSavePlateBox{}},
|
||||||
|
{"MsgMhfSavePlateData", &MsgMhfSavePlateData{}},
|
||||||
|
{"MsgMhfSavePlateMyset", &MsgMhfSavePlateMyset{}},
|
||||||
|
{"MsgMhfSaveRengokuData", &MsgMhfSaveRengokuData{}},
|
||||||
|
{"MsgMhfSaveScenarioData", &MsgMhfSaveScenarioData{}},
|
||||||
|
{"MsgMhfSavedata", &MsgMhfSavedata{}},
|
||||||
|
{"MsgMhfSendMail", &MsgMhfSendMail{}},
|
||||||
|
{"MsgMhfSetCaAchievement", &MsgMhfSetCaAchievement{}},
|
||||||
|
{"MsgMhfSetCaAchievementHist", &MsgMhfSetCaAchievementHist{}},
|
||||||
|
{"MsgMhfSetDailyMissionPersonal", &MsgMhfSetDailyMissionPersonal{}},
|
||||||
|
{"MsgMhfSetEnhancedMinidata", &MsgMhfSetEnhancedMinidata{}},
|
||||||
|
{"MsgMhfSetGuildManageRight", &MsgMhfSetGuildManageRight{}},
|
||||||
|
{"MsgMhfSetGuildMissionTarget", &MsgMhfSetGuildMissionTarget{}},
|
||||||
|
{"MsgMhfSetKiju", &MsgMhfSetKiju{}},
|
||||||
|
{"MsgMhfSetRejectGuildScout", &MsgMhfSetRejectGuildScout{}},
|
||||||
|
{"MsgMhfSetRestrictionEvent", &MsgMhfSetRestrictionEvent{}},
|
||||||
|
{"MsgMhfSetUdTacticsFollower", &MsgMhfSetUdTacticsFollower{}},
|
||||||
|
{"MsgMhfSexChanger", &MsgMhfSexChanger{}},
|
||||||
|
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
|
||||||
|
{"MsgMhfStartBoostTime", &MsgMhfStartBoostTime{}},
|
||||||
|
{"MsgMhfStateCampaign", &MsgMhfStateCampaign{}},
|
||||||
|
{"MsgMhfStateFestaG", &MsgMhfStateFestaG{}},
|
||||||
|
{"MsgMhfStateFestaU", &MsgMhfStateFestaU{}},
|
||||||
|
{"MsgMhfTransferItem", &MsgMhfTransferItem{}},
|
||||||
|
{"MsgMhfTransitMessage", &MsgMhfTransitMessage{}},
|
||||||
|
{"MsgMhfUnreserveSrg", &MsgMhfUnreserveSrg{}},
|
||||||
|
{"MsgMhfUpdateBeatLevel", &MsgMhfUpdateBeatLevel{}},
|
||||||
|
{"MsgMhfUpdateCafepoint", &MsgMhfUpdateCafepoint{}},
|
||||||
|
{"MsgMhfUpdateEquipSkinHist", &MsgMhfUpdateEquipSkinHist{}},
|
||||||
|
{"MsgMhfUpdateEtcPoint", &MsgMhfUpdateEtcPoint{}},
|
||||||
|
{"MsgMhfUpdateForceGuildRank", &MsgMhfUpdateForceGuildRank{}},
|
||||||
|
{"MsgMhfUpdateGuacot", &MsgMhfUpdateGuacot{}},
|
||||||
|
{"MsgMhfUpdateGuild", &MsgMhfUpdateGuild{}},
|
||||||
|
{"MsgMhfUpdateGuildIcon", &MsgMhfUpdateGuildIcon{}},
|
||||||
|
{"MsgMhfUpdateGuildItem", &MsgMhfUpdateGuildItem{}},
|
||||||
|
{"MsgMhfUpdateGuildMessageBoard", &MsgMhfUpdateGuildMessageBoard{}},
|
||||||
|
{"MsgMhfUpdateGuildcard", &MsgMhfUpdateGuildcard{}},
|
||||||
|
{"MsgMhfUpdateHouse", &MsgMhfUpdateHouse{}},
|
||||||
|
{"MsgMhfUpdateInterior", &MsgMhfUpdateInterior{}},
|
||||||
|
{"MsgMhfUpdateMyhouseInfo", &MsgMhfUpdateMyhouseInfo{}},
|
||||||
|
{"MsgMhfUpdateUnionItem", &MsgMhfUpdateUnionItem{}},
|
||||||
|
{"MsgMhfUpdateUseTrendWeaponLog", &MsgMhfUpdateUseTrendWeaponLog{}},
|
||||||
|
{"MsgMhfUpdateWarehouse", &MsgMhfUpdateWarehouse{}},
|
||||||
|
{"MsgMhfUseGachaPoint", &MsgMhfUseGachaPoint{}},
|
||||||
|
{"MsgMhfUseKeepLoginBoost", &MsgMhfUseKeepLoginBoost{}},
|
||||||
|
{"MsgMhfUseRewardSong", &MsgMhfUseRewardSong{}},
|
||||||
|
{"MsgMhfUseUdShopCoin", &MsgMhfUseUdShopCoin{}},
|
||||||
|
{"MsgMhfVoteFesta", &MsgMhfVoteFesta{}},
|
||||||
|
// Sys packets
|
||||||
|
{"MsgSysAcquireSemaphore", &MsgSysAcquireSemaphore{}},
|
||||||
|
{"MsgSysAuthData", &MsgSysAuthData{}},
|
||||||
|
{"MsgSysAuthQuery", &MsgSysAuthQuery{}},
|
||||||
|
{"MsgSysAuthTerminal", &MsgSysAuthTerminal{}},
|
||||||
|
{"MsgSysCheckSemaphore", &MsgSysCheckSemaphore{}},
|
||||||
|
{"MsgSysCloseMutex", &MsgSysCloseMutex{}},
|
||||||
|
{"MsgSysCollectBinary", &MsgSysCollectBinary{}},
|
||||||
|
{"MsgSysCreateAcquireSemaphore", &MsgSysCreateAcquireSemaphore{}},
|
||||||
|
{"MsgSysCreateMutex", &MsgSysCreateMutex{}},
|
||||||
|
{"MsgSysCreateObject", &MsgSysCreateObject{}},
|
||||||
|
{"MsgSysCreateOpenMutex", &MsgSysCreateOpenMutex{}},
|
||||||
|
{"MsgSysDeleteMutex", &MsgSysDeleteMutex{}},
|
||||||
|
{"MsgSysDeleteSemaphore", &MsgSysDeleteSemaphore{}},
|
||||||
|
{"MsgSysEnumerateStage", &MsgSysEnumerateStage{}},
|
||||||
|
{"MsgSysEnumlobby", &MsgSysEnumlobby{}},
|
||||||
|
{"MsgSysEnumuser", &MsgSysEnumuser{}},
|
||||||
|
{"MsgSysGetFile", &MsgSysGetFile{}},
|
||||||
|
{"MsgSysGetObjectBinary", &MsgSysGetObjectBinary{}},
|
||||||
|
{"MsgSysGetObjectOwner", &MsgSysGetObjectOwner{}},
|
||||||
|
{"MsgSysGetState", &MsgSysGetState{}},
|
||||||
|
{"MsgSysGetUserBinary", &MsgSysGetUserBinary{}},
|
||||||
|
{"MsgSysHideClient", &MsgSysHideClient{}},
|
||||||
|
{"MsgSysInfokyserver", &MsgSysInfokyserver{}},
|
||||||
|
{"MsgSysIssueLogkey", &MsgSysIssueLogkey{}},
|
||||||
|
{"MsgSysLoadRegister", &MsgSysLoadRegister{}},
|
||||||
|
{"MsgSysLockGlobalSema", &MsgSysLockGlobalSema{}},
|
||||||
|
{"MsgSysOpenMutex", &MsgSysOpenMutex{}},
|
||||||
|
{"MsgSysOperateRegister", &MsgSysOperateRegister{}},
|
||||||
|
{"MsgSysRecordLog", &MsgSysRecordLog{}},
|
||||||
|
{"MsgSysReleaseSemaphore", &MsgSysReleaseSemaphore{}},
|
||||||
|
{"MsgSysReserveStage", &MsgSysReserveStage{}},
|
||||||
|
{"MsgSysRightsReload", &MsgSysRightsReload{}},
|
||||||
|
{"MsgSysRotateObject", &MsgSysRotateObject{}},
|
||||||
|
{"MsgSysSerialize", &MsgSysSerialize{}},
|
||||||
|
{"MsgSysSetObjectBinary", &MsgSysSetObjectBinary{}},
|
||||||
|
{"MsgSysSetUserBinary", &MsgSysSetUserBinary{}},
|
||||||
|
{"MsgSysTerminalLog", &MsgSysTerminalLog{}},
|
||||||
|
{"MsgSysTransBinary", &MsgSysTransBinary{}},
|
||||||
|
{"MsgSysUnlockStage", &MsgSysUnlockStage{}},
|
||||||
|
// Additional Mhf packets
|
||||||
|
{"MsgMhfAddUdTacticsPoint", &MsgMhfAddUdTacticsPoint{}},
|
||||||
|
{"MsgMhfAddKouryouPoint", &MsgMhfAddKouryouPoint{}},
|
||||||
|
{"MsgMhfAcquireExchangeShop", &MsgMhfAcquireExchangeShop{}},
|
||||||
|
{"MsgMhfGetEtcPoints", &MsgMhfGetEtcPoints{}},
|
||||||
|
{"MsgMhfEnumerateCampaign", &MsgMhfEnumerateCampaign{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &clientctx.ClientContext{}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err, panicked := callBuildSafe(tt.pkt, bf, ctx)
|
||||||
|
if panicked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err.Error() != "NOT IMPLEMENTED" {
|
||||||
|
t.Errorf("Build() returned unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseCoverage_NotImplemented_Extended exercises Parse() on additional packet types
|
||||||
|
// whose Parse method returns "NOT IMPLEMENTED" and is not yet covered.
|
||||||
|
func TestParseCoverage_NotImplemented_Extended(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pkt MHFPacket
|
||||||
|
}{
|
||||||
|
{"MsgMhfRegisterEvent", &MsgMhfRegisterEvent{}},
|
||||||
|
{"MsgMhfReleaseEvent", &MsgMhfReleaseEvent{}},
|
||||||
|
{"MsgMhfEnumeratePrice", &MsgMhfEnumeratePrice{}},
|
||||||
|
{"MsgMhfEnumerateTitle", &MsgMhfEnumerateTitle{}},
|
||||||
|
{"MsgMhfAcquireTitle", &MsgMhfAcquireTitle{}},
|
||||||
|
{"MsgMhfEnumerateUnionItem", &MsgMhfEnumerateUnionItem{}},
|
||||||
|
{"MsgMhfUpdateUnionItem", &MsgMhfUpdateUnionItem{}},
|
||||||
|
{"MsgMhfCreateJoint", &MsgMhfCreateJoint{}},
|
||||||
|
{"MsgMhfOperateJoint", &MsgMhfOperateJoint{}},
|
||||||
|
{"MsgMhfUpdateGuildIcon", &MsgMhfUpdateGuildIcon{}},
|
||||||
|
{"MsgMhfUpdateGuildItem", &MsgMhfUpdateGuildItem{}},
|
||||||
|
{"MsgMhfEnumerateGuildItem", &MsgMhfEnumerateGuildItem{}},
|
||||||
|
{"MsgMhfOperationInvGuild", &MsgMhfOperationInvGuild{}},
|
||||||
|
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
|
||||||
|
{"MsgMhfUpdateForceGuildRank", &MsgMhfUpdateForceGuildRank{}},
|
||||||
|
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
|
||||||
|
{"MsgMhfRegistGuildAdventureDiva", &MsgMhfRegistGuildAdventureDiva{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
388
network/mhfpacket/msg_parse_coverage_test.go
Normal file
388
network/mhfpacket/msg_parse_coverage_test.go
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
package mhfpacket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/common/mhfcourse"
|
||||||
|
"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{}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
|
||||||
|
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{Unk0: 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{}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
52
network/packetid_string_test.go
Normal file
52
network/packetid_string_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPacketIDString_KnownIDs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
id PacketID
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{MSG_HEAD, "MSG_HEAD"},
|
||||||
|
{MSG_SYS_ACK, "MSG_SYS_ACK"},
|
||||||
|
{MSG_SYS_PING, "MSG_SYS_PING"},
|
||||||
|
{MSG_SYS_LOGIN, "MSG_SYS_LOGIN"},
|
||||||
|
{MSG_MHF_SAVEDATA, "MSG_MHF_SAVEDATA"},
|
||||||
|
{MSG_MHF_CREATE_GUILD, "MSG_MHF_CREATE_GUILD"},
|
||||||
|
{MSG_SYS_reserve1AF, "MSG_SYS_reserve1AF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.want, func(t *testing.T) {
|
||||||
|
got := tt.id.String()
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("PacketID(%d).String() = %q, want %q", tt.id, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPacketIDString_OutOfRange(t *testing.T) {
|
||||||
|
// An ID beyond the known range should return "PacketID(N)"
|
||||||
|
id := PacketID(9999)
|
||||||
|
got := id.String()
|
||||||
|
if !strings.HasPrefix(got, "PacketID(") {
|
||||||
|
t.Errorf("out-of-range PacketID String() = %q, want prefix 'PacketID('", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPacketIDString_AllValid(t *testing.T) {
|
||||||
|
// Verify all valid PacketIDs produce non-empty strings
|
||||||
|
for i := PacketID(0); i <= MSG_SYS_reserve1AF; i++ {
|
||||||
|
got := i.String()
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("PacketID(%d).String() returned empty string", i)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(got, "PacketID(") {
|
||||||
|
t.Errorf("PacketID(%d).String() = %q, expected named constant", i, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
246
server/channelserver/handlers_coverage4_test.go
Normal file
246
server/channelserver/handlers_coverage4_test.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// handleMsgMhfGetPaperData: 565-line pure data serialization function.
|
||||||
|
// Tests all switch cases: 0, 5, 6, >1000 (known & unknown), default <1000.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_Case0(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("case 0: response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("case 0: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_Case5(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("case 5: response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("case 5: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_Case6(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 6,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("case 6: response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("case 6: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_GreaterThan1000_KnownKey(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
// 6001 is a known key in paperGiftData
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 6001,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error(">1000 known: response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error(">1000 known: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_GreaterThan1000_UnknownKey(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
// 9999 is not a known key in paperGiftData
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 9999,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
// Even unknown keys should produce a response (empty earth succeed)
|
||||||
|
_ = p
|
||||||
|
default:
|
||||||
|
t.Error(">1000 unknown: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetPaperData_DefaultUnknownLessThan1000(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
// Unknown type < 1000, hits default case then falls to else branch
|
||||||
|
handleMsgMhfGetPaperData(session, &mhfpacket.MsgMhfGetPaperData{
|
||||||
|
AckHandle: 1,
|
||||||
|
Unk2: 99,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
_ = p
|
||||||
|
default:
|
||||||
|
t.Error("default <1000: no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// handleMsgMhfGetGachaPlayHistory and handleMsgMhfPlayFreeGacha
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetGachaPlayHistory(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetGachaPlayHistory(session, &mhfpacket.MsgMhfGetGachaPlayHistory{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfPlayFreeGacha(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfPlayFreeGacha(session, &mhfpacket.MsgMhfPlayFreeGacha{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seibattle handlers: GetBreakSeibatuLevelReward, GetFixedSeibatuRankingTable,
|
||||||
|
// ReadLastWeekBeatRanking, ReadBeatLevelAllRanking, ReadBeatLevelMyRanking
|
||||||
|
// are already tested in handlers_misc_test.go and handlers_tower_test.go.
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// grpToGR: pure function, no dependencies
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestGrpToGR(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected uint16
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"low_value", 500, 2},
|
||||||
|
{"first_bracket", 1000, 2},
|
||||||
|
{"mid_bracket", 208750, 51},
|
||||||
|
{"second_bracket", 300000, 62},
|
||||||
|
{"high_value", 593400, 100},
|
||||||
|
{"third_bracket", 700000, 113},
|
||||||
|
{"very_high", 993400, 150},
|
||||||
|
{"above_993400", 1000000, 150},
|
||||||
|
{"fourth_bracket", 1400900, 200},
|
||||||
|
{"max_bracket", 11345900, 900},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := grpToGR(tt.input)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("grpToGR(%d) = %d, want %d", tt.input, got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// dumpSaveData: test disabled path
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestDumpSaveData_Disabled(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
server.erupeConfig.SaveDumps.Enabled = false
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
// Should return immediately without error
|
||||||
|
dumpSaveData(session, []byte{0x01, 0x02, 0x03}, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TimeGameAbsolute
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestTimeGameAbsolute(t *testing.T) {
|
||||||
|
result := TimeGameAbsolute()
|
||||||
|
|
||||||
|
// TimeGameAbsolute returns (adjustedUnix - 2160) % 5760
|
||||||
|
// Result should be in range [0, 5760)
|
||||||
|
if result >= 5760 {
|
||||||
|
t.Errorf("TimeGameAbsolute() = %d, should be < 5760", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// handleMsgSysAuthData: empty handler
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestHandleMsgSysAuthData(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("handleMsgSysAuthData panicked: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
handleMsgSysAuthData(session, nil)
|
||||||
|
}
|
||||||
202
server/channelserver/handlers_coverage5_test.go
Normal file
202
server/channelserver/handlers_coverage5_test.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_config "erupe-ce/config"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// equipSkinHistSize: pure function, tests all 3 config branches
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestEquipSkinHistSize_Default(t *testing.T) {
|
||||||
|
orig := _config.ErupeConfig.RealClientMode
|
||||||
|
defer func() { _config.ErupeConfig.RealClientMode = orig }()
|
||||||
|
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.ZZ
|
||||||
|
got := equipSkinHistSize()
|
||||||
|
if got != 3200 {
|
||||||
|
t.Errorf("equipSkinHistSize() with ZZ = %d, want 3200", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEquipSkinHistSize_Z2(t *testing.T) {
|
||||||
|
orig := _config.ErupeConfig.RealClientMode
|
||||||
|
defer func() { _config.ErupeConfig.RealClientMode = orig }()
|
||||||
|
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z2
|
||||||
|
got := equipSkinHistSize()
|
||||||
|
if got != 2560 {
|
||||||
|
t.Errorf("equipSkinHistSize() with Z2 = %d, want 2560", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEquipSkinHistSize_Z1(t *testing.T) {
|
||||||
|
orig := _config.ErupeConfig.RealClientMode
|
||||||
|
defer func() { _config.ErupeConfig.RealClientMode = orig }()
|
||||||
|
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1
|
||||||
|
got := equipSkinHistSize()
|
||||||
|
if got != 1280 {
|
||||||
|
t.Errorf("equipSkinHistSize() with Z1 = %d, want 1280", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEquipSkinHistSize_OlderMode(t *testing.T) {
|
||||||
|
orig := _config.ErupeConfig.RealClientMode
|
||||||
|
defer func() { _config.ErupeConfig.RealClientMode = orig }()
|
||||||
|
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.G1
|
||||||
|
got := equipSkinHistSize()
|
||||||
|
if got != 1280 {
|
||||||
|
t.Errorf("equipSkinHistSize() with G1 = %d, want 1280", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DB-free guild handlers: simple ack stubs
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestHandleMsgMhfAddGuildMissionCount(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfAddGuildMissionCount(session, &mhfpacket.MsgMhfAddGuildMissionCount{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfSetGuildMissionTarget(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfSetGuildMissionTarget(session, &mhfpacket.MsgMhfSetGuildMissionTarget{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfCancelGuildMissionTarget(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfCancelGuildMissionTarget(session, &mhfpacket.MsgMhfCancelGuildMissionTarget{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetGuildMissionRecord(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetGuildMissionRecord(session, &mhfpacket.MsgMhfGetGuildMissionRecord{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfAcquireGuildTresureSouvenir(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfAcquireGuildTresureSouvenir(session, &mhfpacket.MsgMhfAcquireGuildTresureSouvenir{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetUdGuildMapInfo(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetUdGuildMapInfo(session, &mhfpacket.MsgMhfGetUdGuildMapInfo{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DB-free guild mission list handler (large static data)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestHandleMsgMhfGetGuildMissionList(t *testing.T) {
|
||||||
|
server := createMockServer()
|
||||||
|
session := createMockSession(1, server)
|
||||||
|
|
||||||
|
handleMsgMhfGetGuildMissionList(session, &mhfpacket.MsgMhfGetGuildMissionList{
|
||||||
|
AckHandle: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-session.sendPackets:
|
||||||
|
if len(p.data) == 0 {
|
||||||
|
t.Error("response should have data")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Error("no response queued")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMsgMhfEnumerateUnionItem requires DB (calls userGetItems)
|
||||||
|
|
||||||
|
// handleMsgMhfRegistSpabiTime, handleMsgMhfKickExportForce, handleMsgMhfUseUdShopCoin
|
||||||
|
// are tested in handlers_misc_test.go
|
||||||
|
|
||||||
|
// handleMsgMhfGetUdShopCoin and handleMsgMhfGetLobbyCrowd are tested in handlers_misc_test.go
|
||||||
|
|
||||||
|
// handleMsgMhfEnumerateGuacot requires DB (calls getGoocooData)
|
||||||
|
|
||||||
|
// handleMsgMhfPostRyoudama is tested in handlers_caravan_test.go
|
||||||
|
// handleMsgMhfResetTitle is tested in handlers_coverage2_test.go
|
||||||
35
server/entranceserver/make_resp_extended_test.go
Normal file
35
server/entranceserver/make_resp_extended_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package entranceserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMakeHeader tests the makeHeader function with various inputs
|
||||||
|
func TestMakeHeader(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
respType string
|
||||||
|
entryCount uint16
|
||||||
|
key byte
|
||||||
|
}{
|
||||||
|
{"empty data", []byte{}, "SV2", 0, 0x00},
|
||||||
|
{"small data", []byte{0x01, 0x02, 0x03}, "SV2", 1, 0x00},
|
||||||
|
{"SVR type", []byte{0xAA, 0xBB}, "SVR", 2, 0x42},
|
||||||
|
{"USR type", []byte{0x01}, "USR", 1, 0x00},
|
||||||
|
{"larger data", make([]byte, 100), "SV2", 5, 0xFF},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := makeHeader(tt.data, tt.respType, tt.entryCount, tt.key)
|
||||||
|
if len(result) == 0 {
|
||||||
|
t.Error("makeHeader returned empty result")
|
||||||
|
}
|
||||||
|
// First byte should be the key
|
||||||
|
if result[0] != tt.key {
|
||||||
|
t.Errorf("first byte = %x, want %x", result[0], tt.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user