mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 17:43:21 +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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetAchData_Level0(t *testing.T) {
|
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")
|
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")
|
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")
|
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},
|
{"handleMsgSysReserve18F", handleMsgSysReserve18F},
|
||||||
{"handleMsgSysReserve19E", handleMsgSysReserve19E},
|
{"handleMsgSysReserve19E", handleMsgSysReserve19E},
|
||||||
{"handleMsgSysReserve19F", handleMsgSysReserve19F},
|
{"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 {
|
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")
|
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"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestWaitStageBinaryInfiniteLoopRisk documents the infinite loop risk in handleMsgSysWaitStageBinary.
|
// 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)",
|
t.Logf("After fix, WaitStageBinary will timeout after %v (%d iterations * %v sleep)",
|
||||||
expectedTimeout, maxIterations, sleepDuration)
|
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"
|
"time"
|
||||||
|
|
||||||
"erupe-ce/config"
|
"erupe-ce/config"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -482,3 +483,265 @@ func TestConfigStruct(t *testing.T) {
|
|||||||
t.Error("Config Enable should be true")
|
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