mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
main.go always sets both Channels and Registry together, making the Channels fallback paths dead code. This removes: - Server.Channels field from the Server struct - 3 if/else fallback blocks in handlers_session.go (replaced with Registry.FindChannelForStage, SearchSessions, SearchStages) - 1 if/else fallback block in handlers_guild_ops.go (replaced with Registry.NotifyMailToCharID) - 3 method fallbacks in sys_channel_server.go (WorldcastMHF, FindSessionByCharID, DisconnectUser now delegate directly) Updates anti-patterns.md #6 to "accepted design" — Session struct is appropriate for this game server's handler pattern, and cross-channel coupling is now fully routed through the ChannelRegistry interface.
1136 lines
36 KiB
Go
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.Registry = NewLocalChannelRegistry(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")
|
|
}
|
|
})
|
|
}
|