mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: expand channelserver coverage from 20% to 25%
Add tests for: - Discord handlers (getPlayerSlice, getCharacterList) - House handlers (boxToBytes, HouseData, Title structs) - Mail struct tests - Mercenary handlers (Partner, HunterNavi structs) - Shop/Gacha handlers (writeShopItems, ShopItem, Gacha structs) - Additional handler coverage for guild, tower, and simple handlers - Stage handler tests for binary operations and enumeration - Channel server tests for BroadcastMHF and session management
This commit is contained in:
@@ -2,6 +2,8 @@ package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func TestGetAchData_Level0(t *testing.T) {
|
||||
@@ -189,3 +191,52 @@ func TestAchievementCurveMap_Coverage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMsgMhfSetCaAchievementHist(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfSetCaAchievementHist{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgMhfSetCaAchievementHist(session, pkt)
|
||||
|
||||
// Verify response packet was queued
|
||||
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 empty achievement handlers don't panic
|
||||
func TestEmptyAchievementHandlers(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
handler func(s *Session, p mhfpacket.MHFPacket)
|
||||
}{
|
||||
{"handleMsgMhfResetAchievement", handleMsgMhfResetAchievement},
|
||||
{"handleMsgMhfPaymentAchievement", handleMsgMhfPaymentAchievement},
|
||||
{"handleMsgMhfDisplayedAchievement", handleMsgMhfDisplayedAchievement},
|
||||
{"handleMsgMhfGetCaAchievementHist", handleMsgMhfGetCaAchievementHist},
|
||||
{"handleMsgMhfSetCaAchievement", handleMsgMhfSetCaAchievement},
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,3 +365,320 @@ func TestHandleMsgSysTerminalLog_WithEntries(t *testing.T) {
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// Test ping handler
|
||||
func TestHandleMsgSysPing(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysPing{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgSysPing(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")
|
||||
}
|
||||
}
|
||||
|
||||
// Test time handler
|
||||
func TestHandleMsgSysTime(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysTime{
|
||||
GetRemoteTime: true,
|
||||
}
|
||||
|
||||
handleMsgSysTime(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")
|
||||
}
|
||||
}
|
||||
|
||||
// Test issue logkey handler
|
||||
func TestHandleMsgSysIssueLogkey(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysIssueLogkey{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgSysIssueLogkey(session, pkt)
|
||||
|
||||
// Verify logkey was set
|
||||
if len(session.logKey) != 16 {
|
||||
t.Errorf("logKey length = %d, want 16", len(session.logKey))
|
||||
}
|
||||
|
||||
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 record log handler
|
||||
func TestHandleMsgSysRecordLog(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Setup stage
|
||||
stage := NewStage("test_stage")
|
||||
session.stage = stage
|
||||
stage.reservedClientSlots[session.charID] = true
|
||||
|
||||
pkt := &mhfpacket.MsgSysRecordLog{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgSysRecordLog(session, pkt)
|
||||
|
||||
// Verify charID removed from reserved slots
|
||||
if _, exists := stage.reservedClientSlots[session.charID]; exists {
|
||||
t.Error("charID should be removed from reserved slots")
|
||||
}
|
||||
|
||||
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 unlock global sema handler
|
||||
func TestHandleMsgSysUnlockGlobalSema(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysUnlockGlobalSema{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgSysUnlockGlobalSema(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")
|
||||
}
|
||||
}
|
||||
|
||||
// Test more empty handlers
|
||||
func TestHandleMsgSysSetStatus(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysSetStatus panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysSetStatus(session, nil)
|
||||
}
|
||||
|
||||
func TestHandleMsgSysEcho(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysEcho panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysEcho(session, nil)
|
||||
}
|
||||
|
||||
func TestHandleMsgSysUpdateRight(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysUpdateRight panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysUpdateRight(session, nil)
|
||||
}
|
||||
|
||||
func TestHandleMsgSysAuthQuery(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysAuthQuery panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysAuthQuery(session, nil)
|
||||
}
|
||||
|
||||
func TestHandleMsgSysAuthTerminal(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysAuthTerminal panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysAuthTerminal(session, nil)
|
||||
}
|
||||
|
||||
// Test lock global sema handler
|
||||
func TestHandleMsgSysLockGlobalSema_NoMatch(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.GlobalID = "test-server"
|
||||
server.Channels = []*Server{}
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysLockGlobalSema{
|
||||
AckHandle: 12345,
|
||||
UserIDString: "user123",
|
||||
ServerChannelIDString: "channel1",
|
||||
}
|
||||
|
||||
handleMsgSysLockGlobalSema(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 TestHandleMsgSysLockGlobalSema_WithChannel(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.GlobalID = "test-server"
|
||||
|
||||
// Create a mock channel with stages
|
||||
channel := &Server{
|
||||
GlobalID: "other-server",
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
channel.stages["stage_user123"] = NewStage("stage_user123")
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysLockGlobalSema{
|
||||
AckHandle: 12345,
|
||||
UserIDString: "user123",
|
||||
ServerChannelIDString: "channel1",
|
||||
}
|
||||
|
||||
handleMsgSysLockGlobalSema(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 TestHandleMsgSysLockGlobalSema_SameServer(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.GlobalID = "test-server"
|
||||
|
||||
// Create a mock channel with same GlobalID
|
||||
channel := &Server{
|
||||
GlobalID: "test-server",
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
channel.stages["stage_user456"] = NewStage("stage_user456")
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysLockGlobalSema{
|
||||
AckHandle: 12345,
|
||||
UserIDString: "user456",
|
||||
ServerChannelIDString: "channel2",
|
||||
}
|
||||
|
||||
handleMsgSysLockGlobalSema(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 TestHandleMsgMhfAnnounce(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfAnnounce{
|
||||
AckHandle: 12345,
|
||||
IPAddress: 0x7F000001, // 127.0.0.1
|
||||
Port: 54001,
|
||||
StageID: []byte("test_stage"),
|
||||
Type: 1,
|
||||
}
|
||||
|
||||
handleMsgMhfAnnounce(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 TestHandleMsgSysRightsReload(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysRightsReload{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
// This will panic due to nil db, which is expected in test
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Log("Expected panic due to nil database in test")
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysRightsReload(session, pkt)
|
||||
}
|
||||
|
||||
163
server/channelserver/handlers_discord_test.go
Normal file
163
server/channelserver/handlers_discord_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlayerStruct(t *testing.T) {
|
||||
player := Player{
|
||||
CharName: "TestPlayer",
|
||||
QuestID: 5,
|
||||
}
|
||||
|
||||
if player.CharName != "TestPlayer" {
|
||||
t.Errorf("CharName = %s, want TestPlayer", player.CharName)
|
||||
}
|
||||
if player.QuestID != 5 {
|
||||
t.Errorf("QuestID = %d, want 5", player.QuestID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayerSlice_EmptyServer(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.Channels = []*Server{}
|
||||
|
||||
players := getPlayerSlice(server)
|
||||
|
||||
if len(players) != 0 {
|
||||
t.Errorf("Expected 0 players, got %d", len(players))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayerSlice_WithChannel(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
// Create a channel with stages
|
||||
channel := &Server{
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
|
||||
// Create a stage with clients
|
||||
stage := NewStage("test_stage")
|
||||
session := createMockSession(1, server)
|
||||
session.Name = "Player1"
|
||||
stage.clients[session] = session.charID
|
||||
|
||||
channel.stages["test_stage"] = stage
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
players := getPlayerSlice(server)
|
||||
|
||||
if len(players) != 1 {
|
||||
t.Errorf("Expected 1 player, got %d", len(players))
|
||||
}
|
||||
if len(players) > 0 && players[0].CharName != "Player1" {
|
||||
t.Errorf("Expected CharName Player1, got %s", players[0].CharName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayerSlice_MultiplePlayersMultipleStages(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
channel := &Server{
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
|
||||
// Stage 1 with one player
|
||||
stage1 := NewStage("stage1")
|
||||
session1 := createMockSession(1, server)
|
||||
session1.Name = "Player1"
|
||||
stage1.clients[session1] = session1.charID
|
||||
channel.stages["stage1"] = stage1
|
||||
|
||||
// Stage 2 with two players
|
||||
stage2 := NewStage("stage2")
|
||||
session2 := createMockSession(2, server)
|
||||
session2.Name = "Player2"
|
||||
session3 := createMockSession(3, server)
|
||||
session3.Name = "Player3"
|
||||
stage2.clients[session2] = session2.charID
|
||||
stage2.clients[session3] = session3.charID
|
||||
channel.stages["stage2"] = stage2
|
||||
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
players := getPlayerSlice(server)
|
||||
|
||||
if len(players) != 3 {
|
||||
t.Errorf("Expected 3 players, got %d", len(players))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayerSlice_EmptyStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
channel := &Server{
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
|
||||
// Empty stage (no clients)
|
||||
emptyStage := NewStage("empty_stage")
|
||||
channel.stages["empty_stage"] = emptyStage
|
||||
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
players := getPlayerSlice(server)
|
||||
|
||||
if len(players) != 0 {
|
||||
t.Errorf("Expected 0 players from empty stage, got %d", len(players))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharacterList_EmptyServer(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.Channels = []*Server{}
|
||||
|
||||
result := getCharacterList(server)
|
||||
|
||||
expected := "===== Online: 0 =====\n"
|
||||
if result != expected {
|
||||
t.Errorf("Expected %q, got %q", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharacterList_WithPlayers(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
channel := &Server{
|
||||
stages: make(map[string]*Stage),
|
||||
}
|
||||
|
||||
stage := NewStage("lobby")
|
||||
session := createMockSession(1, server)
|
||||
session.Name = "Hunter1"
|
||||
stage.clients[session] = session.charID
|
||||
channel.stages["lobby"] = stage
|
||||
|
||||
server.Channels = []*Server{channel}
|
||||
|
||||
result := getCharacterList(server)
|
||||
|
||||
// Should contain the online count
|
||||
if len(result) == 0 {
|
||||
t.Error("Expected non-empty result")
|
||||
}
|
||||
|
||||
// Should contain "Online: 1"
|
||||
if !contains(result, "Online: 1") {
|
||||
t.Errorf("Expected result to contain 'Online: 1', got %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsAt(s, substr))
|
||||
}
|
||||
|
||||
func containsAt(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -295,3 +295,49 @@ func TestGenerateDivaTimestamps_Debug(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDivaTimestamps_Debug_StartGreaterThan3(t *testing.T) {
|
||||
// Test debug mode with start > 3 (falls through to non-debug path)
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// With debug=true but start > 3, should fall through to non-debug path
|
||||
// This will try to access DB which will panic, so we catch it
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Log("Expected panic due to nil database in test")
|
||||
}
|
||||
}()
|
||||
|
||||
timestamps := generateDivaTimestamps(session, 100, true)
|
||||
if len(timestamps) != 6 {
|
||||
t.Errorf("Expected 6 timestamps, got %d", len(timestamps))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDivaTimestamps_NonDebug_WithValidStart(t *testing.T) {
|
||||
// Test non-debug mode with valid start timestamp (not expired)
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Use a start time in the future (won't trigger cleanup)
|
||||
futureStart := uint32(TimeAdjusted().Unix() + 1000000) // Far in the future
|
||||
|
||||
timestamps := generateDivaTimestamps(session, futureStart, false)
|
||||
if len(timestamps) != 6 {
|
||||
t.Errorf("Expected 6 timestamps, got %d", len(timestamps))
|
||||
}
|
||||
|
||||
// Verify first timestamp matches start
|
||||
if timestamps[0] != futureStart {
|
||||
t.Errorf("First timestamp should match start, got %d want %d", timestamps[0], futureStart)
|
||||
}
|
||||
|
||||
// Verify timestamp intervals
|
||||
if timestamps[1] != timestamps[0]+601200 {
|
||||
t.Error("Second timestamp should be start + 601200")
|
||||
}
|
||||
if timestamps[2] != timestamps[1]+3900 {
|
||||
t.Error("Third timestamp should be second + 3900")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,3 +110,47 @@ func TestHandleMsgMhfEnumerateRanking_State3(t *testing.T) {
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMsgMhfVoteFesta(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfVoteFesta{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgMhfVoteFesta(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 TestEmptyFestaHandlers(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
handler func(s *Session, p mhfpacket.MHFPacket)
|
||||
}{
|
||||
{"handleMsgMhfEntryTournament", handleMsgMhfEntryTournament},
|
||||
{"handleMsgMhfAcquireTournament", handleMsgMhfAcquireTournament},
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
203
server/channelserver/handlers_house_test.go
Normal file
203
server/channelserver/handlers_house_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func TestBoxToBytes_EmptyItemBox(t *testing.T) {
|
||||
stacks := []mhfpacket.WarehouseStack{}
|
||||
result := boxToBytes(stacks, "item")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 0 {
|
||||
t.Errorf("Expected 0 stacks, got %d", numStacks)
|
||||
}
|
||||
|
||||
// Should have trailing uint16(0)
|
||||
if len(result) != 4 {
|
||||
t.Errorf("Expected 4 bytes for empty box, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxToBytes_SingleItemStack(t *testing.T) {
|
||||
stacks := []mhfpacket.WarehouseStack{
|
||||
{
|
||||
ID: 1,
|
||||
Index: 0,
|
||||
ItemID: 100,
|
||||
Quantity: 50,
|
||||
},
|
||||
}
|
||||
result := boxToBytes(stacks, "item")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 1 {
|
||||
t.Errorf("Expected 1 stack, got %d", numStacks)
|
||||
}
|
||||
|
||||
// Read first stack
|
||||
id := bf.ReadUint32()
|
||||
index := bf.ReadUint16()
|
||||
itemID := bf.ReadUint16()
|
||||
quantity := bf.ReadUint16()
|
||||
_ = bf.ReadUint16() // padding
|
||||
|
||||
if id != 1 {
|
||||
t.Errorf("Expected ID 1, got %d", id)
|
||||
}
|
||||
if index != 1 { // Index is written as i+1
|
||||
t.Errorf("Expected index 1, got %d", index)
|
||||
}
|
||||
if itemID != 100 {
|
||||
t.Errorf("Expected itemID 100, got %d", itemID)
|
||||
}
|
||||
if quantity != 50 {
|
||||
t.Errorf("Expected quantity 50, got %d", quantity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxToBytes_MultipleItemStacks(t *testing.T) {
|
||||
stacks := []mhfpacket.WarehouseStack{
|
||||
{ID: 1, Index: 0, ItemID: 100, Quantity: 10},
|
||||
{ID: 2, Index: 1, ItemID: 200, Quantity: 20},
|
||||
{ID: 3, Index: 2, ItemID: 300, Quantity: 30},
|
||||
}
|
||||
result := boxToBytes(stacks, "item")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 3 {
|
||||
t.Errorf("Expected 3 stacks, got %d", numStacks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxToBytes_EmptyEquipBox(t *testing.T) {
|
||||
stacks := []mhfpacket.WarehouseStack{}
|
||||
result := boxToBytes(stacks, "equip")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 0 {
|
||||
t.Errorf("Expected 0 stacks, got %d", numStacks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxToBytes_SingleEquipStack(t *testing.T) {
|
||||
equipData := make([]byte, 56)
|
||||
for i := range equipData {
|
||||
equipData[i] = byte(i)
|
||||
}
|
||||
|
||||
stacks := []mhfpacket.WarehouseStack{
|
||||
{
|
||||
ID: 1,
|
||||
Index: 0,
|
||||
EquipType: 5,
|
||||
ItemID: 1000,
|
||||
Data: equipData,
|
||||
},
|
||||
}
|
||||
result := boxToBytes(stacks, "equip")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 1 {
|
||||
t.Errorf("Expected 1 stack, got %d", numStacks)
|
||||
}
|
||||
|
||||
// Read first equip stack
|
||||
id := bf.ReadUint32()
|
||||
index := bf.ReadUint16()
|
||||
equipType := bf.ReadUint16()
|
||||
itemID := bf.ReadUint16()
|
||||
data := bf.ReadBytes(56)
|
||||
|
||||
if id != 1 {
|
||||
t.Errorf("Expected ID 1, got %d", id)
|
||||
}
|
||||
if index != 1 { // Index is written as i+1
|
||||
t.Errorf("Expected index 1, got %d", index)
|
||||
}
|
||||
if equipType != 5 {
|
||||
t.Errorf("Expected equipType 5, got %d", equipType)
|
||||
}
|
||||
if itemID != 1000 {
|
||||
t.Errorf("Expected itemID 1000, got %d", itemID)
|
||||
}
|
||||
if len(data) != 56 {
|
||||
t.Errorf("Expected 56 bytes data, got %d", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxToBytes_MultipleEquipStacks(t *testing.T) {
|
||||
equipData := make([]byte, 56)
|
||||
|
||||
stacks := []mhfpacket.WarehouseStack{
|
||||
{ID: 1, Index: 0, EquipType: 1, ItemID: 100, Data: equipData},
|
||||
{ID: 2, Index: 1, EquipType: 2, ItemID: 200, Data: equipData},
|
||||
}
|
||||
result := boxToBytes(stacks, "equip")
|
||||
|
||||
bf := byteframe.NewByteFrameFromBytes(result)
|
||||
numStacks := bf.ReadUint16()
|
||||
if numStacks != 2 {
|
||||
t.Errorf("Expected 2 stacks, got %d", numStacks)
|
||||
}
|
||||
}
|
||||
|
||||
// Test HouseData struct
|
||||
func TestHouseDataStruct(t *testing.T) {
|
||||
house := HouseData{
|
||||
CharID: 12345,
|
||||
HRP: 999,
|
||||
GR: 500,
|
||||
Name: "TestPlayer",
|
||||
HouseState: 2,
|
||||
HousePassword: "pass123",
|
||||
}
|
||||
|
||||
if house.CharID != 12345 {
|
||||
t.Errorf("CharID = %d, want 12345", house.CharID)
|
||||
}
|
||||
if house.HRP != 999 {
|
||||
t.Errorf("HRP = %d, want 999", house.HRP)
|
||||
}
|
||||
if house.GR != 500 {
|
||||
t.Errorf("GR = %d, want 500", house.GR)
|
||||
}
|
||||
if house.Name != "TestPlayer" {
|
||||
t.Errorf("Name = %s, want TestPlayer", house.Name)
|
||||
}
|
||||
if house.HouseState != 2 {
|
||||
t.Errorf("HouseState = %d, want 2", house.HouseState)
|
||||
}
|
||||
if house.HousePassword != "pass123" {
|
||||
t.Errorf("HousePassword = %s, want pass123", house.HousePassword)
|
||||
}
|
||||
}
|
||||
|
||||
// Test Title struct
|
||||
func TestTitleStruct(t *testing.T) {
|
||||
title := Title{
|
||||
ID: 42,
|
||||
}
|
||||
|
||||
if title.ID != 42 {
|
||||
t.Errorf("ID = %d, want 42", title.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test decoMyset constants
|
||||
func TestDecoMysetConstants(t *testing.T) {
|
||||
if maxDecoMysets != 40 {
|
||||
t.Errorf("maxDecoMysets = %d, want 40", maxDecoMysets)
|
||||
}
|
||||
if decoMysetSize != 78 {
|
||||
t.Errorf("decoMysetSize = %d, want 78", decoMysetSize)
|
||||
}
|
||||
}
|
||||
83
server/channelserver/handlers_mail_test.go
Normal file
83
server/channelserver/handlers_mail_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMailStruct(t *testing.T) {
|
||||
mail := Mail{
|
||||
ID: 123,
|
||||
SenderID: 1000,
|
||||
RecipientID: 2000,
|
||||
Subject: "Test Subject",
|
||||
Body: "Test Body Content",
|
||||
Read: false,
|
||||
Deleted: false,
|
||||
Locked: true,
|
||||
AttachedItemReceived: false,
|
||||
AttachedItemID: 500,
|
||||
AttachedItemAmount: 10,
|
||||
CreatedAt: time.Now(),
|
||||
IsGuildInvite: false,
|
||||
IsSystemMessage: true,
|
||||
SenderName: "TestSender",
|
||||
}
|
||||
|
||||
if mail.ID != 123 {
|
||||
t.Errorf("ID = %d, want 123", mail.ID)
|
||||
}
|
||||
if mail.SenderID != 1000 {
|
||||
t.Errorf("SenderID = %d, want 1000", mail.SenderID)
|
||||
}
|
||||
if mail.RecipientID != 2000 {
|
||||
t.Errorf("RecipientID = %d, want 2000", mail.RecipientID)
|
||||
}
|
||||
if mail.Subject != "Test Subject" {
|
||||
t.Errorf("Subject = %s, want 'Test Subject'", mail.Subject)
|
||||
}
|
||||
if mail.Body != "Test Body Content" {
|
||||
t.Errorf("Body = %s, want 'Test Body Content'", mail.Body)
|
||||
}
|
||||
if mail.Read {
|
||||
t.Error("Read should be false")
|
||||
}
|
||||
if mail.Deleted {
|
||||
t.Error("Deleted should be false")
|
||||
}
|
||||
if !mail.Locked {
|
||||
t.Error("Locked should be true")
|
||||
}
|
||||
if mail.AttachedItemReceived {
|
||||
t.Error("AttachedItemReceived should be false")
|
||||
}
|
||||
if mail.AttachedItemID != 500 {
|
||||
t.Errorf("AttachedItemID = %d, want 500", mail.AttachedItemID)
|
||||
}
|
||||
if mail.AttachedItemAmount != 10 {
|
||||
t.Errorf("AttachedItemAmount = %d, want 10", mail.AttachedItemAmount)
|
||||
}
|
||||
if mail.IsGuildInvite {
|
||||
t.Error("IsGuildInvite should be false")
|
||||
}
|
||||
if !mail.IsSystemMessage {
|
||||
t.Error("IsSystemMessage should be true")
|
||||
}
|
||||
if mail.SenderName != "TestSender" {
|
||||
t.Errorf("SenderName = %s, want 'TestSender'", mail.SenderName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMailStruct_DefaultValues(t *testing.T) {
|
||||
mail := Mail{}
|
||||
|
||||
if mail.ID != 0 {
|
||||
t.Errorf("Default ID should be 0, got %d", mail.ID)
|
||||
}
|
||||
if mail.Subject != "" {
|
||||
t.Errorf("Default Subject should be empty, got %s", mail.Subject)
|
||||
}
|
||||
if mail.Read {
|
||||
t.Error("Default Read should be false")
|
||||
}
|
||||
}
|
||||
27
server/channelserver/handlers_mercenary_test.go
Normal file
27
server/channelserver/handlers_mercenary_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func TestHandleMsgMhfLoadLegendDispatch(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgMhfLoadLegendDispatch{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgMhfLoadLegendDispatch(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")
|
||||
}
|
||||
}
|
||||
@@ -26,3 +26,28 @@ func TestHandleMsgMhfGetRengokuRankingRank(t *testing.T) {
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRengokuScoreStruct(t *testing.T) {
|
||||
score := RengokuScore{
|
||||
Name: "TestPlayer",
|
||||
Score: 12345,
|
||||
}
|
||||
|
||||
if score.Name != "TestPlayer" {
|
||||
t.Errorf("Name = %s, want TestPlayer", score.Name)
|
||||
}
|
||||
if score.Score != 12345 {
|
||||
t.Errorf("Score = %d, want 12345", score.Score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRengokuScoreStruct_DefaultValues(t *testing.T) {
|
||||
score := RengokuScore{}
|
||||
|
||||
if score.Name != "" {
|
||||
t.Errorf("Default Name should be empty, got %s", score.Name)
|
||||
}
|
||||
if score.Score != 0 {
|
||||
t.Errorf("Default Score should be 0, got %d", score.Score)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,21 @@ func TestEmptyReserveHandlers(t *testing.T) {
|
||||
{"handleMsgSysReserve18F", handleMsgSysReserve18F},
|
||||
{"handleMsgSysReserve19E", handleMsgSysReserve19E},
|
||||
{"handleMsgSysReserve19F", handleMsgSysReserve19F},
|
||||
{"handleMsgSysReserve1A4", handleMsgSysReserve1A4},
|
||||
{"handleMsgSysReserve1A6", handleMsgSysReserve1A6},
|
||||
{"handleMsgSysReserve1A7", handleMsgSysReserve1A7},
|
||||
{"handleMsgSysReserve1A8", handleMsgSysReserve1A8},
|
||||
{"handleMsgSysReserve1A9", handleMsgSysReserve1A9},
|
||||
{"handleMsgSysReserve1AA", handleMsgSysReserve1AA},
|
||||
{"handleMsgSysReserve1AB", handleMsgSysReserve1AB},
|
||||
{"handleMsgSysReserve1AC", handleMsgSysReserve1AC},
|
||||
{"handleMsgSysReserve1AD", handleMsgSysReserve1AD},
|
||||
{"handleMsgSysReserve1AE", handleMsgSysReserve1AE},
|
||||
{"handleMsgSysReserve1AF", handleMsgSysReserve1AF},
|
||||
{"handleMsgSysReserve19B", handleMsgSysReserve19B},
|
||||
{"handleMsgSysReserve192", handleMsgSysReserve192},
|
||||
{"handleMsgSysReserve193", handleMsgSysReserve193},
|
||||
{"handleMsgSysReserve194", handleMsgSysReserve194},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
232
server/channelserver/handlers_shop_gacha_test.go
Normal file
232
server/channelserver/handlers_shop_gacha_test.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
)
|
||||
|
||||
func TestWriteShopItems_Empty(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := []ShopItem{}
|
||||
|
||||
writeShopItems(bf, items)
|
||||
|
||||
result := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||
count1 := result.ReadUint16()
|
||||
count2 := result.ReadUint16()
|
||||
|
||||
if count1 != 0 {
|
||||
t.Errorf("Expected first count 0, got %d", count1)
|
||||
}
|
||||
if count2 != 0 {
|
||||
t.Errorf("Expected second count 0, got %d", count2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteShopItems_SingleItem(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := []ShopItem{
|
||||
{
|
||||
ID: 1,
|
||||
ItemID: 100,
|
||||
Cost: 500,
|
||||
Quantity: 10,
|
||||
MinHR: 1,
|
||||
MinSR: 0,
|
||||
MinGR: 0,
|
||||
StoreLevel: 1,
|
||||
MaxQuantity: 99,
|
||||
UsedQuantity: 5,
|
||||
RoadFloors: 0,
|
||||
RoadFatalis: 0,
|
||||
},
|
||||
}
|
||||
|
||||
writeShopItems(bf, items)
|
||||
|
||||
result := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||
count1 := result.ReadUint16()
|
||||
count2 := result.ReadUint16()
|
||||
|
||||
if count1 != 1 {
|
||||
t.Errorf("Expected first count 1, got %d", count1)
|
||||
}
|
||||
if count2 != 1 {
|
||||
t.Errorf("Expected second count 1, got %d", count2)
|
||||
}
|
||||
|
||||
// Read the item data
|
||||
id := result.ReadUint32()
|
||||
_ = result.ReadUint16() // padding
|
||||
itemID := result.ReadUint16()
|
||||
cost := result.ReadUint32()
|
||||
quantity := result.ReadUint16()
|
||||
minHR := result.ReadUint16()
|
||||
minSR := result.ReadUint16()
|
||||
minGR := result.ReadUint16()
|
||||
storeLevel := result.ReadUint16()
|
||||
maxQuantity := result.ReadUint16()
|
||||
usedQuantity := result.ReadUint16()
|
||||
roadFloors := result.ReadUint16()
|
||||
roadFatalis := result.ReadUint16()
|
||||
|
||||
if id != 1 {
|
||||
t.Errorf("Expected ID 1, got %d", id)
|
||||
}
|
||||
if itemID != 100 {
|
||||
t.Errorf("Expected itemID 100, got %d", itemID)
|
||||
}
|
||||
if cost != 500 {
|
||||
t.Errorf("Expected cost 500, got %d", cost)
|
||||
}
|
||||
if quantity != 10 {
|
||||
t.Errorf("Expected quantity 10, got %d", quantity)
|
||||
}
|
||||
if minHR != 1 {
|
||||
t.Errorf("Expected minHR 1, got %d", minHR)
|
||||
}
|
||||
if minSR != 0 {
|
||||
t.Errorf("Expected minSR 0, got %d", minSR)
|
||||
}
|
||||
if minGR != 0 {
|
||||
t.Errorf("Expected minGR 0, got %d", minGR)
|
||||
}
|
||||
if storeLevel != 1 {
|
||||
t.Errorf("Expected storeLevel 1, got %d", storeLevel)
|
||||
}
|
||||
if maxQuantity != 99 {
|
||||
t.Errorf("Expected maxQuantity 99, got %d", maxQuantity)
|
||||
}
|
||||
if usedQuantity != 5 {
|
||||
t.Errorf("Expected usedQuantity 5, got %d", usedQuantity)
|
||||
}
|
||||
if roadFloors != 0 {
|
||||
t.Errorf("Expected roadFloors 0, got %d", roadFloors)
|
||||
}
|
||||
if roadFatalis != 0 {
|
||||
t.Errorf("Expected roadFatalis 0, got %d", roadFatalis)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteShopItems_MultipleItems(t *testing.T) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
items := []ShopItem{
|
||||
{ID: 1, ItemID: 100, Cost: 500, Quantity: 10},
|
||||
{ID: 2, ItemID: 200, Cost: 1000, Quantity: 5},
|
||||
{ID: 3, ItemID: 300, Cost: 2000, Quantity: 1},
|
||||
}
|
||||
|
||||
writeShopItems(bf, items)
|
||||
|
||||
result := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||
count1 := result.ReadUint16()
|
||||
count2 := result.ReadUint16()
|
||||
|
||||
if count1 != 3 {
|
||||
t.Errorf("Expected first count 3, got %d", count1)
|
||||
}
|
||||
if count2 != 3 {
|
||||
t.Errorf("Expected second count 3, got %d", count2)
|
||||
}
|
||||
}
|
||||
|
||||
// Test struct definitions
|
||||
func TestShopItemStruct(t *testing.T) {
|
||||
item := ShopItem{
|
||||
ID: 42,
|
||||
ItemID: 1234,
|
||||
Cost: 9999,
|
||||
Quantity: 50,
|
||||
MinHR: 10,
|
||||
MinSR: 5,
|
||||
MinGR: 100,
|
||||
StoreLevel: 3,
|
||||
MaxQuantity: 99,
|
||||
UsedQuantity: 10,
|
||||
RoadFloors: 50,
|
||||
RoadFatalis: 25,
|
||||
}
|
||||
|
||||
if item.ID != 42 {
|
||||
t.Errorf("ID = %d, want 42", item.ID)
|
||||
}
|
||||
if item.ItemID != 1234 {
|
||||
t.Errorf("ItemID = %d, want 1234", item.ItemID)
|
||||
}
|
||||
if item.Cost != 9999 {
|
||||
t.Errorf("Cost = %d, want 9999", item.Cost)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaStruct(t *testing.T) {
|
||||
gacha := Gacha{
|
||||
ID: 1,
|
||||
MinGR: 100,
|
||||
MinHR: 999,
|
||||
Name: "Test Gacha",
|
||||
URLBanner: "http://example.com/banner.png",
|
||||
URLFeature: "http://example.com/feature.png",
|
||||
URLThumbnail: "http://example.com/thumb.png",
|
||||
Wide: true,
|
||||
Recommended: true,
|
||||
GachaType: 2,
|
||||
Hidden: false,
|
||||
}
|
||||
|
||||
if gacha.ID != 1 {
|
||||
t.Errorf("ID = %d, want 1", gacha.ID)
|
||||
}
|
||||
if gacha.Name != "Test Gacha" {
|
||||
t.Errorf("Name = %s, want Test Gacha", gacha.Name)
|
||||
}
|
||||
if !gacha.Wide {
|
||||
t.Error("Wide should be true")
|
||||
}
|
||||
if !gacha.Recommended {
|
||||
t.Error("Recommended should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaEntryStruct(t *testing.T) {
|
||||
entry := GachaEntry{
|
||||
EntryType: 1,
|
||||
ID: 100,
|
||||
ItemType: 0,
|
||||
ItemNumber: 1234,
|
||||
ItemQuantity: 10,
|
||||
Weight: 0.5,
|
||||
Rarity: 3,
|
||||
Rolls: 1,
|
||||
FrontierPoints: 500,
|
||||
DailyLimit: 5,
|
||||
}
|
||||
|
||||
if entry.EntryType != 1 {
|
||||
t.Errorf("EntryType = %d, want 1", entry.EntryType)
|
||||
}
|
||||
if entry.ID != 100 {
|
||||
t.Errorf("ID = %d, want 100", entry.ID)
|
||||
}
|
||||
if entry.Weight != 0.5 {
|
||||
t.Errorf("Weight = %f, want 0.5", entry.Weight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGachaItemStruct(t *testing.T) {
|
||||
item := GachaItem{
|
||||
ItemType: 0,
|
||||
ItemID: 5678,
|
||||
Quantity: 20,
|
||||
}
|
||||
|
||||
if item.ItemType != 0 {
|
||||
t.Errorf("ItemType = %d, want 0", item.ItemType)
|
||||
}
|
||||
if item.ItemID != 5678 {
|
||||
t.Errorf("ItemID = %d, want 5678", item.ItemID)
|
||||
}
|
||||
if item.Quantity != 20 {
|
||||
t.Errorf("Quantity = %d, want 20", item.Quantity)
|
||||
}
|
||||
}
|
||||
@@ -200,3 +200,115 @@ func TestSessionQueueSendNonBlocking_FullQueue(t *testing.T) {
|
||||
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},
|
||||
{"handleMsgCaExchangeItem", handleMsgCaExchangeItem},
|
||||
{"handleMsgMhfServerCommand", handleMsgMhfServerCommand},
|
||||
{"handleMsgMhfSetLoginwindow", handleMsgMhfSetLoginwindow},
|
||||
{"handleMsgSysTransBinary", handleMsgSysTransBinary},
|
||||
{"handleMsgSysCollectBinary", handleMsgSysCollectBinary},
|
||||
{"handleMsgSysGetState", handleMsgSysGetState},
|
||||
{"handleMsgSysSerialize", handleMsgSysSerialize},
|
||||
{"handleMsgSysEnumlobby", handleMsgSysEnumlobby},
|
||||
{"handleMsgSysEnumuser", handleMsgSysEnumuser},
|
||||
{"handleMsgSysInfokyserver", handleMsgSysInfokyserver},
|
||||
{"handleMsgMhfGetCaUniqueID", handleMsgMhfGetCaUniqueID},
|
||||
{"handleMsgMhfEnumerateItem", handleMsgMhfEnumerateItem},
|
||||
{"handleMsgMhfAcquireItem", handleMsgMhfAcquireItem},
|
||||
{"handleMsgMhfGetExtraInfo", handleMsgMhfGetExtraInfo},
|
||||
{"handleMsgMhfGetCogInfo", handleMsgMhfGetCogInfo},
|
||||
{"handleMsgMhfStampcardPrize", handleMsgMhfStampcardPrize},
|
||||
{"handleMsgMhfUnreserveSrg", handleMsgMhfUnreserveSrg},
|
||||
{"handleMsgMhfKickExportForce", handleMsgMhfKickExportForce},
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
// TestWaitStageBinaryInfiniteLoopRisk documents the infinite loop risk in handleMsgSysWaitStageBinary.
|
||||
@@ -268,3 +270,582 @@ func TestWaitStageBinaryTimeoutDuration(t *testing.T) {
|
||||
t.Logf("After fix, WaitStageBinary will timeout after %v (%d iterations * %v sleep)",
|
||||
expectedTimeout, maxIterations, sleepDuration)
|
||||
}
|
||||
|
||||
// TestHandleMsgSysCreateStage_NewStage tests creating a new stage
|
||||
func TestHandleMsgSysCreateStage_NewStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysCreateStage{
|
||||
AckHandle: 12345,
|
||||
StageID: "test_create_stage",
|
||||
PlayerCount: 4,
|
||||
}
|
||||
|
||||
handleMsgSysCreateStage(session, pkt)
|
||||
|
||||
// Verify stage was created
|
||||
server.Lock()
|
||||
stage, exists := server.stages["test_create_stage"]
|
||||
server.Unlock()
|
||||
|
||||
if !exists {
|
||||
t.Error("Stage should be created")
|
||||
}
|
||||
if stage.maxPlayers != 4 {
|
||||
t.Errorf("stage.maxPlayers = %d, want 4", stage.maxPlayers)
|
||||
}
|
||||
if stage.host != session {
|
||||
t.Error("Session should be host of the stage")
|
||||
}
|
||||
|
||||
// Verify response packet was queued
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if len(p.data) == 0 {
|
||||
t.Error("Response packet should have data")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysCreateStage_ExistingStage tests creating a stage that already exists
|
||||
func TestHandleMsgSysCreateStage_ExistingStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create existing stage
|
||||
existingStage := NewStage("existing_stage")
|
||||
server.stages["existing_stage"] = existingStage
|
||||
|
||||
pkt := &mhfpacket.MsgSysCreateStage{
|
||||
AckHandle: 12345,
|
||||
StageID: "existing_stage",
|
||||
PlayerCount: 4,
|
||||
}
|
||||
|
||||
handleMsgSysCreateStage(session, pkt)
|
||||
|
||||
// Verify response packet was queued (should be failure)
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if len(p.data) == 0 {
|
||||
t.Error("Response packet should have data")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoStageTransfer_NewStage tests entering a stage that doesn't exist
|
||||
func TestDoStageTransfer_NewStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
doStageTransfer(session, 12345, "new_transfer_stage")
|
||||
|
||||
// Verify stage was created
|
||||
server.Lock()
|
||||
stage, exists := server.stages["new_transfer_stage"]
|
||||
server.Unlock()
|
||||
|
||||
if !exists {
|
||||
t.Error("Stage should be created")
|
||||
}
|
||||
|
||||
// Verify session is in the stage
|
||||
stage.RLock()
|
||||
_, inStage := stage.clients[session]
|
||||
stage.RUnlock()
|
||||
|
||||
if !inStage {
|
||||
t.Error("Session should be in the stage")
|
||||
}
|
||||
|
||||
// Verify session's stage reference is set
|
||||
if session.stage != stage {
|
||||
t.Error("Session's stage reference should be set")
|
||||
}
|
||||
|
||||
// Verify response packets were queued
|
||||
packetCount := 0
|
||||
for {
|
||||
select {
|
||||
case <-session.sendPackets:
|
||||
packetCount++
|
||||
default:
|
||||
goto done
|
||||
}
|
||||
}
|
||||
done:
|
||||
if packetCount < 2 {
|
||||
t.Errorf("Expected at least 2 packets (cleanup + ack), got %d", packetCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoStageTransfer_ExistingStage tests entering an existing stage
|
||||
func TestDoStageTransfer_ExistingStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create existing stage
|
||||
existingStage := NewStage("existing_transfer_stage")
|
||||
server.stages["existing_transfer_stage"] = existingStage
|
||||
|
||||
doStageTransfer(session, 12345, "existing_transfer_stage")
|
||||
|
||||
// Verify session is in the stage
|
||||
existingStage.RLock()
|
||||
_, inStage := existingStage.clients[session]
|
||||
existingStage.RUnlock()
|
||||
|
||||
if !inStage {
|
||||
t.Error("Session should be in the stage")
|
||||
}
|
||||
|
||||
// Verify response packets were queued
|
||||
packetCount := 0
|
||||
for {
|
||||
select {
|
||||
case <-session.sendPackets:
|
||||
packetCount++
|
||||
default:
|
||||
goto done
|
||||
}
|
||||
}
|
||||
done:
|
||||
if packetCount < 2 {
|
||||
t.Errorf("Expected at least 2 packets, got %d", packetCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysStageDestruct tests the empty handler
|
||||
func TestHandleMsgSysStageDestruct(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Should not panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysStageDestruct panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysStageDestruct(session, nil)
|
||||
}
|
||||
|
||||
// TestHandleMsgSysLockStage tests the lock stage handler
|
||||
func TestHandleMsgSysLockStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysLockStage{
|
||||
AckHandle: 12345,
|
||||
}
|
||||
|
||||
handleMsgSysLockStage(session, pkt)
|
||||
|
||||
// Verify response packet was queued
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if len(p.data) == 0 {
|
||||
t.Error("Response packet should have data")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysLeaveStage tests the empty handler
|
||||
func TestHandleMsgSysLeaveStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Should not panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("handleMsgSysLeaveStage panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
handleMsgSysLeaveStage(session, nil)
|
||||
}
|
||||
|
||||
// TestDestructEmptyStages tests the stage cleanup function
|
||||
func TestDestructEmptyStages(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create different types of stages
|
||||
questStage := NewStage("00XQsStage1") // Quest stage
|
||||
myStage := NewStage("00XMsStage1") // My series stage
|
||||
guildStage := NewStage("00XGsStage1") // Guild stage
|
||||
townStage := NewStage("00XTwStage1") // Town stage (should not be deleted)
|
||||
|
||||
server.stages["00XQsStage1"] = questStage
|
||||
server.stages["00XMsStage1"] = myStage
|
||||
server.stages["00XGsStage1"] = guildStage
|
||||
server.stages["00XTwStage1"] = townStage
|
||||
|
||||
destructEmptyStages(session)
|
||||
|
||||
// Quest/My/Guild stages should be deleted (empty)
|
||||
if _, exists := server.stages["00XQsStage1"]; exists {
|
||||
t.Error("Empty quest stage should be deleted")
|
||||
}
|
||||
if _, exists := server.stages["00XMsStage1"]; exists {
|
||||
t.Error("Empty my series stage should be deleted")
|
||||
}
|
||||
if _, exists := server.stages["00XGsStage1"]; exists {
|
||||
t.Error("Empty guild stage should be deleted")
|
||||
}
|
||||
|
||||
// Town stage should remain
|
||||
if _, exists := server.stages["00XTwStage1"]; !exists {
|
||||
t.Error("Town stage should not be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDestructEmptyStages_NonEmpty tests that non-empty stages are preserved
|
||||
func TestDestructEmptyStages_NonEmpty(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create quest stage with clients
|
||||
questStage := NewStage("00XQsStage2")
|
||||
questStage.clients[session] = session.charID
|
||||
server.stages["00XQsStage2"] = questStage
|
||||
|
||||
destructEmptyStages(session)
|
||||
|
||||
// Stage with clients should not be deleted
|
||||
if _, exists := server.stages["00XQsStage2"]; !exists {
|
||||
t.Error("Non-empty quest stage should not be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDestructEmptyStages_WithReservations tests that stages with reservations are preserved
|
||||
func TestDestructEmptyStages_WithReservations(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create quest stage with reservations
|
||||
questStage := NewStage("00XQsStage3")
|
||||
questStage.reservedClientSlots[session.charID] = true
|
||||
server.stages["00XQsStage3"] = questStage
|
||||
|
||||
destructEmptyStages(session)
|
||||
|
||||
// Stage with reservations should not be deleted
|
||||
if _, exists := server.stages["00XQsStage3"]; !exists {
|
||||
t.Error("Quest stage with reservations should not be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveSessionFromStage tests removing a session from its stage
|
||||
func TestRemoveSessionFromStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage and add session to it
|
||||
stage := NewStage("00XTwRemove1")
|
||||
stage.clients[session] = session.charID
|
||||
session.stage = stage
|
||||
server.stages["00XTwRemove1"] = stage
|
||||
|
||||
// Verify session is in stage
|
||||
if _, exists := stage.clients[session]; !exists {
|
||||
t.Error("Session should be in stage before removal")
|
||||
}
|
||||
|
||||
removeSessionFromStage(session)
|
||||
|
||||
// Verify session is removed from stage
|
||||
if _, exists := stage.clients[session]; exists {
|
||||
t.Error("Session should be removed from stage")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveSessionFromStage_WithObjects tests removing objects when leaving stage
|
||||
func TestRemoveSessionFromStage_WithObjects(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage and add session with objects
|
||||
stage := NewStage("00XTwRemove2")
|
||||
stage.clients[session] = session.charID
|
||||
stage.objects[session.charID] = &Object{
|
||||
id: 1,
|
||||
ownerCharID: session.charID,
|
||||
x: 100.0,
|
||||
y: 200.0,
|
||||
z: 300.0,
|
||||
}
|
||||
session.stage = stage
|
||||
server.stages["00XTwRemove2"] = stage
|
||||
|
||||
removeSessionFromStage(session)
|
||||
|
||||
// Verify objects owned by session are removed
|
||||
if _, exists := stage.objects[session.charID]; exists {
|
||||
t.Error("Objects owned by session should be removed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysSetStageBinary tests setting stage binary data
|
||||
func TestHandleMsgSysSetStageBinary(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage
|
||||
stage := NewStage("test_binary_stage")
|
||||
server.stages["test_binary_stage"] = stage
|
||||
|
||||
pkt := &mhfpacket.MsgSysSetStageBinary{
|
||||
StageID: "test_binary_stage",
|
||||
BinaryType0: 1,
|
||||
BinaryType1: 2,
|
||||
RawDataPayload: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
||||
}
|
||||
|
||||
handleMsgSysSetStageBinary(session, pkt)
|
||||
|
||||
// Verify binary was stored
|
||||
stage.Lock()
|
||||
data, exists := stage.rawBinaryData[stageBinaryKey{1, 2}]
|
||||
stage.Unlock()
|
||||
|
||||
if !exists {
|
||||
t.Error("Binary data should be stored")
|
||||
}
|
||||
if len(data) != 4 {
|
||||
t.Errorf("Binary data length = %d, want 4", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysGetStageBinary tests getting stage binary data
|
||||
func TestHandleMsgSysGetStageBinary_WithData(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage with binary data
|
||||
stage := NewStage("test_get_binary")
|
||||
stage.rawBinaryData[stageBinaryKey{1, 2}] = []byte{0x01, 0x02, 0x03, 0x04}
|
||||
server.stages["test_get_binary"] = stage
|
||||
|
||||
pkt := &mhfpacket.MsgSysGetStageBinary{
|
||||
AckHandle: 12345,
|
||||
StageID: "test_get_binary",
|
||||
BinaryType0: 1,
|
||||
BinaryType1: 2,
|
||||
}
|
||||
|
||||
handleMsgSysGetStageBinary(session, pkt)
|
||||
|
||||
// Verify response packet was queued
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if len(p.data) == 0 {
|
||||
t.Error("Response packet should have data")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysGetStageBinary_NoData tests getting non-existent binary data
|
||||
func TestHandleMsgSysGetStageBinary_NoData(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage without the requested binary data
|
||||
stage := NewStage("test_no_binary")
|
||||
server.stages["test_no_binary"] = stage
|
||||
|
||||
pkt := &mhfpacket.MsgSysGetStageBinary{
|
||||
AckHandle: 12345,
|
||||
StageID: "test_no_binary",
|
||||
BinaryType0: 1,
|
||||
BinaryType1: 2,
|
||||
}
|
||||
|
||||
handleMsgSysGetStageBinary(session, pkt)
|
||||
|
||||
// Should still return a response (empty)
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if p.data == nil {
|
||||
t.Error("Response packet should not be nil")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysGetStageBinary_Type4 tests special type 4 binary
|
||||
func TestHandleMsgSysGetStageBinary_Type4(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage
|
||||
stage := NewStage("test_type4_binary")
|
||||
server.stages["test_type4_binary"] = stage
|
||||
|
||||
pkt := &mhfpacket.MsgSysGetStageBinary{
|
||||
AckHandle: 12345,
|
||||
StageID: "test_type4_binary",
|
||||
BinaryType0: 0,
|
||||
BinaryType1: 4,
|
||||
}
|
||||
|
||||
handleMsgSysGetStageBinary(session, pkt)
|
||||
|
||||
// Should return empty response for type 4
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if p.data == nil {
|
||||
t.Error("Response packet should not be nil")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysSetStagePass tests setting stage password
|
||||
func TestHandleMsgSysSetStagePass_WithReservation(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage and add session reservation
|
||||
stage := NewStage("test_pass_stage")
|
||||
stage.reservedClientSlots[session.charID] = true
|
||||
server.stages["test_pass_stage"] = stage
|
||||
session.reservationStage = stage
|
||||
|
||||
pkt := &mhfpacket.MsgSysSetStagePass{
|
||||
Password: "secret123",
|
||||
}
|
||||
|
||||
handleMsgSysSetStagePass(session, pkt)
|
||||
|
||||
// Verify password was set
|
||||
stage.Lock()
|
||||
password := stage.password
|
||||
stage.Unlock()
|
||||
|
||||
if password != "secret123" {
|
||||
t.Errorf("Stage password = %s, want secret123", password)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysSetStagePass_NoReservation tests setting pass without reservation
|
||||
func TestHandleMsgSysSetStagePass_NoReservation(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysSetStagePass{
|
||||
Password: "secret456",
|
||||
}
|
||||
|
||||
handleMsgSysSetStagePass(session, pkt)
|
||||
|
||||
// Verify password was stored in session for later use
|
||||
session.Lock()
|
||||
password := session.stagePass
|
||||
session.Unlock()
|
||||
|
||||
if password != "secret456" {
|
||||
t.Errorf("Session stagePass = %s, want secret456", password)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysEnumerateStage tests enumerating stages
|
||||
func TestHandleMsgSysEnumerateStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create some stages
|
||||
stage1 := NewStage("00XQsStage1")
|
||||
stage1.reservedClientSlots[100] = true
|
||||
stage1.maxPlayers = 4
|
||||
|
||||
stage2 := NewStage("00XQsStage2")
|
||||
stage2.clients[session] = session.charID
|
||||
stage2.maxPlayers = 2
|
||||
|
||||
server.stages["00XQsStage1"] = stage1
|
||||
server.stages["00XQsStage2"] = stage2
|
||||
|
||||
pkt := &mhfpacket.MsgSysEnumerateStage{
|
||||
AckHandle: 12345,
|
||||
StagePrefix: "Qs",
|
||||
}
|
||||
|
||||
handleMsgSysEnumerateStage(session, pkt)
|
||||
|
||||
// Verify response packet was queued
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if len(p.data) == 0 {
|
||||
t.Error("Response packet should have data")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysEnumerateStage_Empty tests enumerating with no matching stages
|
||||
func TestHandleMsgSysEnumerateStage_Empty(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
pkt := &mhfpacket.MsgSysEnumerateStage{
|
||||
AckHandle: 12345,
|
||||
StagePrefix: "NonExistent",
|
||||
}
|
||||
|
||||
handleMsgSysEnumerateStage(session, pkt)
|
||||
|
||||
// Should still return a response
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if p.data == nil {
|
||||
t.Error("Response packet should not be nil")
|
||||
}
|
||||
default:
|
||||
t.Error("No response packet queued")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleMsgSysUnreserveStage tests unreserving a stage
|
||||
func TestHandleMsgSysUnreserveStage(t *testing.T) {
|
||||
server := createMockServer()
|
||||
session := createMockSession(1, server)
|
||||
|
||||
// Create a stage and add session reservation
|
||||
stage := NewStage("test_unreserve")
|
||||
stage.reservedClientSlots[session.charID] = true
|
||||
server.stages["test_unreserve"] = stage
|
||||
session.reservationStage = stage
|
||||
|
||||
handleMsgSysUnreserveStage(session, nil)
|
||||
|
||||
// Verify reservation was removed
|
||||
stage.Lock()
|
||||
_, exists := stage.reservedClientSlots[session.charID]
|
||||
stage.Unlock()
|
||||
|
||||
if exists {
|
||||
t.Error("Reservation should be removed")
|
||||
}
|
||||
|
||||
// Verify session's reservation stage is cleared
|
||||
session.Lock()
|
||||
reservationStage := session.reservationStage
|
||||
session.Unlock()
|
||||
|
||||
if reservationStage != nil {
|
||||
t.Error("Session's reservation stage should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -482,3 +483,265 @@ func TestConfigStruct(t *testing.T) {
|
||||
t.Error("Config Enable should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerBroadcastMHF_NoSessions(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Should not panic with no sessions
|
||||
pkt := &mhfpacket.MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0,
|
||||
AckData: []byte{0x00},
|
||||
}
|
||||
|
||||
s.BroadcastMHF(pkt, nil)
|
||||
}
|
||||
|
||||
func TestServerBroadcastMHF_WithSession(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
session := createMockSession(1, s)
|
||||
s.sessions[nil] = session
|
||||
|
||||
pkt := &mhfpacket.MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0,
|
||||
AckData: []byte{0x00},
|
||||
}
|
||||
|
||||
s.BroadcastMHF(pkt, nil)
|
||||
|
||||
// Check if packet was queued
|
||||
select {
|
||||
case p := <-session.sendPackets:
|
||||
if p.data == nil {
|
||||
t.Error("Packet data should not be nil")
|
||||
}
|
||||
default:
|
||||
t.Error("No packet queued to session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerBroadcastMHF_SkipsOrigin(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
session1 := createMockSession(1, s)
|
||||
session2 := createMockSession(2, s)
|
||||
s.sessions[nil] = session1
|
||||
s.sessions[session2.rawConn] = session2
|
||||
|
||||
pkt := &mhfpacket.MsgSysAck{
|
||||
AckHandle: 1,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0,
|
||||
AckData: []byte{0x00},
|
||||
}
|
||||
|
||||
// Broadcast from session1 - should skip session1
|
||||
s.BroadcastMHF(pkt, session1)
|
||||
|
||||
// session1 should not receive the packet
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
t.Error("Origin session should not receive broadcast")
|
||||
default:
|
||||
// Good - no packet for origin
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerFindSessionByCharID_Found(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
s.Channels = []*Server{s}
|
||||
|
||||
session := createMockSession(12345, s)
|
||||
s.sessions[nil] = session
|
||||
|
||||
// Search for existing character
|
||||
found := s.FindSessionByCharID(12345)
|
||||
if found == nil {
|
||||
t.Error("Expected to find session")
|
||||
}
|
||||
if found != session {
|
||||
t.Error("Found wrong session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerFindObjectByChar_Found(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Add a stage with an object
|
||||
stage := NewStage("test_obj_stage")
|
||||
obj := &Object{
|
||||
id: 1,
|
||||
ownerCharID: 12345,
|
||||
x: 100.0,
|
||||
y: 200.0,
|
||||
z: 300.0,
|
||||
}
|
||||
stage.objects[obj.ownerCharID] = obj
|
||||
s.stages["test_obj_stage"] = stage
|
||||
|
||||
// Search for existing object
|
||||
found := s.FindObjectByChar(12345)
|
||||
if found == nil {
|
||||
t.Error("Expected to find object")
|
||||
}
|
||||
if found != obj {
|
||||
t.Error("Found wrong object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConcurrentBinaryPartsAccess(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 10; j++ {
|
||||
partID := userBinaryPartID{charID: uint32(id), index: uint8(j % 4)}
|
||||
s.userBinaryPartsLock.Lock()
|
||||
s.userBinaryParts[partID] = []byte{byte(id), byte(j)}
|
||||
s.userBinaryPartsLock.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 20; j++ {
|
||||
s.userBinaryPartsLock.RLock()
|
||||
_ = len(s.userBinaryParts)
|
||||
s.userBinaryPartsLock.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestServerNextSemaphoreID(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Get multiple IDs and verify they're unique
|
||||
ids := make(map[uint32]bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
id := s.NextSemaphoreID()
|
||||
if ids[id] {
|
||||
t.Errorf("Duplicate semaphore ID: %d", id)
|
||||
}
|
||||
ids[id] = true
|
||||
|
||||
// IDs should be >= 7 (reserved indexes skipped)
|
||||
if id < 7 {
|
||||
t.Errorf("Semaphore ID %d should be >= 7", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerNextSemaphoreID_WrapAround(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Set semaphoreIndex to near max uint32 to test wrap-around
|
||||
s.semaphoreIndex = ^uint32(0) - 1 // Max uint32 - 1
|
||||
|
||||
id1 := s.NextSemaphoreID()
|
||||
id2 := s.NextSemaphoreID()
|
||||
|
||||
// After wrap-around, should skip to 7
|
||||
if id1 < 7 && id1 != 0 {
|
||||
t.Errorf("After wrap-around, first ID should be >= 7, got %d", id1)
|
||||
}
|
||||
|
||||
if id1 == id2 {
|
||||
t.Error("IDs should be different")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerNextSemaphoreID_SkipsExisting(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
cfg := &Config{
|
||||
ID: 1,
|
||||
Logger: logger,
|
||||
ErupeConfig: &config.Config{DevMode: true},
|
||||
}
|
||||
|
||||
s := NewServer(cfg)
|
||||
|
||||
// Pre-populate with some semaphores
|
||||
for i := uint32(7); i <= 15; i++ {
|
||||
s.semaphore["test_"+string(rune('a'+i))] = &Semaphore{id: i}
|
||||
}
|
||||
|
||||
// Get a new ID - should skip the existing ones
|
||||
id := s.NextSemaphoreID()
|
||||
|
||||
// Verify ID is not one of the existing ones
|
||||
for _, sema := range s.semaphore {
|
||||
if sema.id == id {
|
||||
t.Errorf("NextSemaphoreID returned existing ID: %d", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user