mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-24 16:43:37 +01:00
MSG_CA_EXCHANGE_ITEM and MSG_MHF_USE_UD_SHOP_COIN had Parse() returning
"NOT IMPLEMENTED". The dispatch loop in handlePacketGroup treats any
Parse error as a silent drop — no ACK is sent, causing the client to
wait indefinitely (softlock). Reported on 9.3.0-rc1 for forge item
purchases and Hunting Road N-point interactions.
Fix follows the pattern from d27da5e: parse only the AckHandle, return
nil from Parse, and respond with doAckBufFail so the client's error
branch exits cleanly without reading response fields.
310 lines
7.4 KiB
Go
310 lines
7.4 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"erupe-ce/network/mhfpacket"
|
|
)
|
|
|
|
// Test simple handler patterns that don't require database
|
|
|
|
func TestHandlerMsgMhfSexChanger(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfSexChanger{
|
|
AckHandle: 12345,
|
|
}
|
|
|
|
// Should not panic
|
|
handleMsgMhfSexChanger(session, pkt)
|
|
|
|
// Should queue a response
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response packet should have data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestHandlerMsgMhfEnterTournamentQuest(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
// Should not panic with nil packet (empty handler)
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleMsgMhfEnterTournamentQuest panicked: %v", r)
|
|
}
|
|
}()
|
|
|
|
handleMsgMhfEnterTournamentQuest(session, nil)
|
|
}
|
|
|
|
func TestHandlerMsgMhfGetUdBonusQuestInfo(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetUdBonusQuestInfo{
|
|
AckHandle: 12345,
|
|
}
|
|
|
|
handleMsgMhfGetUdBonusQuestInfo(session, pkt)
|
|
|
|
// Should queue a response
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response packet should have data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
// Test that acknowledge handlers work correctly
|
|
|
|
func TestAckResponseFormats(t *testing.T) {
|
|
server := createMockServer()
|
|
|
|
tests := []struct {
|
|
name string
|
|
handler func(s *Session, ackHandle uint32, data []byte)
|
|
}{
|
|
{"doAckBufSucceed", doAckBufSucceed},
|
|
{"doAckBufFail", doAckBufFail},
|
|
{"doAckSimpleSucceed", doAckSimpleSucceed},
|
|
{"doAckSimpleFail", doAckSimpleFail},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
session := createMockSession(1, server)
|
|
testData := []byte{0x01, 0x02, 0x03, 0x04}
|
|
|
|
tt.handler(session, 99999, testData)
|
|
|
|
select {
|
|
case pkt := <-session.sendPackets:
|
|
if pkt.data == nil {
|
|
t.Error("Packet data should not be nil")
|
|
}
|
|
default:
|
|
t.Error("Handler should queue a packet")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStubHandlers(t *testing.T) {
|
|
server := createMockServer()
|
|
|
|
tests := []struct {
|
|
name string
|
|
handler func(s *Session, ackHandle uint32)
|
|
}{
|
|
{"stubEnumerateNoResults", stubEnumerateNoResults},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
session := createMockSession(1, server)
|
|
|
|
tt.handler(session, 12345)
|
|
|
|
select {
|
|
case pkt := <-session.sendPackets:
|
|
if pkt.data == nil {
|
|
t.Error("Packet data should not be nil")
|
|
}
|
|
default:
|
|
t.Error("Stub handler should queue a packet")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test packet queueing
|
|
|
|
func TestSessionQueueSendMHF(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgSysAck{
|
|
AckHandle: 12345,
|
|
IsBufferResponse: false,
|
|
ErrorCode: 0,
|
|
AckData: []byte{0x00},
|
|
}
|
|
|
|
session.QueueSendMHF(pkt)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Queued packet should have data")
|
|
}
|
|
default:
|
|
t.Error("QueueSendMHF should queue a packet")
|
|
}
|
|
}
|
|
|
|
func TestSessionQueueSendNonBlocking(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
data := []byte{0x01, 0x02, 0x03, 0x04}
|
|
session.QueueSendNonBlocking(data)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) != 4 {
|
|
t.Errorf("Queued data len = %d, want 4", len(p.data))
|
|
}
|
|
if p.nonBlocking != true {
|
|
t.Error("Packet should be marked as non-blocking")
|
|
}
|
|
default:
|
|
t.Error("QueueSendNonBlocking should queue data")
|
|
}
|
|
}
|
|
|
|
func TestSessionQueueSendNonBlocking_FullQueue(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
// Fill the queue
|
|
for i := 0; i < 20; i++ {
|
|
session.sendPackets <- packet{data: []byte{byte(i)}, nonBlocking: true}
|
|
}
|
|
|
|
// Non-blocking send should not block when queue is full
|
|
// It should drop the packet instead
|
|
done := make(chan bool, 1)
|
|
go func() {
|
|
session.QueueSendNonBlocking([]byte{0xFF})
|
|
done <- true
|
|
}()
|
|
|
|
// Wait for completion with a reasonable timeout
|
|
// The function should return immediately (dropping the packet)
|
|
select {
|
|
case <-done:
|
|
// Good - didn't block, function completed
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("QueueSendNonBlocking blocked on full queue")
|
|
}
|
|
}
|
|
|
|
// Additional handler tests for coverage
|
|
|
|
func TestHandleMsgMhfGetGuildWeeklyBonusMaster(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetGuildWeeklyBonusMaster{
|
|
AckHandle: 12345,
|
|
}
|
|
|
|
handleMsgMhfGetGuildWeeklyBonusMaster(session, pkt)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response packet should have data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestHandleMsgMhfGetGuildWeeklyBonusActiveCount(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount{
|
|
AckHandle: 12345,
|
|
}
|
|
|
|
handleMsgMhfGetGuildWeeklyBonusActiveCount(session, pkt)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response packet should have data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestHandleMsgMhfAddGuildWeeklyBonusExceptionalUser(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
pkt := &mhfpacket.MsgMhfAddGuildWeeklyBonusExceptionalUser{
|
|
AckHandle: 12345,
|
|
}
|
|
|
|
handleMsgMhfAddGuildWeeklyBonusExceptionalUser(session, pkt)
|
|
|
|
select {
|
|
case p := <-session.sendPackets:
|
|
if len(p.data) == 0 {
|
|
t.Error("Response packet should have data")
|
|
}
|
|
default:
|
|
t.Error("No response packet queued")
|
|
}
|
|
}
|
|
|
|
func TestEmptyHandlers_NoDb(t *testing.T) {
|
|
server := createMockServer()
|
|
session := createMockSession(1, server)
|
|
|
|
// Test handlers that are empty and should not panic
|
|
tests := []struct {
|
|
name string
|
|
handler func(s *Session, p mhfpacket.MHFPacket)
|
|
}{
|
|
{"handleMsgHead", handleMsgHead},
|
|
{"handleMsgSysExtendThreshold", handleMsgSysExtendThreshold},
|
|
{"handleMsgSysEnd", handleMsgSysEnd},
|
|
{"handleMsgSysNop", handleMsgSysNop},
|
|
{"handleMsgSysAck", handleMsgSysAck},
|
|
{"handleMsgSysUpdateRight", handleMsgSysUpdateRight},
|
|
{"handleMsgSysAuthQuery", handleMsgSysAuthQuery},
|
|
{"handleMsgSysAuthTerminal", handleMsgSysAuthTerminal},
|
|
{"handleMsgMhfServerCommand", handleMsgMhfServerCommand},
|
|
{"handleMsgMhfSetLoginwindow", handleMsgMhfSetLoginwindow},
|
|
{"handleMsgSysTransBinary", handleMsgSysTransBinary},
|
|
{"handleMsgSysCollectBinary", handleMsgSysCollectBinary},
|
|
{"handleMsgSysGetState", handleMsgSysGetState},
|
|
{"handleMsgSysSerialize", handleMsgSysSerialize},
|
|
{"handleMsgSysEnumlobby", handleMsgSysEnumlobby},
|
|
{"handleMsgSysEnumuser", handleMsgSysEnumuser},
|
|
{"handleMsgSysInfokyserver", handleMsgSysInfokyserver},
|
|
{"handleMsgMhfGetCaUniqueID", handleMsgMhfGetCaUniqueID},
|
|
{"handleMsgMhfStampcardPrize", handleMsgMhfStampcardPrize},
|
|
{"handleMsgMhfKickExportForce", handleMsgMhfKickExportForce},
|
|
{"handleMsgSysSetStatus", handleMsgSysSetStatus},
|
|
{"handleMsgSysEcho", handleMsgSysEcho},
|
|
{"handleMsgMhfEnterTournamentQuest", handleMsgMhfEnterTournamentQuest},
|
|
}
|
|
|
|
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.handler(session, nil)
|
|
})
|
|
}
|
|
}
|