Files
Erupe/server/channelserver/handlers_coverage3_test.go
Houmgaor ad4afb4d3b refactor(channelserver): replace global stagesLock with sync.Map-backed StageMap
The global stagesLock sync.RWMutex protected map[string]*Stage, causing
all stage operations to contend on a single lock even for unrelated
stages. Any stage creation or deletion blocked all reads server-wide.

Replace with a typed StageMap wrapper around sync.Map which provides
lock-free reads and allows concurrent writes to disjoint keys. Per-stage
sync.RWMutex remains unchanged for protecting individual stage state.

StageMap exposes Get, GetOrCreate, StoreIfAbsent, Store, Delete, and
Range methods. Updated ~50 call sites across 6 production files and
9 test files.
2026-02-22 15:47:21 +01:00

1136 lines
36 KiB
Go

package channelserver
import (
"sync"
"testing"
"erupe-ce/network/mhfpacket"
)
// =============================================================================
// Category 1: Empty handlers from handlers.go
// These have empty function bodies and can be called with nil packet safely.
// =============================================================================
func TestEmptyHandlers_HandlersGo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
{"handleMsgSysEcho", func() { handleMsgSysEcho(session, nil) }},
{"handleMsgSysUpdateRight", func() { handleMsgSysUpdateRight(session, nil) }},
{"handleMsgSysAuthQuery", func() { handleMsgSysAuthQuery(session, nil) }},
{"handleMsgSysAuthTerminal", func() { handleMsgSysAuthTerminal(session, nil) }},
{"handleMsgCaExchangeItem", func() { handleMsgCaExchangeItem(session, nil) }},
{"handleMsgMhfServerCommand", func() { handleMsgMhfServerCommand(session, nil) }},
{"handleMsgMhfSetLoginwindow", func() { handleMsgMhfSetLoginwindow(session, nil) }},
{"handleMsgSysTransBinary", func() { handleMsgSysTransBinary(session, nil) }},
{"handleMsgSysCollectBinary", func() { handleMsgSysCollectBinary(session, nil) }},
{"handleMsgSysGetState", func() { handleMsgSysGetState(session, nil) }},
{"handleMsgSysSerialize", func() { handleMsgSysSerialize(session, nil) }},
{"handleMsgSysEnumlobby", func() { handleMsgSysEnumlobby(session, nil) }},
{"handleMsgSysEnumuser", func() { handleMsgSysEnumuser(session, nil) }},
{"handleMsgSysInfokyserver", func() { handleMsgSysInfokyserver(session, nil) }},
{"handleMsgMhfGetCaUniqueID", func() { handleMsgMhfGetCaUniqueID(session, nil) }},
{"handleMsgMhfGetExtraInfo", func() { handleMsgMhfGetExtraInfo(session, nil) }},
{"handleMsgSysSetStatus", func() { handleMsgSysSetStatus(session, nil) }},
{"handleMsgMhfStampcardPrize", func() { handleMsgMhfStampcardPrize(session, nil) }},
{"handleMsgMhfKickExportForce", func() { handleMsgMhfKickExportForce(session, nil) }},
{"handleMsgMhfRegistSpabiTime", func() { handleMsgMhfRegistSpabiTime(session, nil) }},
{"handleMsgMhfDebugPostValue", func() { handleMsgMhfDebugPostValue(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 2: Empty handlers from handlers_object.go
// All empty function bodies, safe to call with nil packet.
// =============================================================================
func TestEmptyHandlers_ObjectGo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
{"handleMsgSysDeleteObject", func() { handleMsgSysDeleteObject(session, nil) }},
{"handleMsgSysRotateObject", func() { handleMsgSysRotateObject(session, nil) }},
{"handleMsgSysDuplicateObject", func() { handleMsgSysDuplicateObject(session, nil) }},
{"handleMsgSysGetObjectBinary", func() { handleMsgSysGetObjectBinary(session, nil) }},
{"handleMsgSysGetObjectOwner", func() { handleMsgSysGetObjectOwner(session, nil) }},
{"handleMsgSysUpdateObjectBinary", func() { handleMsgSysUpdateObjectBinary(session, nil) }},
{"handleMsgSysCleanupObject", func() { handleMsgSysCleanupObject(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 3: Empty handlers from handlers_clients.go
// All empty function bodies, safe to call with nil packet.
// =============================================================================
func TestEmptyHandlers_ClientsGo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
{"handleMsgMhfShutClient", func() { handleMsgMhfShutClient(session, nil) }},
{"handleMsgSysHideClient", func() { handleMsgSysHideClient(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 4: Empty handler from handlers_stage.go
// =============================================================================
func TestEmptyHandlers_StageGo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
{"handleMsgSysStageDestruct", func() { handleMsgSysStageDestruct(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 5: Empty handlers from handlers_achievement.go
// =============================================================================
func TestEmptyHandlers_AchievementGo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
{"handleMsgMhfDisplayedAchievement", func() {
handleMsgMhfDisplayedAchievement(session, &mhfpacket.MsgMhfDisplayedAchievement{})
}},
{"handleMsgMhfGetCaAchievementHist", func() { handleMsgMhfGetCaAchievementHist(session, nil) }},
{"handleMsgMhfSetCaAchievement", func() { handleMsgMhfSetCaAchievement(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 6: Empty handlers from handlers_caravan.go
// =============================================================================
// TestEmptyHandlers_CaravanGo removed: caravan handlers on main do type assertions
// and require proper packet structs, not nil.
// =============================================================================
// Category 7: Simple ack handlers from handlers_tactics.go (no DB needed)
// =============================================================================
func TestSimpleAckHandlers_TacticsGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfAddUdTacticsPoint", func(s *Session) {
handleMsgMhfAddUdTacticsPoint(s, &mhfpacket.MsgMhfAddUdTacticsPoint{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// TestSimpleAckHandlers_TowerGo removed: tower handlers on main access s.server.db
// and cannot be tested without a database connection.
// =============================================================================
// Category 9: Simple ack handlers from handlers_reward.go (no DB needed)
// =============================================================================
func TestSimpleAckHandlers_RewardGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfGetRewardSong", func(s *Session) {
handleMsgMhfGetRewardSong(s, &mhfpacket.MsgMhfGetRewardSong{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// =============================================================================
// Category 10: Simple ack handler from handlers_semaphore.go (no DB needed)
// handleMsgSysCreateSemaphore produces a response via doAckSimpleSucceed.
// =============================================================================
func TestSimpleAckHandlers_SemaphoreGo(t *testing.T) {
server := createMockServer()
t.Run("handleMsgSysCreateSemaphore", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysCreateSemaphore(session, &mhfpacket.MsgSysCreateSemaphore{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("handleMsgSysCreateSemaphore: response should have data")
}
default:
t.Error("handleMsgSysCreateSemaphore: no response queued")
}
})
}
// =============================================================================
// Category 11: handleMsgSysCreateAcquireSemaphore from handlers_semaphore.go
// This handler accesses s.server.semaphore map. It creates or acquires a
// semaphore, so it needs the semaphore map initialized on the server.
// =============================================================================
func TestHandleMsgSysCreateAcquireSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
t.Run("creates_new_semaphore", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysCreateAcquireSemaphore(session, &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 1,
SemaphoreID: "test_sema_1",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
// Verify semaphore was created
if _, exists := server.semaphore["test_sema_1"]; !exists {
t.Error("semaphore should have been created in server map")
}
})
t.Run("acquires_existing_semaphore", func(t *testing.T) {
session := createMockSession(2, server)
// Acquire the same semaphore again
handleMsgSysCreateAcquireSemaphore(session, &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 2,
SemaphoreID: "test_sema_1",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("creates_ravi_semaphore", func(t *testing.T) {
session := createMockSession(3, server)
handleMsgSysCreateAcquireSemaphore(session, &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 3,
SemaphoreID: "hs_l0u3B51",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
if _, exists := server.semaphore["hs_l0u3B51"]; !exists {
t.Error("ravi semaphore should have been created")
}
})
}
// =============================================================================
// Category 12: Additional simple ack handlers from various files (no DB)
// =============================================================================
// TestSimpleAckHandlers_MiscFiles removed: handleMsgMhfGetRengokuBinary panics
// on missing file (explicit panic in handler), cannot test without rengoku_data.bin.
// =============================================================================
// Category 13: Other empty handlers from various files
// =============================================================================
func TestEmptyHandlers_MiscFiles(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
fn func()
}{
// From handlers_reward.go
{"handleMsgMhfUseRewardSong", func() { handleMsgMhfUseRewardSong(session, nil) }},
{"handleMsgMhfAddRewardSongCount", func() { handleMsgMhfAddRewardSongCount(session, nil) }},
{"handleMsgMhfAcceptReadReward", func() { handleMsgMhfAcceptReadReward(session, nil) }},
// From handlers_caravan.go
{"handleMsgMhfPostRyoudama", func() { handleMsgMhfPostRyoudama(session, nil) }},
// From handlers_tactics.go
{"handleMsgMhfSetUdTacticsFollower", func() { handleMsgMhfSetUdTacticsFollower(session, nil) }},
{"handleMsgMhfGetUdTacticsLog", func() { handleMsgMhfGetUdTacticsLog(session, nil) }},
// From handlers_achievement.go
{"handleMsgMhfPaymentAchievement", func() { handleMsgMhfPaymentAchievement(session, nil) }},
// From handlers.go (additional empty ones)
{"handleMsgMhfGetCogInfo", func() { handleMsgMhfGetCogInfo(session, nil) }},
{"handleMsgMhfUseUdShopCoin", func() { handleMsgMhfUseUdShopCoin(session, nil) }},
{"handleMsgMhfGetDailyMissionMaster", func() { handleMsgMhfGetDailyMissionMaster(session, nil) }},
{"handleMsgMhfGetDailyMissionPersonal", func() { handleMsgMhfGetDailyMissionPersonal(session, nil) }},
{"handleMsgMhfSetDailyMissionPersonal", func() { handleMsgMhfSetDailyMissionPersonal(session, nil) }},
// From handlers_object.go (additional empty ones)
{"handleMsgSysAddObject", func() { handleMsgSysAddObject(session, nil) }},
{"handleMsgSysDelObject", func() { handleMsgSysDelObject(session, nil) }},
{"handleMsgSysDispObject", func() { handleMsgSysDispObject(session, nil) }},
{"handleMsgSysHideObject", func() { handleMsgSysHideObject(session, nil) }},
// From handlers.go (non-trivial but no pkt dereference)
{"handleMsgHead", func() { handleMsgHead(session, nil) }},
{"handleMsgSysExtendThreshold", func() { handleMsgSysExtendThreshold(session, nil) }},
{"handleMsgSysEnd", func() { handleMsgSysEnd(session, nil) }},
{"handleMsgSysNop", func() { handleMsgSysNop(session, nil) }},
{"handleMsgSysAck", func() { handleMsgSysAck(session, nil) }},
// From handlers_semaphore.go
{"handleMsgSysReleaseSemaphore", func() { handleMsgSysReleaseSemaphore(session, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.fn()
})
}
}
// =============================================================================
// Category 14: Handlers that produce responses without DB access
// These are non-trivial handlers with static/canned responses.
// =============================================================================
func TestNonTrivialHandlers_NoDB(t *testing.T) {
server := createMockServer()
t.Run("handleMsgMhfGetEarthStatus", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetEarthStatus(session, &mhfpacket.MsgMhfGetEarthStatus{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetEarthValue_Type1", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetEarthValue(session, &mhfpacket.MsgMhfGetEarthValue{AckHandle: 1, ReqType: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetEarthValue_Type2", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetEarthValue(session, &mhfpacket.MsgMhfGetEarthValue{AckHandle: 1, ReqType: 2})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetEarthValue_Type3", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetEarthValue(session, &mhfpacket.MsgMhfGetEarthValue{AckHandle: 1, ReqType: 3})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetSeibattle", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetSeibattle(session, &mhfpacket.MsgMhfGetSeibattle{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
// handleMsgMhfGetTrendWeapon removed: requires database access
// handleMsgMhfUpdateUseTrendWeaponLog removed: requires database access
t.Run("handleMsgMhfUpdateBeatLevel", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfUpdateBeatLevel(session, &mhfpacket.MsgMhfUpdateBeatLevel{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfReadBeatLevel", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfReadBeatLevel(session, &mhfpacket.MsgMhfReadBeatLevel{
AckHandle: 1,
ValidIDCount: 2,
IDs: [16]uint32{100, 200},
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfTransferItem", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfTransferItem(session, &mhfpacket.MsgMhfTransferItem{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfEnumerateOrder", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfEnumerateOrder(session, &mhfpacket.MsgMhfEnumerateOrder{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetUdShopCoin", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetUdShopCoin(session, &mhfpacket.MsgMhfGetUdShopCoin{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfGetLobbyCrowd", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetLobbyCrowd(session, &mhfpacket.MsgMhfGetLobbyCrowd{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgMhfEnumeratePrice", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfEnumeratePrice(session, &mhfpacket.MsgMhfEnumeratePrice{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 15: Handlers from handlers_tactics.go that produce responses (no DB)
// =============================================================================
func TestNonTrivialHandlers_TacticsGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfGetUdTacticsPoint", func(s *Session) {
handleMsgMhfGetUdTacticsPoint(s, &mhfpacket.MsgMhfGetUdTacticsPoint{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsRewardList", func(s *Session) {
handleMsgMhfGetUdTacticsRewardList(s, &mhfpacket.MsgMhfGetUdTacticsRewardList{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsFollower", func(s *Session) {
handleMsgMhfGetUdTacticsFollower(s, &mhfpacket.MsgMhfGetUdTacticsFollower{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsBonusQuest", func(s *Session) {
handleMsgMhfGetUdTacticsBonusQuest(s, &mhfpacket.MsgMhfGetUdTacticsBonusQuest{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsFirstQuestBonus", func(s *Session) {
handleMsgMhfGetUdTacticsFirstQuestBonus(s, &mhfpacket.MsgMhfGetUdTacticsFirstQuestBonus{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsRemainingPoint", func(s *Session) {
handleMsgMhfGetUdTacticsRemainingPoint(s, &mhfpacket.MsgMhfGetUdTacticsRemainingPoint{AckHandle: 1})
}},
{"handleMsgMhfGetUdTacticsRanking", func(s *Session) {
handleMsgMhfGetUdTacticsRanking(s, &mhfpacket.MsgMhfGetUdTacticsRanking{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// =============================================================================
// Category 16: Handlers from handlers_tower.go that produce responses (no DB)
// =============================================================================
func TestNonTrivialHandlers_TowerGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfGetTenrouirai_Type1", func(s *Session) {
handleMsgMhfGetTenrouirai(s, &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, Unk0: 1})
}},
{"handleMsgMhfGetTenrouirai_Unknown", func(s *Session) {
handleMsgMhfGetTenrouirai(s, &mhfpacket.MsgMhfGetTenrouirai{AckHandle: 1, Unk0: 0, DataType: 0})
}},
// handleMsgMhfGetTenrouirai_Type4, handleMsgMhfPostTenrouirai, handleMsgMhfGetGemInfo removed: require DB
{"handleMsgMhfGetWeeklySeibatuRankingReward", func(s *Session) {
handleMsgMhfGetWeeklySeibatuRankingReward(s, &mhfpacket.MsgMhfGetWeeklySeibatuRankingReward{AckHandle: 1})
}},
{"handleMsgMhfPresentBox", func(s *Session) {
handleMsgMhfPresentBox(s, &mhfpacket.MsgMhfPresentBox{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// =============================================================================
// Category 17: Handlers from handlers_reward.go that produce responses (no DB)
// =============================================================================
func TestNonTrivialHandlers_RewardGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfGetAdditionalBeatReward", func(s *Session) {
handleMsgMhfGetAdditionalBeatReward(s, &mhfpacket.MsgMhfGetAdditionalBeatReward{AckHandle: 1})
}},
{"handleMsgMhfGetUdRankingRewardList", func(s *Session) {
handleMsgMhfGetUdRankingRewardList(s, &mhfpacket.MsgMhfGetUdRankingRewardList{AckHandle: 1})
}},
{"handleMsgMhfAcquireMonthlyReward", func(s *Session) {
handleMsgMhfAcquireMonthlyReward(s, &mhfpacket.MsgMhfAcquireMonthlyReward{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// =============================================================================
// Category 18: Handlers from handlers_caravan.go that produce responses (no DB)
// =============================================================================
func TestNonTrivialHandlers_CaravanGo(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
fn func(s *Session)
}{
{"handleMsgMhfGetRyoudama", func(s *Session) {
handleMsgMhfGetRyoudama(s, &mhfpacket.MsgMhfGetRyoudama{AckHandle: 1})
}},
{"handleMsgMhfGetTinyBin", func(s *Session) {
handleMsgMhfGetTinyBin(s, &mhfpacket.MsgMhfGetTinyBin{AckHandle: 1})
}},
{"handleMsgMhfPostTinyBin", func(s *Session) {
handleMsgMhfPostTinyBin(s, &mhfpacket.MsgMhfPostTinyBin{AckHandle: 1})
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.fn(session)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("%s: response should have data", tt.name)
}
default:
t.Errorf("%s: no response queued", tt.name)
}
})
}
}
// =============================================================================
// Category 19: Handlers from handlers_rengoku.go (no DB needed)
// =============================================================================
func TestNonTrivialHandlers_RengokuGo(t *testing.T) {
server := createMockServer()
t.Run("handleMsgMhfGetRengokuRankingRank", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgMhfGetRengokuRankingRank(session, &mhfpacket.MsgMhfGetRengokuRankingRank{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 20: Handlers from handlers.go that produce responses (no DB)
// =============================================================================
// TestNonTrivialHandlers_InfoScenarioCounter removed: requires database access.
// =============================================================================
// Category 21: handleMsgSysPing and handleMsgSysTime (no DB)
// =============================================================================
func TestSimpleHandlers_PingAndTime(t *testing.T) {
server := createMockServer()
t.Run("handleMsgSysPing", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysPing(session, &mhfpacket.MsgSysPing{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("handleMsgSysTime", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysTime(session, &mhfpacket.MsgSysTime{})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 22: handleMsgSysIssueLogkey (no DB, uses crypto/rand)
// =============================================================================
func TestHandleMsgSysIssueLogkey_Coverage3(t *testing.T) {
server := createMockServer()
t.Run("generates_logkey", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysIssueLogkey(session, &mhfpacket.MsgSysIssueLogkey{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
if session.logKey == nil {
t.Error("logKey should be set after IssueLogkey")
}
if len(session.logKey) != 16 {
t.Errorf("logKey length = %d, want 16", len(session.logKey))
}
})
}
// =============================================================================
// Category 23: handleMsgSysUnlockGlobalSema (no DB)
// =============================================================================
func TestHandleMsgSysUnlockGlobalSema_Coverage3(t *testing.T) {
server := createMockServer()
t.Run("produces_response", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysUnlockGlobalSema(session, &mhfpacket.MsgSysUnlockGlobalSema{AckHandle: 1})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 24: handleMsgSysLockGlobalSema (no DB, but needs Channels)
// =============================================================================
func TestHandleMsgSysLockGlobalSema(t *testing.T) {
server := createMockServer()
server.Channels = make([]*Server, 0)
t.Run("no_channels_returns_response", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysLockGlobalSema(session, &mhfpacket.MsgSysLockGlobalSema{
AckHandle: 1,
UserIDString: "testuser",
ServerChannelIDString: "ch1",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 25: handleMsgSysCheckSemaphore (no DB)
// =============================================================================
func TestHandleMsgSysCheckSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
t.Run("semaphore_not_exists", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysCheckSemaphore(session, &mhfpacket.MsgSysCheckSemaphore{
AckHandle: 1,
SemaphoreID: "nonexistent",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("semaphore_exists", func(t *testing.T) {
session := createMockSession(1, server)
server.semaphore["existing_sema"] = NewSemaphore(session, "existing_sema", 1)
handleMsgSysCheckSemaphore(session, &mhfpacket.MsgSysCheckSemaphore{
AckHandle: 1,
SemaphoreID: "existing_sema",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 26: handleMsgSysAcquireSemaphore (no DB)
// =============================================================================
func TestHandleMsgSysAcquireSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
t.Run("semaphore_exists", func(t *testing.T) {
session := createMockSession(1, server)
server.semaphore["acquire_sema"] = NewSemaphore(session, "acquire_sema", 1)
handleMsgSysAcquireSemaphore(session, &mhfpacket.MsgSysAcquireSemaphore{
AckHandle: 1,
SemaphoreID: "acquire_sema",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("semaphore_not_exists", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysAcquireSemaphore(session, &mhfpacket.MsgSysAcquireSemaphore{
AckHandle: 1,
SemaphoreID: "nonexistent_sema",
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 27: handleMsgSysCreateStage (no DB)
// =============================================================================
func TestHandleMsgSysCreateStage_Coverage3(t *testing.T) {
server := createMockServer()
t.Run("creates_new_stage", func(t *testing.T) {
session := createMockSession(1, server)
handleMsgSysCreateStage(session, &mhfpacket.MsgSysCreateStage{
AckHandle: 1,
StageID: "test_create_stage",
PlayerCount: 4,
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
if _, exists := server.stages.Get("test_create_stage"); !exists {
t.Error("stage should have been created")
}
})
t.Run("duplicate_stage_fails", func(t *testing.T) {
session := createMockSession(1, server)
// Stage already exists from the previous test
handleMsgSysCreateStage(session, &mhfpacket.MsgSysCreateStage{
AckHandle: 2,
StageID: "test_create_stage",
PlayerCount: 4,
})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data even on failure")
}
default:
t.Error("no response queued")
}
})
}
// =============================================================================
// Category 28: Concurrency test for empty handlers
// Verify that calling empty handlers concurrently does not panic.
// =============================================================================
func TestEmptyHandlers_Concurrent(t *testing.T) {
server := createMockServer()
handlers := []func(*Session, mhfpacket.MHFPacket){
handleMsgSysEcho,
handleMsgSysUpdateRight,
handleMsgSysAuthQuery,
handleMsgSysAuthTerminal,
handleMsgCaExchangeItem,
handleMsgMhfServerCommand,
handleMsgMhfSetLoginwindow,
handleMsgSysTransBinary,
handleMsgSysCollectBinary,
handleMsgSysGetState,
handleMsgSysSerialize,
handleMsgSysEnumlobby,
handleMsgSysEnumuser,
handleMsgSysInfokyserver,
handleMsgMhfGetCaUniqueID,
handleMsgMhfGetExtraInfo,
handleMsgSysSetStatus,
handleMsgSysDeleteObject,
handleMsgSysRotateObject,
handleMsgSysDuplicateObject,
handleMsgSysGetObjectBinary,
handleMsgSysGetObjectOwner,
handleMsgSysUpdateObjectBinary,
handleMsgSysCleanupObject,
handleMsgMhfShutClient,
handleMsgSysHideClient,
handleMsgSysStageDestruct,
}
var wg sync.WaitGroup
for _, h := range handlers {
for i := 0; i < 10; i++ {
wg.Add(1)
go func(handler func(*Session, mhfpacket.MHFPacket)) {
defer wg.Done()
session := createMockSession(1, server)
handler(session, nil)
}(h)
}
}
wg.Wait()
}
// =============================================================================
// Category 29: stubEnumerateNoResults and stubGetNoResults helper coverage
// These are called by many handlers; test them directly too.
// =============================================================================
func TestStubHelpers(t *testing.T) {
server := createMockServer()
t.Run("stubEnumerateNoResults", func(t *testing.T) {
session := createMockSession(1, server)
stubEnumerateNoResults(session, 1)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("doAckBufSucceed", func(t *testing.T) {
session := createMockSession(1, server)
doAckBufSucceed(session, 1, []byte{0x01, 0x02, 0x03})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("doAckBufFail", func(t *testing.T) {
session := createMockSession(1, server)
doAckBufFail(session, 1, []byte{0x01, 0x02, 0x03})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("doAckSimpleSucceed", func(t *testing.T) {
session := createMockSession(1, server)
doAckSimpleSucceed(session, 1, []byte{0x00, 0x00, 0x00, 0x00})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
t.Run("doAckSimpleFail", func(t *testing.T) {
session := createMockSession(1, server)
doAckSimpleFail(session, 1, []byte{0x00, 0x00, 0x00, 0x00})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("response should have data")
}
default:
t.Error("no response queued")
}
})
}