test: import 38 channelserver test files from v9.2.x-stable

Port test files from v9.2.x-stable branch to increase channelserver
coverage from 13.8% to 25.6% (556 tests passing).

Adapted all files to main's struct definitions: config import alias,
Airou/CatDefinition rename, packet field mismatches, Raviente struct
differences, and maxPlayers defaults. Removed tests referencing
production code not yet on main (Player, FestivalColour, etc.).
Excluded handlers_register_test.go (Raviente completely redesigned).
This commit is contained in:
Houmgaor
2026-02-16 22:19:44 +01:00
parent b1c8b2848f
commit be4cd2001c
39 changed files with 10200 additions and 0 deletions

View File

@@ -0,0 +1,454 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestGetAchData_Level0(t *testing.T) {
// Score 0 should give level 0 with progress toward first threshold
ach := GetAchData(0, 0)
if ach.Level != 0 {
t.Errorf("Level = %d, want 0", ach.Level)
}
if ach.Progress != 0 {
t.Errorf("Progress = %d, want 0", ach.Progress)
}
if ach.NextValue != 5 {
t.Errorf("NextValue = %d, want 5", ach.NextValue)
}
}
func TestGetAchData_Level1(t *testing.T) {
// Score 5 (exactly at first threshold) should give level 1
ach := GetAchData(0, 5)
if ach.Level != 1 {
t.Errorf("Level = %d, want 1", ach.Level)
}
if ach.Value != 5 {
t.Errorf("Value = %d, want 5", ach.Value)
}
}
func TestGetAchData_Partial(t *testing.T) {
// Score 3 should give level 0 with progress 3
ach := GetAchData(0, 3)
if ach.Level != 0 {
t.Errorf("Level = %d, want 0", ach.Level)
}
if ach.Progress != 3 {
t.Errorf("Progress = %d, want 3", ach.Progress)
}
if ach.Required != 5 {
t.Errorf("Required = %d, want 5", ach.Required)
}
}
func TestGetAchData_MaxLevel(t *testing.T) {
// Score 999 should give max level for curve 0
ach := GetAchData(0, 999)
if ach.Level != 8 {
t.Errorf("Level = %d, want 8", ach.Level)
}
if ach.Trophy != 0x7F {
t.Errorf("Trophy = %x, want 0x7F (gold)", ach.Trophy)
}
}
func TestGetAchData_BronzeTrophy(t *testing.T) {
// Level 7 should have bronze trophy (0x40)
// Curve 0: 5, 15, 30, 50, 100, 150, 200, 300
// Cumulative: 5, 20, 50, 100, 200, 350, 550, 850
// To reach level 7, need 550+ points (sum of first 7 thresholds)
ach := GetAchData(0, 550)
if ach.Level != 7 {
t.Errorf("Level = %d, want 7", ach.Level)
}
if ach.Trophy != 0x60 {
t.Errorf("Trophy = %x, want 0x60 (silver)", ach.Trophy)
}
}
func TestGetAchData_SilverTrophy(t *testing.T) {
// Level 8 (max) should have gold trophy (0x7F)
// Need 850+ (sum of all 8 thresholds) for max level
ach := GetAchData(0, 850)
if ach.Level != 8 {
t.Errorf("Level = %d, want 8", ach.Level)
}
if ach.Trophy != 0x7F {
t.Errorf("Trophy = %x, want 0x7F (gold)", ach.Trophy)
}
}
func TestGetAchData_DifferentCurves(t *testing.T) {
tests := []struct {
name string
id uint8
score int32
wantLvl uint8
wantProg uint32
}{
{"Curve1_ID7_Level0", 7, 0, 0, 0},
{"Curve1_ID7_Level1", 7, 1, 1, 0},
{"Curve2_ID8_Level0", 8, 0, 0, 0},
{"Curve2_ID8_Level1", 8, 1, 1, 0},
{"Curve3_ID16_Level0", 16, 0, 0, 0},
{"Curve3_ID16_Partial", 16, 5, 0, 5},
{"Curve3_ID16_Level1", 16, 10, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ach := GetAchData(tt.id, tt.score)
if ach.Level != tt.wantLvl {
t.Errorf("Level = %d, want %d", ach.Level, tt.wantLvl)
}
if ach.Progress != tt.wantProg {
t.Errorf("Progress = %d, want %d", ach.Progress, tt.wantProg)
}
})
}
}
func TestGetAchData_AllCurveMappings(t *testing.T) {
// Verify all achievement IDs have valid curve mappings
for id := uint8(0); id <= 32; id++ {
curve, ok := achievementCurveMap[id]
if !ok {
t.Errorf("Achievement ID %d has no curve mapping", id)
continue
}
if len(curve) != 8 {
t.Errorf("Achievement ID %d curve has %d elements, want 8", id, len(curve))
}
}
}
func TestGetAchData_ValueAccumulation(t *testing.T) {
// Test that Value correctly accumulates based on level
// Level values: 1=5, 2-4=10, 5-7=15, 8=20
// At max level 8: 5 + 10*3 + 15*3 + 20 = 5 + 30 + 45 + 20 = 100
ach := GetAchData(0, 1000) // Score well above max
expectedValue := uint32(5 + 10 + 10 + 10 + 15 + 15 + 15 + 20)
if ach.Value != expectedValue {
t.Errorf("Value = %d, want %d", ach.Value, expectedValue)
}
}
func TestGetAchData_NextValueByLevel(t *testing.T) {
tests := []struct {
level uint8
wantNext uint16
approxScore int32
}{
{0, 5, 0},
{1, 10, 5},
{2, 10, 15},
{3, 10, 30},
{4, 15, 50},
{5, 15, 100},
}
for _, tt := range tests {
t.Run("Level"+string(rune('0'+tt.level)), func(t *testing.T) {
ach := GetAchData(0, tt.approxScore)
if ach.Level != tt.level {
t.Skipf("Skipping: got level %d, expected %d", ach.Level, tt.level)
}
if ach.NextValue != tt.wantNext {
t.Errorf("NextValue at level %d = %d, want %d", ach.Level, ach.NextValue, tt.wantNext)
}
})
}
}
func TestAchievementCurves(t *testing.T) {
// Verify curve values are strictly increasing
for i, curve := range achievementCurves {
for j := 1; j < len(curve); j++ {
if curve[j] <= curve[j-1] {
t.Errorf("Curve %d: value[%d]=%d should be > value[%d]=%d",
i, j, curve[j], j-1, curve[j-1])
}
}
}
}
func TestAchievementCurveMap_Coverage(t *testing.T) {
// Ensure all mapped curves exist
for id, curve := range achievementCurveMap {
found := false
for _, c := range achievementCurves {
if &c[0] == &curve[0] {
found = true
break
}
}
if !found {
t.Errorf("Achievement ID %d maps to unknown curve", id)
}
}
}
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)
})
}
}
// --- NEW TESTS ---
// TestGetAchData_Level6BronzeTrophy tests that level 6 (in-progress toward level 7)
// awards the bronze trophy (0x40).
// Curve 0: {5, 15, 30, 50, 100, 150, 200, 300}
// Cumulative at each level: L1=5, L2=20, L3=50, L4=100, L5=200, L6=350, L7=550, L8=850
// At cumulative 350, we reach level 6. Score 400 means level 6 with progress 50 toward next.
func TestGetAchData_Level6BronzeTrophy(t *testing.T) {
// Score to reach level 6 and be partway to level 7:
// cumulative to level 6 = 5+15+30+50+100+150 = 350
// score 400 = level 6 with 50 remaining progress
ach := GetAchData(0, 400)
if ach.Level != 6 {
t.Errorf("Level = %d, want 6", ach.Level)
}
if ach.Trophy != 0x40 {
t.Errorf("Trophy = 0x%02x, want 0x40 (bronze)", ach.Trophy)
}
if ach.NextValue != 15 {
t.Errorf("NextValue = %d, want 15", ach.NextValue)
}
if ach.Progress != 50 {
t.Errorf("Progress = %d, want 50", ach.Progress)
}
if ach.Required != 200 {
t.Errorf("Required = %d, want 200 (curve[6])", ach.Required)
}
}
// TestGetAchData_Level7SilverTrophy tests that level 7 (in-progress toward level 8)
// awards the silver trophy (0x60).
// cumulative to level 7 = 5+15+30+50+100+150+200 = 550
// score 600 = level 7 with 50 remaining progress
func TestGetAchData_Level7SilverTrophy(t *testing.T) {
ach := GetAchData(0, 600)
if ach.Level != 7 {
t.Errorf("Level = %d, want 7", ach.Level)
}
if ach.Trophy != 0x60 {
t.Errorf("Trophy = 0x%02x, want 0x60 (silver)", ach.Trophy)
}
if ach.NextValue != 20 {
t.Errorf("NextValue = %d, want 20", ach.NextValue)
}
if ach.Progress != 50 {
t.Errorf("Progress = %d, want 50", ach.Progress)
}
if ach.Required != 300 {
t.Errorf("Required = %d, want 300 (curve[7])", ach.Required)
}
}
// TestGetAchData_MaxedOut_AllCurves tests that reaching max level on each curve
// produces the correct gold trophy and the last threshold as Required/Progress.
func TestGetAchData_MaxedOut_AllCurves(t *testing.T) {
tests := []struct {
name string
id uint8
score int32
lastThresh int32
}{
// Curve 0: {5,15,30,50,100,150,200,300} sum=850, last=300
{"Curve0_ID0", 0, 5000, 300},
// Curve 1: {1,5,10,15,30,50,75,100} sum=286, last=100
{"Curve1_ID7", 7, 5000, 100},
// Curve 2: {1,2,3,4,5,6,7,8} sum=36, last=8
{"Curve2_ID8", 8, 5000, 8},
// Curve 3: {10,50,100,200,350,500,750,999} sum=2959, last=999
{"Curve3_ID16", 16, 50000, 999},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ach := GetAchData(tt.id, tt.score)
if ach.Level != 8 {
t.Errorf("Level = %d, want 8 (max)", ach.Level)
}
if ach.Trophy != 0x7F {
t.Errorf("Trophy = 0x%02x, want 0x7F (gold)", ach.Trophy)
}
if ach.Required != uint32(tt.lastThresh) {
t.Errorf("Required = %d, want %d", ach.Required, tt.lastThresh)
}
if ach.Progress != ach.Required {
t.Errorf("Progress = %d, want %d (should equal Required at max)", ach.Progress, ach.Required)
}
})
}
}
// TestGetAchData_ExactlyAtEachThreshold tests the exact cumulative score at each
// threshold boundary for curve 0.
func TestGetAchData_ExactlyAtEachThreshold(t *testing.T) {
// Curve 0: {5, 15, 30, 50, 100, 150, 200, 300}
// Cumulative thresholds (exact score to reach each level):
// L1: 5, L2: 20, L3: 50, L4: 100, L5: 200, L6: 350, L7: 550, L8: 850
cumulativeScores := []int32{5, 20, 50, 100, 200, 350, 550, 850}
expectedLevels := []uint8{1, 2, 3, 4, 5, 6, 7, 8}
expectedValues := []uint32{5, 15, 25, 35, 50, 65, 80, 100}
for i, score := range cumulativeScores {
t.Run("ExactThreshold_L"+string(rune('1'+i)), func(t *testing.T) {
ach := GetAchData(0, score)
if ach.Level != expectedLevels[i] {
t.Errorf("score=%d: Level = %d, want %d", score, ach.Level, expectedLevels[i])
}
if ach.Value != expectedValues[i] {
t.Errorf("score=%d: Value = %d, want %d", score, ach.Value, expectedValues[i])
}
})
}
}
// TestGetAchData_OneBeforeEachThreshold tests scores that are one less than
// each cumulative threshold, verifying they stay at the previous level.
func TestGetAchData_OneBeforeEachThreshold(t *testing.T) {
// Curve 0: cumulative thresholds: 5, 20, 50, 100, 200, 350, 550, 850
cumulativeScores := []int32{4, 19, 49, 99, 199, 349, 549, 849}
expectedLevels := []uint8{0, 1, 2, 3, 4, 5, 6, 7}
for i, score := range cumulativeScores {
t.Run("OneBeforeThreshold_L"+string(rune('0'+i)), func(t *testing.T) {
ach := GetAchData(0, score)
if ach.Level != expectedLevels[i] {
t.Errorf("score=%d: Level = %d, want %d", score, ach.Level, expectedLevels[i])
}
})
}
}
// TestGetAchData_Curve2_FestaWins exercises the "Festa wins" curve which has
// small thresholds: {1, 2, 3, 4, 5, 6, 7, 8}
func TestGetAchData_Curve2_FestaWins(t *testing.T) {
// Curve 2: {1, 2, 3, 4, 5, 6, 7, 8}
// Cumulative: 1, 3, 6, 10, 15, 21, 28, 36
tests := []struct {
score int32
wantLvl uint8
wantProg uint32
wantReq uint32
}{
{0, 0, 0, 1},
{1, 1, 0, 2}, // Exactly at first threshold
{2, 1, 1, 2}, // One into second threshold
{3, 2, 0, 3}, // Exactly at second cumulative
{36, 8, 8, 8}, // Max level (sum of all thresholds)
{100, 8, 8, 8}, // Well above max
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
ach := GetAchData(8, tt.score) // ID 8 maps to curve 2
if ach.Level != tt.wantLvl {
t.Errorf("score=%d: Level = %d, want %d", tt.score, ach.Level, tt.wantLvl)
}
if ach.Progress != tt.wantProg {
t.Errorf("score=%d: Progress = %d, want %d", tt.score, ach.Progress, tt.wantProg)
}
if ach.Required != tt.wantReq {
t.Errorf("score=%d: Required = %d, want %d", tt.score, ach.Required, tt.wantReq)
}
})
}
}
// TestGetAchData_AllIDs_ZeroScore verifies that calling GetAchData with score=0
// for every valid ID returns level 0 without panicking.
func TestGetAchData_AllIDs_ZeroScore(t *testing.T) {
for id := uint8(0); id <= 32; id++ {
ach := GetAchData(id, 0)
if ach.Level != 0 {
t.Errorf("ID %d, score 0: Level = %d, want 0", id, ach.Level)
}
if ach.Value != 0 {
t.Errorf("ID %d, score 0: Value = %d, want 0", id, ach.Value)
}
if ach.Trophy != 0 {
t.Errorf("ID %d, score 0: Trophy = 0x%02x, want 0x00", id, ach.Trophy)
}
}
}
// TestGetAchData_AllIDs_MaxScore verifies that calling GetAchData with a very
// high score for every valid ID returns level 8 with gold trophy.
func TestGetAchData_AllIDs_MaxScore(t *testing.T) {
for id := uint8(0); id <= 32; id++ {
ach := GetAchData(id, 99999)
if ach.Level != 8 {
t.Errorf("ID %d: Level = %d, want 8", id, ach.Level)
}
if ach.Trophy != 0x7F {
t.Errorf("ID %d: Trophy = 0x%02x, want 0x7F", id, ach.Trophy)
}
// At max, Progress should equal Required
if ach.Progress != ach.Required {
t.Errorf("ID %d: Progress (%d) != Required (%d) at max", id, ach.Progress, ach.Required)
}
}
}
// TestGetAchData_UpdatedAlwaysFalse confirms Updated is always false since
// GetAchData never sets it.
func TestGetAchData_UpdatedAlwaysFalse(t *testing.T) {
scores := []int32{0, 1, 5, 50, 500, 5000}
for _, score := range scores {
ach := GetAchData(0, score)
if ach.Updated {
t.Errorf("score=%d: Updated should always be false, got true", score)
}
}
}

View File

@@ -0,0 +1,77 @@
package channelserver
import (
"testing"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetBbsUserStatus(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetBbsUserStatus{
AckHandle: 12345,
}
handleMsgMhfGetBbsUserStatus(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")
}
}
func TestHandleMsgMhfGetBbsSnsStatus(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetBbsSnsStatus{
AckHandle: 12345,
}
handleMsgMhfGetBbsSnsStatus(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")
}
}
func TestHandleMsgMhfApplyBbsArticle(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
Screenshots: _config.ScreenshotsOptions{
Host: "example.com",
Port: 8080,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfApplyBbsArticle{
AckHandle: 12345,
}
handleMsgMhfApplyBbsArticle(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")
}
}

View File

@@ -0,0 +1,110 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetBoostTime(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetBoostTime{
AckHandle: 12345,
}
handleMsgMhfGetBoostTime(session, pkt)
select {
case p := <-session.sendPackets:
// Response should be empty bytes for this handler
if p.data == nil {
t.Error("Response packet data should not be nil")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfPostBoostTimeQuestReturn(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostBoostTimeQuestReturn{
AckHandle: 12345,
}
handleMsgMhfPostBoostTimeQuestReturn(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 TestHandleMsgMhfPostBoostTime(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostBoostTime{
AckHandle: 12345,
}
handleMsgMhfPostBoostTime(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 TestHandleMsgMhfPostBoostTimeLimit(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostBoostTimeLimit{
AckHandle: 12345,
}
handleMsgMhfPostBoostTimeLimit(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 TestCafeBonusStruct(t *testing.T) {
// Test CafeBonus struct can be created
bonus := CafeBonus{
ID: 1,
TimeReq: 3600,
ItemType: 1,
ItemID: 100,
Quantity: 5,
Claimed: false,
}
if bonus.ID != 1 {
t.Errorf("ID = %d, want 1", bonus.ID)
}
if bonus.TimeReq != 3600 {
t.Errorf("TimeReq = %d, want 3600", bonus.TimeReq)
}
if bonus.Claimed {
t.Error("Claimed should be false")
}
}

View File

@@ -0,0 +1,70 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfEnumerateCampaign(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateCampaign{
AckHandle: 12345,
}
handleMsgMhfEnumerateCampaign(session, pkt)
// Verify response packet was queued (fail response expected)
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 TestHandleMsgMhfStateCampaign(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfStateCampaign{
AckHandle: 12345,
}
handleMsgMhfStateCampaign(session, pkt)
// Verify response packet was queued (fail response expected)
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 TestHandleMsgMhfApplyCampaign(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfApplyCampaign{
AckHandle: 12345,
}
handleMsgMhfApplyCampaign(session, pkt)
// Verify response packet was queued (fail response expected)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}

View File

@@ -0,0 +1,141 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetRyoudama(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRyoudama{
AckHandle: 12345,
}
handleMsgMhfGetRyoudama(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 TestHandleMsgMhfPostRyoudama(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfPostRyoudama panicked: %v", r)
}
}()
handleMsgMhfPostRyoudama(session, nil)
}
func TestHandleMsgMhfGetTinyBin(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetTinyBin{
AckHandle: 12345,
}
handleMsgMhfGetTinyBin(session, pkt)
select {
case p := <-session.sendPackets:
// Response might be empty bytes
if p.data == nil {
t.Error("Response packet data should not be nil")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfPostTinyBin(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostTinyBin{
AckHandle: 12345,
}
handleMsgMhfPostTinyBin(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 TestHandleMsgMhfCaravanMyScore(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCaravanMyScore{
AckHandle: 12345,
}
handleMsgMhfCaravanMyScore(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 TestHandleMsgMhfCaravanRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCaravanRanking{
AckHandle: 12345,
}
handleMsgMhfCaravanRanking(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 TestHandleMsgMhfCaravanMyRank(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCaravanMyRank{
AckHandle: 12345,
}
handleMsgMhfCaravanMyRank(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")
}
}

View File

@@ -0,0 +1,700 @@
package channelserver
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
// Test empty handlers don't panic
func TestHandleMsgHead(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgHead panicked: %v", r)
}
}()
handleMsgHead(session, nil)
}
func TestHandleMsgSysExtendThreshold(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysExtendThreshold panicked: %v", r)
}
}()
handleMsgSysExtendThreshold(session, nil)
}
func TestHandleMsgSysEnd(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysEnd panicked: %v", r)
}
}()
handleMsgSysEnd(session, nil)
}
func TestHandleMsgSysNop(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysNop panicked: %v", r)
}
}()
handleMsgSysNop(session, nil)
}
func TestHandleMsgSysAck(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysAck panicked: %v", r)
}
}()
handleMsgSysAck(session, nil)
}
func TestHandleMsgCaExchangeItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgCaExchangeItem panicked: %v", r)
}
}()
handleMsgCaExchangeItem(session, nil)
}
func TestHandleMsgMhfServerCommand(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfServerCommand panicked: %v", r)
}
}()
handleMsgMhfServerCommand(session, nil)
}
func TestHandleMsgMhfSetLoginwindow(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfSetLoginwindow panicked: %v", r)
}
}()
handleMsgMhfSetLoginwindow(session, nil)
}
func TestHandleMsgSysTransBinary(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysTransBinary panicked: %v", r)
}
}()
handleMsgSysTransBinary(session, nil)
}
func TestHandleMsgSysCollectBinary(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysCollectBinary panicked: %v", r)
}
}()
handleMsgSysCollectBinary(session, nil)
}
func TestHandleMsgSysGetState(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysGetState panicked: %v", r)
}
}()
handleMsgSysGetState(session, nil)
}
func TestHandleMsgSysSerialize(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysSerialize panicked: %v", r)
}
}()
handleMsgSysSerialize(session, nil)
}
func TestHandleMsgSysEnumlobby(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysEnumlobby panicked: %v", r)
}
}()
handleMsgSysEnumlobby(session, nil)
}
func TestHandleMsgSysEnumuser(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysEnumuser panicked: %v", r)
}
}()
handleMsgSysEnumuser(session, nil)
}
func TestHandleMsgSysInfokyserver(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysInfokyserver panicked: %v", r)
}
}()
handleMsgSysInfokyserver(session, nil)
}
func TestHandleMsgMhfGetCaUniqueID(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetCaUniqueID panicked: %v", r)
}
}()
handleMsgMhfGetCaUniqueID(session, nil)
}
func TestHandleMsgMhfEnumerateItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateItem{
AckHandle: 12345,
}
handleMsgMhfEnumerateItem(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 TestHandleMsgMhfAcquireItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireItem{
AckHandle: 12345,
}
handleMsgMhfAcquireItem(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 TestHandleMsgMhfGetExtraInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetExtraInfo panicked: %v", r)
}
}()
handleMsgMhfGetExtraInfo(session, nil)
}
// Test handlers that return simple responses
func TestHandleMsgMhfTransferItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfTransferItem{
AckHandle: 12345,
}
handleMsgMhfTransferItem(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 TestHandleMsgMhfEnumeratePrice(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumeratePrice{
AckHandle: 12345,
}
handleMsgMhfEnumeratePrice(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 TestHandleMsgMhfEnumerateOrder(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateOrder{
AckHandle: 12345,
}
handleMsgMhfEnumerateOrder(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 terminal log handler
func TestHandleMsgSysTerminalLog(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 12345,
LogID: 100,
Entries: []mhfpacket.TerminalLogEntry{},
}
handleMsgSysTerminalLog(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 TestHandleMsgSysTerminalLog_WithEntries(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 12345,
LogID: 100,
Entries: []mhfpacket.TerminalLogEntry{
{Type1: 1, Type2: 2},
{Type1: 3, Type2: 4},
},
}
handleMsgSysTerminalLog(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 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,
Data: make([]byte, 256), // Must be large enough for ByteFrame reads (32 offset + 176 uint8s)
}
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"),
Data: byteframe.NewByteFrameFromBytes([]byte{0x00}),
}
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)
}

View File

@@ -0,0 +1,920 @@
package channelserver
import (
"testing"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
)
// Tests for guild handlers that do not require database access.
func TestHandleMsgMhfEntryRookieGuild(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEntryRookieGuild{
AckHandle: 12345,
Unk: 42,
}
handleMsgMhfEntryRookieGuild(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 TestHandleMsgMhfGenerateUdGuildMap(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGenerateUdGuildMap{
AckHandle: 12345,
}
handleMsgMhfGenerateUdGuildMap(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 TestHandleMsgMhfCheckMonthlyItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfCheckMonthlyItem{
AckHandle: 12345,
Type: 0,
}
handleMsgMhfCheckMonthlyItem(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 TestHandleMsgMhfAcquireMonthlyItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireMonthlyItem{
AckHandle: 12345,
}
handleMsgMhfAcquireMonthlyItem(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 TestHandleMsgMhfEnumerateInvGuild(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateInvGuild{
AckHandle: 12345,
}
handleMsgMhfEnumerateInvGuild(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 TestHandleMsgMhfOperationInvGuild(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfOperationInvGuild{
AckHandle: 12345,
Operation: 1,
}
handleMsgMhfOperationInvGuild(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")
}
}
// Tests for mercenary handlers that do not require database access.
func TestHandleMsgMhfMercenaryHuntdata_Unk0Is1(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
AckHandle: 12345,
Unk0: 1,
}
handleMsgMhfMercenaryHuntdata(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 TestHandleMsgMhfMercenaryHuntdata_Unk0Is0(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
AckHandle: 12345,
Unk0: 0,
}
handleMsgMhfMercenaryHuntdata(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 TestHandleMsgMhfMercenaryHuntdata_Unk0Is2(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
AckHandle: 12345,
Unk0: 2,
}
handleMsgMhfMercenaryHuntdata(session, pkt)
// Unk0=2 takes the else branch (same as 0)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
// Tests for festa/ranking handlers.
func TestHandleMsgMhfEnumerateRanking_DefaultBranch(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: 0,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 99999,
}
handleMsgMhfEnumerateRanking(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 TestHandleMsgMhfEnumerateRanking_NegativeState(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: -1,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 99999,
}
handleMsgMhfEnumerateRanking(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")
}
}
// Tests for rengoku handlers.
func TestHandleMsgMhfGetRengokuRankingRank_ResponseData(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRengokuRankingRank{
AckHandle: 55555,
}
handleMsgMhfGetRengokuRankingRank(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")
}
}
// Tests for empty handlers that are not covered in other test files.
func TestEmptyHandlers_Coverage2(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
handler func(s *Session, p mhfpacket.MHFPacket)
}{
{"handleMsgSysCastedBinary", handleMsgSysCastedBinary},
{"handleMsgMhfResetTitle", handleMsgMhfResetTitle},
{"handleMsgMhfUpdateForceGuildRank", handleMsgMhfUpdateForceGuildRank},
{"handleMsgMhfUpdateGuild", handleMsgMhfUpdateGuild},
{"handleMsgMhfUpdateGuildcard", handleMsgMhfUpdateGuildcard},
}
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)
})
}
}
// Tests for handlers.go - handlers that produce responses without DB access.
func TestHandleMsgSysTerminalLog_MultipleEntries(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 12345,
LogID: 200,
Entries: []mhfpacket.TerminalLogEntry{
{Type1: 10, Type2: 20},
{Type1: 11, Type2: 21},
{Type1: 12, Type2: 22},
},
}
handleMsgSysTerminalLog(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 TestHandleMsgSysTerminalLog_ZeroLogID(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 12345,
LogID: 0,
Entries: []mhfpacket.TerminalLogEntry{},
}
handleMsgSysTerminalLog(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 TestHandleMsgSysPing_DifferentAckHandle(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysPing{
AckHandle: 0xFFFFFFFF,
}
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")
}
}
func TestHandleMsgSysTime_GetRemoteTimeFalse(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTime{
GetRemoteTime: false,
}
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")
}
}
func TestHandleMsgSysIssueLogkey_LogKeyGenerated(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysIssueLogkey{
AckHandle: 77777,
}
handleMsgSysIssueLogkey(session, pkt)
// Verify that the logKey was set on the session
session.Lock()
keyLen := len(session.logKey)
session.Unlock()
if keyLen != 16 {
t.Errorf("logKey length = %d, want 16", keyLen)
}
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 TestHandleMsgSysIssueLogkey_Uniqueness(t *testing.T) {
server := createMockServer()
// Generate two logkeys and verify they differ
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
pkt1 := &mhfpacket.MsgSysIssueLogkey{AckHandle: 1}
pkt2 := &mhfpacket.MsgSysIssueLogkey{AckHandle: 2}
handleMsgSysIssueLogkey(session1, pkt1)
handleMsgSysIssueLogkey(session2, pkt2)
// Drain send packets
<-session1.sendPackets
<-session2.sendPackets
session1.Lock()
key1 := make([]byte, len(session1.logKey))
copy(key1, session1.logKey)
session1.Unlock()
session2.Lock()
key2 := make([]byte, len(session2.logKey))
copy(key2, session2.logKey)
session2.Unlock()
if len(key1) != 16 || len(key2) != 16 {
t.Fatalf("logKeys should be 16 bytes each, got %d and %d", len(key1), len(key2))
}
same := true
for i := range key1 {
if key1[i] != key2[i] {
same = false
break
}
}
if same {
t.Error("Two generated logkeys should differ (extremely unlikely to be the same)")
}
}
// Tests for event handlers.
func TestHandleMsgMhfReleaseEvent_ErrorCode(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReleaseEvent{
AckHandle: 88888,
}
handleMsgMhfReleaseEvent(session, pkt)
// This handler manually sends a response with error code 0x41
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 TestHandleMsgMhfEnumerateEvent_Stub(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateEvent{
AckHandle: 77777,
}
handleMsgMhfEnumerateEvent(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")
}
}
// Tests for achievement handler.
func TestHandleMsgMhfSetCaAchievementHist_Response(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfSetCaAchievementHist{
AckHandle: 44444,
}
handleMsgMhfSetCaAchievementHist(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 concurrent handler invocations to catch potential data races.
func TestHandlersConcurrentInvocations(t *testing.T) {
server := createMockServer()
done := make(chan struct{})
const numGoroutines = 10
for i := 0; i < numGoroutines; i++ {
go func(id uint32) {
defer func() {
if r := recover(); r != nil {
t.Errorf("goroutine %d panicked: %v", id, r)
}
done <- struct{}{}
}()
session := createMockSession(id, server)
// Run several handlers concurrently
handleMsgSysPing(session, &mhfpacket.MsgSysPing{AckHandle: id})
<-session.sendPackets
handleMsgSysTime(session, &mhfpacket.MsgSysTime{GetRemoteTime: true})
<-session.sendPackets
handleMsgSysIssueLogkey(session, &mhfpacket.MsgSysIssueLogkey{AckHandle: id})
<-session.sendPackets
handleMsgMhfMercenaryHuntdata(session, &mhfpacket.MsgMhfMercenaryHuntdata{AckHandle: id, Unk0: 1})
<-session.sendPackets
handleMsgMhfEnumerateMercenaryLog(session, &mhfpacket.MsgMhfEnumerateMercenaryLog{AckHandle: id})
<-session.sendPackets
}(uint32(i + 100))
}
for i := 0; i < numGoroutines; i++ {
<-done
}
}
// Test record log handler with stage setup.
func TestHandleMsgSysRecordLog_RemovesReservation(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
stage := NewStage("test_stage_record")
session.stage = stage
stage.reservedClientSlots[session.charID] = true
pkt := &mhfpacket.MsgSysRecordLog{
AckHandle: 55555,
Data: make([]byte, 256),
}
handleMsgSysRecordLog(session, pkt)
if _, exists := stage.reservedClientSlots[session.charID]; exists {
t.Error("charID should be removed from reserved slots after record log")
}
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 TestHandleMsgSysRecordLog_NoExistingReservation(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
stage := NewStage("test_stage_no_reservation")
session.stage = stage
// No reservation exists for this charID
pkt := &mhfpacket.MsgSysRecordLog{
AckHandle: 55556,
Data: make([]byte, 256),
}
// Should not panic even if charID is not in reservedClientSlots
handleMsgSysRecordLog(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 unlock global sema handler.
func TestHandleMsgSysUnlockGlobalSema_Response(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysUnlockGlobalSema{
AckHandle: 66666,
}
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 handlers from handlers_event.go with edge cases.
func TestHandleMsgMhfSetRestrictionEvent_Response(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfSetRestrictionEvent{
AckHandle: 11111,
}
handleMsgMhfSetRestrictionEvent(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 TestHandleMsgMhfGetRestrictionEvent_Empty(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetRestrictionEvent panicked: %v", r)
}
}()
handleMsgMhfGetRestrictionEvent(session, nil)
}
// Test handlers from handlers_mercenary.go - legend dispatch (no DB).
func TestHandleMsgMhfLoadLegendDispatch_Response(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfLoadLegendDispatch{
AckHandle: 22222,
}
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")
}
}
// Test multiple handler invocations on the same session to verify session state is not corrupted.
func TestMultipleHandlersOnSameSession(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Call multiple handlers in sequence
handleMsgSysPing(session, &mhfpacket.MsgSysPing{AckHandle: 1})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from Ping handler")
}
handleMsgSysTime(session, &mhfpacket.MsgSysTime{GetRemoteTime: true})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from Time handler")
}
handleMsgMhfRegisterEvent(session, &mhfpacket.MsgMhfRegisterEvent{AckHandle: 2, WorldID: 5, LandID: 10})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from RegisterEvent handler")
}
handleMsgMhfReleaseEvent(session, &mhfpacket.MsgMhfReleaseEvent{AckHandle: 3})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from ReleaseEvent handler")
}
handleMsgMhfEnumerateEvent(session, &mhfpacket.MsgMhfEnumerateEvent{AckHandle: 4})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from EnumerateEvent handler")
}
handleMsgMhfSetCaAchievementHist(session, &mhfpacket.MsgMhfSetCaAchievementHist{AckHandle: 5})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from SetCaAchievementHist handler")
}
handleMsgMhfGetRengokuRankingRank(session, &mhfpacket.MsgMhfGetRengokuRankingRank{AckHandle: 6})
select {
case <-session.sendPackets:
default:
t.Fatal("Expected packet from GetRengokuRankingRank handler")
}
}
// Test festa timestamp generation.
func TestGenerateFestaTimestamps_Debug(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
start uint32
}{
{"Debug_Start1", 1},
{"Debug_Start2", 2},
{"Debug_Start3", 3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
timestamps := generateFestaTimestamps(session, tt.start, true)
if len(timestamps) != 5 {
t.Errorf("Expected 5 timestamps, got %d", len(timestamps))
}
for i, ts := range timestamps {
if ts == 0 {
t.Errorf("Timestamp %d should not be zero", i)
}
}
})
}
}
func TestGenerateFestaTimestamps_NonDebug_FutureStart(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Use a far-future start time so it does not trigger cleanup
futureStart := uint32(TimeAdjusted().Unix() + 5000000)
timestamps := generateFestaTimestamps(session, futureStart, false)
if len(timestamps) != 5 {
t.Errorf("Expected 5 timestamps, got %d", len(timestamps))
}
if timestamps[0] != futureStart {
t.Errorf("First timestamp = %d, want %d", timestamps[0], futureStart)
}
// Verify intervals
if timestamps[1] != timestamps[0]+604800 {
t.Errorf("Second timestamp should be start+604800, got %d", timestamps[1])
}
if timestamps[2] != timestamps[1]+604800 {
t.Errorf("Third timestamp should be second+604800, got %d", timestamps[2])
}
if timestamps[3] != timestamps[2]+9000 {
t.Errorf("Fourth timestamp should be third+9000, got %d", timestamps[3])
}
if timestamps[4] != timestamps[3]+1240200 {
t.Errorf("Fifth timestamp should be fourth+1240200, got %d", timestamps[4])
}
}
// Test trial struct from handlers_festa.go.
func TestFestaTrialStruct(t *testing.T) {
trial := FestaTrial{
ID: 100,
Objective: 2,
GoalID: 500,
TimesReq: 10,
Locale: 1,
Reward: 50,
}
if trial.ID != 100 {
t.Errorf("ID = %d, want 100", trial.ID)
}
if trial.Objective != 2 {
t.Errorf("Objective = %d, want 2", trial.Objective)
}
if trial.GoalID != 500 {
t.Errorf("GoalID = %d, want 500", trial.GoalID)
}
if trial.TimesReq != 10 {
t.Errorf("TimesReq = %d, want 10", trial.TimesReq)
}
}
// Test prize struct from handlers_festa.go.
func TestPrizeStruct(t *testing.T) {
prize := Prize{
ID: 1,
Tier: 2,
SoulsReq: 100,
ItemID: 0x1234,
NumItem: 5,
Claimed: 1,
}
if prize.ID != 1 {
t.Errorf("ID = %d, want 1", prize.ID)
}
if prize.Tier != 2 {
t.Errorf("Tier = %d, want 2", prize.Tier)
}
if prize.SoulsReq != 100 {
t.Errorf("SoulsReq = %d, want 100", prize.SoulsReq)
}
if prize.Claimed != 1 {
t.Errorf("Claimed = %d, want 1", prize.Claimed)
}
}
// Test Airou struct from handlers_mercenary.go.
func TestAirouStruct(t *testing.T) {
cat := Airou{
ID: 42,
Name: []byte("TestCat"),
Task: 4,
Personality: 2,
Class: 1,
Experience: 1500,
WeaponType: 6,
WeaponID: 100,
}
if cat.ID != 42 {
t.Errorf("ID = %d, want 42", cat.ID)
}
if cat.Task != 4 {
t.Errorf("Task = %d, want 4", cat.Task)
}
if cat.Experience != 1500 {
t.Errorf("Experience = %d, want 1500", cat.Experience)
}
if cat.WeaponType != 6 {
t.Errorf("WeaponType = %d, want 6", cat.WeaponType)
}
if cat.WeaponID != 100 {
t.Errorf("WeaponID = %d, want 100", cat.WeaponID)
}
}
// Test RengokuScore struct default values.
func TestRengokuScoreStruct_Fields(t *testing.T) {
score := RengokuScore{
Name: "Hunter",
Score: 99999,
}
if score.Name != "Hunter" {
t.Errorf("Name = %s, want Hunter", score.Name)
}
if score.Score != 99999 {
t.Errorf("Score = %d, want 99999", score.Score)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
// Tests for handlers that do NOT require database access, exercising additional
// code paths not covered by existing test files (handlers_core_test.go,
// handlers_rengoku_test.go, etc.).
// TestHandleMsgSysPing_DifferentAckHandles verifies ping works with various ack handles.
func TestHandleMsgSysPing_DifferentAckHandles(t *testing.T) {
server := createMockServer()
ackHandles := []uint32{0, 1, 99999, 0xFFFFFFFF}
for _, ack := range ackHandles {
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysPing{AckHandle: ack}
handleMsgSysPing(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("AckHandle=%d: Response packet should have data", ack)
}
default:
t.Errorf("AckHandle=%d: No response packet queued", ack)
}
}
}
// TestHandleMsgSysTerminalLog_NoEntries verifies the handler works with nil entries.
func TestHandleMsgSysTerminalLog_NoEntries(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 99999,
LogID: 0,
Entries: nil,
}
handleMsgSysTerminalLog(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")
}
}
// TestHandleMsgSysTerminalLog_ManyEntries verifies the handler with many log entries.
func TestHandleMsgSysTerminalLog_ManyEntries(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
entries := make([]mhfpacket.TerminalLogEntry, 20)
for i := range entries {
entries[i] = mhfpacket.TerminalLogEntry{
Index: uint32(i),
Type1: uint8(i % 256),
Type2: uint8((i + 1) % 256),
}
}
pkt := &mhfpacket.MsgSysTerminalLog{
AckHandle: 55555,
LogID: 42,
Entries: entries,
}
handleMsgSysTerminalLog(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")
}
}
// TestHandleMsgSysTime_MultipleCalls verifies calling time handler repeatedly.
func TestHandleMsgSysTime_MultipleCalls(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysTime{
GetRemoteTime: false,
Timestamp: 0,
}
for i := 0; i < 5; i++ {
handleMsgSysTime(session, pkt)
}
// Should have 5 queued responses
count := 0
for {
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
count++
default:
goto done
}
}
done:
if count != 5 {
t.Errorf("Expected 5 queued responses, got %d", count)
}
}
// TestHandleMsgMhfGetRengokuRankingRank_DifferentAck verifies rengoku ranking
// works with different ack handles.
func TestHandleMsgMhfGetRengokuRankingRank_DifferentAck(t *testing.T) {
server := createMockServer()
ackHandles := []uint32{0, 1, 54321, 0xDEADBEEF}
for _, ack := range ackHandles {
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRengokuRankingRank{AckHandle: ack}
handleMsgMhfGetRengokuRankingRank(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Errorf("AckHandle=%d: Response packet should have data", ack)
}
default:
t.Errorf("AckHandle=%d: No response packet queued", ack)
}
}
}

View File

@@ -0,0 +1 @@
package channelserver

View File

@@ -0,0 +1,343 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetUdInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdInfo{
AckHandle: 12345,
}
handleMsgMhfGetUdInfo(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 TestHandleMsgMhfGetKijuInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetKijuInfo{
AckHandle: 12345,
}
handleMsgMhfGetKijuInfo(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 TestHandleMsgMhfSetKiju(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfSetKiju{
AckHandle: 12345,
}
handleMsgMhfSetKiju(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 TestHandleMsgMhfAddUdPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAddUdPoint{
AckHandle: 12345,
}
handleMsgMhfAddUdPoint(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 TestHandleMsgMhfGetUdMyPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdMyPoint{
AckHandle: 12345,
}
handleMsgMhfGetUdMyPoint(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 TestHandleMsgMhfGetUdTotalPointInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTotalPointInfo{
AckHandle: 12345,
}
handleMsgMhfGetUdTotalPointInfo(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 TestHandleMsgMhfGetUdSelectedColorInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdSelectedColorInfo{
AckHandle: 12345,
}
handleMsgMhfGetUdSelectedColorInfo(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 TestHandleMsgMhfGetUdMonsterPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdMonsterPoint{
AckHandle: 12345,
}
handleMsgMhfGetUdMonsterPoint(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 TestHandleMsgMhfGetUdDailyPresentList(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdDailyPresentList{
AckHandle: 12345,
}
handleMsgMhfGetUdDailyPresentList(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 TestHandleMsgMhfGetUdNormaPresentList(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdNormaPresentList{
AckHandle: 12345,
}
handleMsgMhfGetUdNormaPresentList(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 TestHandleMsgMhfAcquireUdItem(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireUdItem{
AckHandle: 12345,
}
handleMsgMhfAcquireUdItem(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 TestHandleMsgMhfGetUdRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdRanking{
AckHandle: 12345,
}
handleMsgMhfGetUdRanking(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 TestHandleMsgMhfGetUdMyRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdMyRanking{
AckHandle: 12345,
}
handleMsgMhfGetUdMyRanking(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 TestGenerateDivaTimestamps_Debug(t *testing.T) {
// Test debug mode timestamps
tests := []struct {
name string
start uint32
}{
{"Debug_Start1", 1},
{"Debug_Start2", 2},
{"Debug_Start3", 3},
}
server := createMockServer()
session := createMockSession(1, server)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
timestamps := generateDivaTimestamps(session, tt.start, true)
if len(timestamps) != 6 {
t.Errorf("Expected 6 timestamps, got %d", len(timestamps))
}
// Verify timestamps are non-zero
for i, ts := range timestamps {
if ts == 0 {
t.Errorf("Timestamp %d should not be zero", i)
}
}
})
}
}
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")
}
}

View File

@@ -0,0 +1,258 @@
package channelserver
import (
"math/bits"
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfRegisterEvent(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfRegisterEvent{
AckHandle: 12345,
WorldID: 1,
LandID: 2,
}
handleMsgMhfRegisterEvent(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")
}
}
func TestHandleMsgMhfReleaseEvent(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReleaseEvent{
AckHandle: 12345,
}
handleMsgMhfReleaseEvent(session, pkt)
// Verify response packet was queued (with special error code 0x41)
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 TestHandleMsgMhfEnumerateEvent(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateEvent{
AckHandle: 12345,
}
handleMsgMhfEnumerateEvent(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")
}
}
func TestHandleMsgMhfGetRestrictionEvent(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetRestrictionEvent panicked: %v", r)
}
}()
handleMsgMhfGetRestrictionEvent(session, nil)
}
func TestHandleMsgMhfSetRestrictionEvent(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfSetRestrictionEvent{
AckHandle: 12345,
}
handleMsgMhfSetRestrictionEvent(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")
}
}
func TestGenerateFeatureWeapons(t *testing.T) {
tests := []struct {
name string
count int
}{
{"single weapon", 1},
{"few weapons", 3},
{"normal count", 7},
{"max weapons", 14},
{"over max", 20}, // Should cap at 14
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := generateFeatureWeapons(tt.count)
// Result should be non-zero for positive counts
if tt.count > 0 && result.ActiveFeatures == 0 {
t.Error("Expected non-zero ActiveFeatures")
}
// Should not exceed max value (2^14 - 1 = 16383)
if result.ActiveFeatures > 16383 {
t.Errorf("ActiveFeatures = %d, exceeds max of 16383", result.ActiveFeatures)
}
})
}
}
func TestGenerateFeatureWeapons_Randomness(t *testing.T) {
// Generate multiple times and verify some variation
results := make(map[uint32]int)
iterations := 100
for i := 0; i < iterations; i++ {
result := generateFeatureWeapons(5)
results[result.ActiveFeatures]++
}
// Should have some variation (not all the same)
if len(results) == 1 {
t.Error("Expected some variation in generated weapons")
}
}
func TestGenerateFeatureWeapons_ZeroCount(t *testing.T) {
result := generateFeatureWeapons(0)
// Should return 0 for no weapons
if result.ActiveFeatures != 0 {
t.Errorf("Expected 0 for zero count, got %d", result.ActiveFeatures)
}
}
// --- NEW TESTS ---
// TestGenerateFeatureWeapons_BitCount verifies that the number of set bits
// in ActiveFeatures matches the requested count (capped at 14).
func TestGenerateFeatureWeapons_BitCount(t *testing.T) {
tests := []struct {
name string
count int
wantBits int
}{
{"1 weapon", 1, 1},
{"5 weapons", 5, 5},
{"10 weapons", 10, 10},
{"14 weapons", 14, 14},
{"20 capped to 14", 20, 14},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := generateFeatureWeapons(tt.count)
setBits := bits.OnesCount32(result.ActiveFeatures)
if setBits != tt.wantBits {
t.Errorf("Set bits = %d, want %d (ActiveFeatures=0b%032b)",
setBits, tt.wantBits, result.ActiveFeatures)
}
})
}
}
// TestGenerateFeatureWeapons_BitsInRange verifies that all set bits are within
// bits 0-13 (no bits above bit 13 should be set).
func TestGenerateFeatureWeapons_BitsInRange(t *testing.T) {
for i := 0; i < 50; i++ {
result := generateFeatureWeapons(7)
// Bits 14+ should never be set
if result.ActiveFeatures&^uint32(0x3FFF) != 0 {
t.Errorf("Bits above 13 are set: 0x%08X", result.ActiveFeatures)
}
}
}
// TestGenerateFeatureWeapons_MaxYieldsAllBits verifies that requesting 14
// weapons sets exactly bits 0-13 (the value 16383 = 0x3FFF).
func TestGenerateFeatureWeapons_MaxYieldsAllBits(t *testing.T) {
result := generateFeatureWeapons(14)
if result.ActiveFeatures != 0x3FFF {
t.Errorf("ActiveFeatures = 0x%04X, want 0x3FFF (all 14 bits set)", result.ActiveFeatures)
}
}
// TestGenerateFeatureWeapons_StartTimeZero verifies that the returned
// activeFeature has a zero StartTime (not set by generateFeatureWeapons).
func TestGenerateFeatureWeapons_StartTimeZero(t *testing.T) {
result := generateFeatureWeapons(5)
if !result.StartTime.IsZero() {
t.Errorf("StartTime should be zero, got %v", result.StartTime)
}
}
// TestHandleMsgMhfRegisterEvent_DifferentValues tests with various Unk2/Unk4 values.
func TestHandleMsgMhfRegisterEvent_DifferentValues(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
worldID uint16
landID uint16
}{
{"zeros", 0, 0},
{"max values", 65535, 65535},
{"typical", 5, 10},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfRegisterEvent{
AckHandle: 99999,
WorldID: tt.worldID,
LandID: tt.landID,
}
handleMsgMhfRegisterEvent(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")
}
})
}
}

View File

@@ -0,0 +1,109 @@
package channelserver
import (
"testing"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfEnumerateRanking_Default(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: 0, // Default state
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 12345,
}
handleMsgMhfEnumerateRanking(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 TestHandleMsgMhfEnumerateRanking_State1(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: 1,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 12345,
}
handleMsgMhfEnumerateRanking(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 TestHandleMsgMhfEnumerateRanking_State2(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: 2,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 12345,
}
handleMsgMhfEnumerateRanking(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 TestHandleMsgMhfEnumerateRanking_State3(t *testing.T) {
server := createMockServer()
server.erupeConfig = &_config.Config{
DebugOptions: _config.DebugOptions{
TournamentOverride: 3,
},
}
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateRanking{
AckHandle: 12345,
}
handleMsgMhfEnumerateRanking(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")
}
}

View File

@@ -0,0 +1,249 @@
package channelserver
import (
"encoding/json"
"testing"
)
func TestGuildIconScan_Bytes(t *testing.T) {
jsonData := []byte(`{"Parts":[{"Index":1,"ID":100,"Page":2,"Size":3,"Rotation":4,"Red":255,"Green":128,"Blue":0,"PosX":50,"PosY":60}]}`)
gi := &GuildIcon{}
err := gi.Scan(jsonData)
if err != nil {
t.Fatalf("Scan([]byte) error = %v", err)
}
if len(gi.Parts) != 1 {
t.Fatalf("Parts length = %d, want 1", len(gi.Parts))
}
part := gi.Parts[0]
if part.Index != 1 {
t.Errorf("Index = %d, want 1", part.Index)
}
if part.ID != 100 {
t.Errorf("ID = %d, want 100", part.ID)
}
if part.Page != 2 {
t.Errorf("Page = %d, want 2", part.Page)
}
if part.Size != 3 {
t.Errorf("Size = %d, want 3", part.Size)
}
if part.Rotation != 4 {
t.Errorf("Rotation = %d, want 4", part.Rotation)
}
if part.Red != 255 {
t.Errorf("Red = %d, want 255", part.Red)
}
if part.Green != 128 {
t.Errorf("Green = %d, want 128", part.Green)
}
if part.Blue != 0 {
t.Errorf("Blue = %d, want 0", part.Blue)
}
if part.PosX != 50 {
t.Errorf("PosX = %d, want 50", part.PosX)
}
if part.PosY != 60 {
t.Errorf("PosY = %d, want 60", part.PosY)
}
}
func TestGuildIconScan_String(t *testing.T) {
jsonStr := `{"Parts":[{"Index":5,"ID":200,"Page":1,"Size":2,"Rotation":0,"Red":100,"Green":50,"Blue":25,"PosX":300,"PosY":400}]}`
gi := &GuildIcon{}
err := gi.Scan(jsonStr)
if err != nil {
t.Fatalf("Scan(string) error = %v", err)
}
if len(gi.Parts) != 1 {
t.Fatalf("Parts length = %d, want 1", len(gi.Parts))
}
if gi.Parts[0].ID != 200 {
t.Errorf("ID = %d, want 200", gi.Parts[0].ID)
}
if gi.Parts[0].PosX != 300 {
t.Errorf("PosX = %d, want 300", gi.Parts[0].PosX)
}
}
func TestGuildIconScan_MultipleParts(t *testing.T) {
jsonData := []byte(`{"Parts":[{"Index":0,"ID":1,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0},{"Index":1,"ID":2,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0},{"Index":2,"ID":3,"Page":0,"Size":0,"Rotation":0,"Red":0,"Green":0,"Blue":0,"PosX":0,"PosY":0}]}`)
gi := &GuildIcon{}
err := gi.Scan(jsonData)
if err != nil {
t.Fatalf("Scan() error = %v", err)
}
if len(gi.Parts) != 3 {
t.Fatalf("Parts length = %d, want 3", len(gi.Parts))
}
for i, part := range gi.Parts {
if part.Index != uint16(i) {
t.Errorf("Parts[%d].Index = %d, want %d", i, part.Index, i)
}
}
}
func TestGuildIconScan_EmptyParts(t *testing.T) {
gi := &GuildIcon{}
err := gi.Scan([]byte(`{"Parts":[]}`))
if err != nil {
t.Fatalf("Scan() error = %v", err)
}
if len(gi.Parts) != 0 {
t.Errorf("Parts length = %d, want 0", len(gi.Parts))
}
}
func TestGuildIconScan_InvalidJSON(t *testing.T) {
gi := &GuildIcon{}
err := gi.Scan([]byte(`{invalid`))
if err == nil {
t.Error("Scan() with invalid JSON should return error")
}
}
func TestGuildIconScan_InvalidJSONString(t *testing.T) {
gi := &GuildIcon{}
err := gi.Scan("{invalid")
if err == nil {
t.Error("Scan() with invalid JSON string should return error")
}
}
func TestGuildIconScan_UnsupportedType(t *testing.T) {
gi := &GuildIcon{}
// Passing an unsupported type should not error (just no-op)
err := gi.Scan(12345)
if err != nil {
t.Errorf("Scan(int) unexpected error = %v", err)
}
}
func TestGuildIconValue(t *testing.T) {
gi := &GuildIcon{
Parts: []GuildIconPart{
{Index: 1, ID: 100, Page: 2, Size: 3, Rotation: 4, Red: 255, Green: 128, Blue: 0, PosX: 50, PosY: 60},
},
}
val, err := gi.Value()
if err != nil {
t.Fatalf("Value() error = %v", err)
}
jsonBytes, ok := val.([]byte)
if !ok {
t.Fatalf("Value() returned %T, want []byte", val)
}
// Verify round-trip
gi2 := &GuildIcon{}
err = json.Unmarshal(jsonBytes, gi2)
if err != nil {
t.Fatalf("json.Unmarshal error = %v", err)
}
if len(gi2.Parts) != 1 {
t.Fatalf("round-trip Parts length = %d, want 1", len(gi2.Parts))
}
if gi2.Parts[0].ID != 100 {
t.Errorf("round-trip ID = %d, want 100", gi2.Parts[0].ID)
}
if gi2.Parts[0].Red != 255 {
t.Errorf("round-trip Red = %d, want 255", gi2.Parts[0].Red)
}
}
func TestGuildIconValue_Empty(t *testing.T) {
gi := &GuildIcon{}
val, err := gi.Value()
if err != nil {
t.Fatalf("Value() error = %v", err)
}
if val == nil {
t.Error("Value() should not return nil")
}
}
func TestGuildIconScanValueRoundTrip(t *testing.T) {
original := &GuildIcon{
Parts: []GuildIconPart{
{Index: 0, ID: 10, Page: 1, Size: 2, Rotation: 45, Red: 200, Green: 150, Blue: 100, PosX: 500, PosY: 600},
{Index: 1, ID: 20, Page: 3, Size: 4, Rotation: 90, Red: 50, Green: 75, Blue: 255, PosX: 100, PosY: 200},
},
}
// Value -> Scan round trip
val, err := original.Value()
if err != nil {
t.Fatalf("Value() error = %v", err)
}
restored := &GuildIcon{}
err = restored.Scan(val)
if err != nil {
t.Fatalf("Scan() error = %v", err)
}
if len(restored.Parts) != len(original.Parts) {
t.Fatalf("Parts length = %d, want %d", len(restored.Parts), len(original.Parts))
}
for i := range original.Parts {
if restored.Parts[i] != original.Parts[i] {
t.Errorf("Parts[%d] mismatch: got %+v, want %+v", i, restored.Parts[i], original.Parts[i])
}
}
}
func TestFestivalColorCodes(t *testing.T) {
tests := []struct {
colour FestivalColor
code int16
}{
{FestivalColorBlue, 0},
{FestivalColorRed, 1},
{FestivalColorNone, -1},
}
for _, tt := range tests {
t.Run(string(tt.colour), func(t *testing.T) {
code, ok := FestivalColorCodes[tt.colour]
if !ok {
t.Fatalf("FestivalColorCodes missing key %s", tt.colour)
}
if code != tt.code {
t.Errorf("FestivalColorCodes[%s] = %d, want %d", tt.colour, code, tt.code)
}
})
}
}
func TestFestivalColorConstants(t *testing.T) {
if FestivalColorNone != "none" {
t.Errorf("FestivalColorNone = %s, want none", FestivalColorNone)
}
if FestivalColorRed != "red" {
t.Errorf("FestivalColorRed = %s, want red", FestivalColorRed)
}
if FestivalColorBlue != "blue" {
t.Errorf("FestivalColorBlue = %s, want blue", FestivalColorBlue)
}
}
func TestGuildApplicationTypeConstants(t *testing.T) {
if GuildApplicationTypeApplied != "applied" {
t.Errorf("GuildApplicationTypeApplied = %s, want applied", GuildApplicationTypeApplied)
}
if GuildApplicationTypeInvited != "invited" {
t.Errorf("GuildApplicationTypeInvited = %s, want invited", GuildApplicationTypeInvited)
}
}

View File

@@ -0,0 +1,209 @@
package channelserver
import (
"testing"
)
func TestGuildMember_CanRecruit(t *testing.T) {
tests := []struct {
name string
member GuildMember
expected bool
}{
{
name: "recruiter flag true",
member: GuildMember{
Recruiter: true,
OrderIndex: 10,
IsLeader: false,
},
expected: true,
},
{
name: "order index 1",
member: GuildMember{
Recruiter: false,
OrderIndex: 1,
IsLeader: false,
},
expected: true,
},
{
name: "order index 2",
member: GuildMember{
Recruiter: false,
OrderIndex: 2,
IsLeader: false,
},
expected: true,
},
{
name: "order index 3",
member: GuildMember{
Recruiter: false,
OrderIndex: 3,
IsLeader: false,
},
expected: true,
},
{
name: "order index 0 (sub-leader)",
member: GuildMember{
Recruiter: false,
OrderIndex: 0,
IsLeader: false,
},
expected: true,
},
{
name: "order index 4 cannot recruit",
member: GuildMember{
Recruiter: false,
OrderIndex: 4,
IsLeader: false,
},
expected: false,
},
{
name: "order index 5 cannot recruit",
member: GuildMember{
Recruiter: false,
OrderIndex: 5,
IsLeader: false,
},
expected: false,
},
{
name: "is leader can recruit",
member: GuildMember{
Recruiter: false,
OrderIndex: 100,
IsLeader: true,
},
expected: true,
},
{
name: "regular member cannot recruit",
member: GuildMember{
Recruiter: false,
OrderIndex: 10,
IsLeader: false,
},
expected: false,
},
{
name: "all flags true",
member: GuildMember{
Recruiter: true,
OrderIndex: 1,
IsLeader: true,
},
expected: true,
},
{
name: "high order index with leader",
member: GuildMember{
Recruiter: false,
OrderIndex: 255,
IsLeader: true,
},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.member.CanRecruit()
if result != tt.expected {
t.Errorf("CanRecruit() = %v, expected %v (Recruiter=%v, OrderIndex=%d, IsLeader=%v)",
result, tt.expected, tt.member.Recruiter, tt.member.OrderIndex, tt.member.IsLeader)
}
})
}
}
func TestGuildMember_IsSubLeader(t *testing.T) {
tests := []struct {
name string
orderIndex uint16
expected bool
}{
{
name: "order index 0",
orderIndex: 0,
expected: true,
},
{
name: "order index 1",
orderIndex: 1,
expected: true,
},
{
name: "order index 2",
orderIndex: 2,
expected: true,
},
{
name: "order index 3",
orderIndex: 3,
expected: true,
},
{
name: "order index 4",
orderIndex: 4,
expected: false,
},
{
name: "order index 5",
orderIndex: 5,
expected: false,
},
{
name: "order index 100",
orderIndex: 100,
expected: false,
},
{
name: "order index 255",
orderIndex: 255,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
member := GuildMember{OrderIndex: tt.orderIndex}
result := member.IsSubLeader()
if result != tt.expected {
t.Errorf("IsSubLeader() with OrderIndex=%d = %v, expected %v",
tt.orderIndex, result, tt.expected)
}
})
}
}
func TestGuildMember_CanRecruit_Priority(t *testing.T) {
// Test that Recruiter flag takes priority (short-circuit)
member := GuildMember{
Recruiter: true,
OrderIndex: 100, // Would fail OrderIndex check
IsLeader: false,
}
if !member.CanRecruit() {
t.Error("Recruiter flag should allow recruiting regardless of OrderIndex")
}
}
func TestGuildMember_CanRecruit_OrderIndexBoundary(t *testing.T) {
// Test the exact boundary at OrderIndex == 3 vs 4
member3 := GuildMember{Recruiter: false, OrderIndex: 3, IsLeader: false}
member4 := GuildMember{Recruiter: false, OrderIndex: 4, IsLeader: false}
if !member3.CanRecruit() {
t.Error("OrderIndex 3 should be able to recruit")
}
if member4.CanRecruit() {
t.Error("OrderIndex 4 should NOT be able to recruit")
}
}

View 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")
}
}

View File

@@ -0,0 +1,298 @@
package channelserver
import (
"bytes"
"encoding/binary"
"testing"
"erupe-ce/common/byteframe"
"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")
}
}
// --- NEW TESTS ---
// buildCatBytes constructs a binary cat data payload suitable for GetAirouDetails.
func buildCatBytes(cats []Airou) []byte {
buf := new(bytes.Buffer)
// catCount
buf.WriteByte(byte(len(cats)))
for _, cat := range cats {
catBuf := new(bytes.Buffer)
// ID (uint32)
binary.Write(catBuf, binary.BigEndian, cat.ID)
// 1 byte skip (unknown bool)
catBuf.WriteByte(0)
// Name (18 bytes)
name := make([]byte, 18)
copy(name, cat.Name)
catBuf.Write(name)
// Task (uint8)
catBuf.WriteByte(cat.Task)
// 16 bytes skip (appearance data)
catBuf.Write(make([]byte, 16))
// Personality (uint8)
catBuf.WriteByte(cat.Personality)
// Class (uint8)
catBuf.WriteByte(cat.Class)
// 5 bytes skip (affection and colour sliders)
catBuf.Write(make([]byte, 5))
// Experience (uint32)
binary.Write(catBuf, binary.BigEndian, cat.Experience)
// 1 byte skip (bool for weapon equipped)
catBuf.WriteByte(0)
// WeaponType (uint8)
catBuf.WriteByte(cat.WeaponType)
// WeaponID (uint16)
binary.Write(catBuf, binary.BigEndian, cat.WeaponID)
catData := catBuf.Bytes()
// catDefLen (uint32) - total length of the cat data after this field
binary.Write(buf, binary.BigEndian, uint32(len(catData)))
buf.Write(catData)
}
return buf.Bytes()
}
func TestGetAirouDetails_Empty(t *testing.T) {
// Zero cats
data := []byte{0x00}
bf := byteframe.NewByteFrameFromBytes(data)
cats := GetAirouDetails(bf)
if len(cats) != 0 {
t.Errorf("Expected 0 cats, got %d", len(cats))
}
}
func TestGetAirouDetails_SingleCat(t *testing.T) {
input := Airou{
ID: 42,
Name: []byte("TestCat"),
Task: 4,
Personality: 3,
Class: 2,
Experience: 1500,
WeaponType: 6,
WeaponID: 100,
}
data := buildCatBytes([]Airou{input})
bf := byteframe.NewByteFrameFromBytes(data)
cats := GetAirouDetails(bf)
if len(cats) != 1 {
t.Fatalf("Expected 1 cat, got %d", len(cats))
}
cat := cats[0]
if cat.ID != 42 {
t.Errorf("ID = %d, want 42", cat.ID)
}
if cat.Task != 4 {
t.Errorf("Task = %d, want 4", cat.Task)
}
if cat.Personality != 3 {
t.Errorf("Personality = %d, want 3", cat.Personality)
}
if cat.Class != 2 {
t.Errorf("Class = %d, want 2", cat.Class)
}
if cat.Experience != 1500 {
t.Errorf("Experience = %d, want 1500", cat.Experience)
}
if cat.WeaponType != 6 {
t.Errorf("WeaponType = %d, want 6", cat.WeaponType)
}
if cat.WeaponID != 100 {
t.Errorf("WeaponID = %d, want 100", cat.WeaponID)
}
// Name should be 18 bytes (padded with nulls)
if len(cat.Name) != 18 {
t.Errorf("Name length = %d, want 18", len(cat.Name))
}
// First bytes should match "TestCat"
if !bytes.HasPrefix(cat.Name, []byte("TestCat")) {
t.Errorf("Name does not start with 'TestCat', got %v", cat.Name)
}
}
func TestGetAirouDetails_MultipleCats(t *testing.T) {
inputs := []Airou{
{ID: 1, Name: []byte("Alpha"), Task: 1, Personality: 0, Class: 0, Experience: 100, WeaponType: 6, WeaponID: 10},
{ID: 2, Name: []byte("Beta"), Task: 2, Personality: 1, Class: 1, Experience: 200, WeaponType: 6, WeaponID: 20},
{ID: 3, Name: []byte("Gamma"), Task: 4, Personality: 2, Class: 2, Experience: 300, WeaponType: 6, WeaponID: 30},
}
data := buildCatBytes(inputs)
bf := byteframe.NewByteFrameFromBytes(data)
cats := GetAirouDetails(bf)
if len(cats) != 3 {
t.Fatalf("Expected 3 cats, got %d", len(cats))
}
for i, cat := range cats {
if cat.ID != inputs[i].ID {
t.Errorf("Cat %d: CatID = %d, want %d", i, cat.ID, inputs[i].ID)
}
if cat.Task != inputs[i].Task {
t.Errorf("Cat %d: CurrentTask = %d, want %d", i, cat.Task, inputs[i].Task)
}
if cat.Experience != inputs[i].Experience {
t.Errorf("Cat %d: Experience = %d, want %d", i, cat.Experience, inputs[i].Experience)
}
if cat.WeaponID != inputs[i].WeaponID {
t.Errorf("Cat %d: WeaponID = %d, want %d", i, cat.WeaponID, inputs[i].WeaponID)
}
}
}
func TestGetAirouDetails_ExtraTrailingBytes(t *testing.T) {
// The GetAirouDetails function handles extra bytes by seeking to catStart+catDefLen.
// Simulate a cat definition with extra trailing bytes by increasing catDefLen.
buf := new(bytes.Buffer)
buf.WriteByte(1) // catCount = 1
catBuf := new(bytes.Buffer)
binary.Write(catBuf, binary.BigEndian, uint32(99)) // catID
catBuf.WriteByte(0) // skip
catBuf.Write(make([]byte, 18)) // name
catBuf.WriteByte(3) // currentTask
catBuf.Write(make([]byte, 16)) // appearance skip
catBuf.WriteByte(1) // personality
catBuf.WriteByte(2) // class
catBuf.Write(make([]byte, 5)) // affection skip
binary.Write(catBuf, binary.BigEndian, uint32(500)) // experience
catBuf.WriteByte(0) // weapon equipped bool
catBuf.WriteByte(6) // weaponType
binary.Write(catBuf, binary.BigEndian, uint16(50)) // weaponID
catData := catBuf.Bytes()
// Add 10 extra trailing bytes
extra := make([]byte, 10)
catDataWithExtra := append(catData, extra...)
binary.Write(buf, binary.BigEndian, uint32(len(catDataWithExtra)))
buf.Write(catDataWithExtra)
bf := byteframe.NewByteFrameFromBytes(buf.Bytes())
cats := GetAirouDetails(bf)
if len(cats) != 1 {
t.Fatalf("Expected 1 cat, got %d", len(cats))
}
if cats[0].ID != 99 {
t.Errorf("ID = %d, want 99", cats[0].ID)
}
if cats[0].Experience != 500 {
t.Errorf("Experience = %d, want 500", cats[0].Experience)
}
}
func TestGetAirouDetails_CatNamePadding(t *testing.T) {
// Verify that names shorter than 18 bytes are correctly padded with null bytes.
input := Airou{
ID: 1,
Name: []byte("Hi"),
}
data := buildCatBytes([]Airou{input})
bf := byteframe.NewByteFrameFromBytes(data)
cats := GetAirouDetails(bf)
if len(cats) != 1 {
t.Fatalf("Expected 1 cat, got %d", len(cats))
}
if len(cats[0].Name) != 18 {
t.Errorf("Name length = %d, want 18", len(cats[0].Name))
}
// "Hi" followed by null bytes
if cats[0].Name[0] != 'H' || cats[0].Name[1] != 'i' {
t.Errorf("Name first bytes = %v, want 'Hi...'", cats[0].Name[:2])
}
}
// TestHandleMsgMhfMercenaryHuntdata_Unk0_1 tests with Unk0=1 (returns 1 byte)
func TestHandleMsgMhfMercenaryHuntdata_Unk0_1(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
AckHandle: 12345,
Unk0: 1,
}
handleMsgMhfMercenaryHuntdata(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")
}
}
// TestHandleMsgMhfMercenaryHuntdata_Unk0_0 tests with Unk0=0 (returns 0 bytes payload)
func TestHandleMsgMhfMercenaryHuntdata_Unk0_0(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfMercenaryHuntdata{
AckHandle: 12345,
Unk0: 0,
}
handleMsgMhfMercenaryHuntdata(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")
}
}
// TestHandleMsgMhfEnumerateMercenaryLog tests the mercenary log enumeration handler
func TestHandleMsgMhfEnumerateMercenaryLog(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEnumerateMercenaryLog{
AckHandle: 12345,
}
handleMsgMhfEnumerateMercenaryLog(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")
}
}

View File

@@ -0,0 +1,601 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
// Test handlers with simple responses
func TestHandleMsgMhfGetEarthStatus(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetEarthStatus{
AckHandle: 12345,
}
handleMsgMhfGetEarthStatus(session, pkt)
select {
case p := <-session.sendPackets:
if p.data == nil {
t.Error("Response packet data should not be nil")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfGetEarthValue_Type1(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetEarthValue{
AckHandle: 12345,
ReqType: 1,
}
handleMsgMhfGetEarthValue(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 TestHandleMsgMhfGetEarthValue_Type2(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetEarthValue{
AckHandle: 12345,
ReqType: 2,
}
handleMsgMhfGetEarthValue(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 TestHandleMsgMhfGetEarthValue_Type3(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetEarthValue{
AckHandle: 12345,
ReqType: 3,
}
handleMsgMhfGetEarthValue(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 TestHandleMsgMhfGetEarthValue_UnknownType(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetEarthValue{
AckHandle: 12345,
ReqType: 99, // Unknown type
}
handleMsgMhfGetEarthValue(session, pkt)
select {
case p := <-session.sendPackets:
// Should still return a response (empty values)
if p.data == nil {
t.Error("Response packet data should not be nil")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfReadBeatLevel(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReadBeatLevel{
AckHandle: 12345,
ValidIDCount: 2,
IDs: [16]uint32{1, 2},
}
handleMsgMhfReadBeatLevel(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 TestHandleMsgMhfReadBeatLevel_NoIDs(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReadBeatLevel{
AckHandle: 12345,
ValidIDCount: 0,
IDs: [16]uint32{},
}
handleMsgMhfReadBeatLevel(session, pkt)
select {
case p := <-session.sendPackets:
if p.data == nil {
t.Error("Response packet data should not be nil")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfUpdateBeatLevel(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfUpdateBeatLevel{
AckHandle: 12345,
}
handleMsgMhfUpdateBeatLevel(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 empty handlers don't panic
func TestHandleMsgMhfStampcardPrize(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfStampcardPrize panicked: %v", r)
}
}()
handleMsgMhfStampcardPrize(session, nil)
}
func TestHandleMsgMhfUnreserveSrg(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfUnreserveSrg{
AckHandle: 12345,
}
handleMsgMhfUnreserveSrg(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 TestHandleMsgMhfReadBeatLevelAllRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReadBeatLevelAllRanking{
AckHandle: 12345,
}
handleMsgMhfReadBeatLevelAllRanking(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 TestHandleMsgMhfReadBeatLevelMyRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReadBeatLevelMyRanking{
AckHandle: 12345,
}
handleMsgMhfReadBeatLevelMyRanking(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 TestHandleMsgMhfReadLastWeekBeatRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfReadLastWeekBeatRanking{
AckHandle: 12345,
}
handleMsgMhfReadLastWeekBeatRanking(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 TestHandleMsgMhfGetFixedSeibatuRankingTable(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetFixedSeibatuRankingTable{
AckHandle: 12345,
}
handleMsgMhfGetFixedSeibatuRankingTable(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 TestHandleMsgMhfKickExportForce(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfKickExportForce panicked: %v", r)
}
}()
handleMsgMhfKickExportForce(session, nil)
}
func TestHandleMsgMhfRegistSpabiTime(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfRegistSpabiTime panicked: %v", r)
}
}()
handleMsgMhfRegistSpabiTime(session, nil)
}
func TestHandleMsgMhfDebugPostValue(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfDebugPostValue panicked: %v", r)
}
}()
handleMsgMhfDebugPostValue(session, nil)
}
func TestHandleMsgMhfGetCogInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetCogInfo panicked: %v", r)
}
}()
handleMsgMhfGetCogInfo(session, nil)
}
// Additional handler tests for coverage
func TestHandleMsgMhfGetNotice(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetNotice{
AckHandle: 12345,
}
handleMsgMhfGetNotice(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 TestHandleMsgMhfPostNotice(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostNotice{
AckHandle: 12345,
}
handleMsgMhfPostNotice(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 TestHandleMsgMhfGetRandFromTable(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRandFromTable{
AckHandle: 12345,
Results: 3,
}
handleMsgMhfGetRandFromTable(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 TestHandleMsgMhfGetSenyuDailyCount(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetSenyuDailyCount{
AckHandle: 12345,
}
handleMsgMhfGetSenyuDailyCount(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 TestHandleMsgMhfGetSeibattle(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetSeibattle{
AckHandle: 12345,
}
handleMsgMhfGetSeibattle(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 TestHandleMsgMhfPostSeibattle(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostSeibattle{
AckHandle: 12345,
}
handleMsgMhfPostSeibattle(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 TestHandleMsgMhfGetDailyMissionMaster(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetDailyMissionMaster panicked: %v", r)
}
}()
handleMsgMhfGetDailyMissionMaster(session, nil)
}
func TestHandleMsgMhfGetDailyMissionPersonal(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetDailyMissionPersonal panicked: %v", r)
}
}()
handleMsgMhfGetDailyMissionPersonal(session, nil)
}
func TestHandleMsgMhfSetDailyMissionPersonal(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfSetDailyMissionPersonal panicked: %v", r)
}
}()
handleMsgMhfSetDailyMissionPersonal(session, nil)
}
func TestHandleMsgMhfGetUdShopCoin(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdShopCoin{
AckHandle: 12345,
}
handleMsgMhfGetUdShopCoin(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 TestHandleMsgMhfUseUdShopCoin(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfUseUdShopCoin panicked: %v", r)
}
}()
handleMsgMhfUseUdShopCoin(session, nil)
}
func TestHandleMsgMhfGetLobbyCrowd(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetLobbyCrowd{
AckHandle: 12345,
}
handleMsgMhfGetLobbyCrowd(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")
}
}
// Distribution struct tests
func TestDistributionStruct(t *testing.T) {
dist := Distribution{
ID: 1,
MinHR: 1,
MaxHR: 999,
MinSR: 0,
MaxSR: 999,
MinGR: 0,
MaxGR: 999,
TimesAcceptable: 1,
TimesAccepted: 0,
EventName: "Test Event",
Description: "Test Description",
Selection: false,
}
if dist.ID != 1 {
t.Errorf("ID = %d, want 1", dist.ID)
}
if dist.EventName != "Test Event" {
t.Errorf("EventName = %s, want Test Event", dist.EventName)
}
}
func TestDistributionItemStruct(t *testing.T) {
item := DistributionItem{
ItemType: 1,
ID: 100,
ItemID: 1234,
Quantity: 10,
}
if item.ItemType != 1 {
t.Errorf("ItemType = %d, want 1", item.ItemType)
}
if item.ItemID != 1234 {
t.Errorf("ItemID = %d, want 1234", item.ItemID)
}
}

View File

@@ -0,0 +1,77 @@
package channelserver
import (
"testing"
)
// Test that all mutex handlers don't panic (they are empty implementations)
func TestHandleMsgSysCreateMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysCreateMutex panicked: %v", r)
}
}()
handleMsgSysCreateMutex(session, nil)
}
func TestHandleMsgSysCreateOpenMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysCreateOpenMutex panicked: %v", r)
}
}()
handleMsgSysCreateOpenMutex(session, nil)
}
func TestHandleMsgSysDeleteMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDeleteMutex panicked: %v", r)
}
}()
handleMsgSysDeleteMutex(session, nil)
}
func TestHandleMsgSysOpenMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysOpenMutex panicked: %v", r)
}
}()
handleMsgSysOpenMutex(session, nil)
}
func TestHandleMsgSysCloseMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysCloseMutex panicked: %v", r)
}
}()
handleMsgSysCloseMutex(session, nil)
}

View File

@@ -0,0 +1,372 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgSysCreateObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Create a stage for the session
stage := NewStage("test_stage")
session.stage = stage
pkt := &mhfpacket.MsgSysCreateObject{
AckHandle: 12345,
X: 100.0,
Y: 50.0,
Z: -25.0,
Unk0: 0,
}
handleMsgSysCreateObject(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")
}
// Verify object was created in stage
if len(stage.objects) != 1 {
t.Errorf("Stage should have 1 object, got %d", len(stage.objects))
}
}
func TestHandleMsgSysCreateObject_MultipleObjects(t *testing.T) {
server := createMockServer()
// Create multiple sessions that create objects
sessions := make([]*Session, 3)
stage := NewStage("test_stage")
for i := 0; i < 3; i++ {
sessions[i] = createMockSession(uint32(i+1), server)
sessions[i].stage = stage
pkt := &mhfpacket.MsgSysCreateObject{
AckHandle: uint32(12345 + i),
X: float32(i * 10),
Y: float32(i * 20),
Z: float32(i * 30),
}
handleMsgSysCreateObject(sessions[i], pkt)
// Drain send queue
select {
case <-sessions[i].sendPackets:
default:
}
}
// All objects should exist
if len(stage.objects) != 3 {
t.Errorf("Stage should have 3 objects, got %d", len(stage.objects))
}
}
func TestHandleMsgSysPositionObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Create a stage with an existing object
stage := NewStage("test_stage")
session.stage = stage
// Add another session to receive broadcast
session2 := createMockSession(2, server)
session2.stage = stage
stage.clients[session] = session.charID
stage.clients[session2] = session2.charID
// Create an object
stage.objects[session.charID] = &Object{
id: 1,
ownerCharID: session.charID,
x: 0,
y: 0,
z: 0,
}
pkt := &mhfpacket.MsgSysPositionObject{
ObjID: 1,
X: 100.0,
Y: 200.0,
Z: 300.0,
}
handleMsgSysPositionObject(session, pkt)
// Verify object position was updated
obj := stage.objects[session.charID]
if obj.x != 100.0 || obj.y != 200.0 || obj.z != 300.0 {
t.Errorf("Object position not updated: got (%f, %f, %f), want (100, 200, 300)",
obj.x, obj.y, obj.z)
}
// Verify broadcast was sent to session2
select {
case <-session2.sendPackets:
// Good - broadcast received
default:
t.Error("Position update should be broadcast to other sessions")
}
}
func TestHandleMsgSysPositionObject_NoObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
stage := NewStage("test_stage")
session.stage = stage
stage.clients[session] = session.charID
// Position update for non-existent object - should not panic
pkt := &mhfpacket.MsgSysPositionObject{
ObjID: 999,
X: 100.0,
Y: 200.0,
Z: 300.0,
}
// Should not panic
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysPositionObject panicked with non-existent object: %v", r)
}
}()
handleMsgSysPositionObject(session, pkt)
}
func TestHandleMsgSysDeleteObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDeleteObject panicked: %v", r)
}
}()
handleMsgSysDeleteObject(session, nil)
}
func TestHandleMsgSysRotateObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysRotateObject panicked: %v", r)
}
}()
handleMsgSysRotateObject(session, nil)
}
func TestHandleMsgSysDuplicateObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDuplicateObject panicked: %v", r)
}
}()
handleMsgSysDuplicateObject(session, nil)
}
func TestHandleMsgSysGetObjectBinary(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysGetObjectBinary panicked: %v", r)
}
}()
handleMsgSysGetObjectBinary(session, nil)
}
func TestHandleMsgSysGetObjectOwner(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysGetObjectOwner panicked: %v", r)
}
}()
handleMsgSysGetObjectOwner(session, nil)
}
func TestHandleMsgSysUpdateObjectBinary(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysUpdateObjectBinary panicked: %v", r)
}
}()
handleMsgSysUpdateObjectBinary(session, nil)
}
func TestHandleMsgSysCleanupObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysCleanupObject panicked: %v", r)
}
}()
handleMsgSysCleanupObject(session, nil)
}
func TestHandleMsgSysAddObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysAddObject panicked: %v", r)
}
}()
handleMsgSysAddObject(session, nil)
}
func TestHandleMsgSysDelObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDelObject panicked: %v", r)
}
}()
handleMsgSysDelObject(session, nil)
}
func TestHandleMsgSysDispObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDispObject panicked: %v", r)
}
}()
handleMsgSysDispObject(session, nil)
}
func TestHandleMsgSysHideObject(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysHideObject panicked: %v", r)
}
}()
handleMsgSysHideObject(session, nil)
}
func TestObjectHandlers_SequentialCreateObject(t *testing.T) {
server := createMockServer()
stage := NewStage("test_stage")
// Create objects sequentially from multiple sessions
// Note: handleMsgSysCreateObject has a race condition in NextObjectID
// so we test sequential creation instead
for i := 0; i < 10; i++ {
session := createMockSession(uint32(i), server)
session.stage = stage
pkt := &mhfpacket.MsgSysCreateObject{
AckHandle: uint32(i),
X: float32(i),
Y: float32(i * 2),
Z: float32(i * 3),
}
handleMsgSysCreateObject(session, pkt)
// Drain send queue
select {
case <-session.sendPackets:
default:
}
}
// All objects should be created
if len(stage.objects) != 10 {
t.Errorf("Expected 10 objects, got %d", len(stage.objects))
}
}
func TestObjectHandlers_SequentialPositionUpdate(t *testing.T) {
server := createMockServer()
stage := NewStage("test_stage")
session := createMockSession(1, server)
session.stage = stage
stage.clients[session] = session.charID
// Create an object
stage.objects[session.charID] = &Object{
id: 1,
ownerCharID: session.charID,
x: 0,
y: 0,
z: 0,
}
// Sequentially update object position
for i := 0; i < 10; i++ {
pkt := &mhfpacket.MsgSysPositionObject{
ObjID: 1,
X: float32(i),
Y: float32(i * 2),
Z: float32(i * 3),
}
handleMsgSysPositionObject(session, pkt)
}
// Verify final position
obj := stage.objects[session.charID]
if obj.x != 9 || obj.y != 18 || obj.z != 27 {
t.Errorf("Object position not as expected: got (%f, %f, %f), want (9, 18, 27)",
obj.x, obj.y, obj.z)
}
}

View File

@@ -0,0 +1,128 @@
package channelserver
import (
"encoding/binary"
"testing"
_config "erupe-ce/config"
)
func TestBackportQuest_Basic(t *testing.T) {
// Set up config for the test
oldConfig := _config.ErupeConfig
defer func() { _config.ErupeConfig = oldConfig }()
_config.ErupeConfig = &_config.Config{}
_config.ErupeConfig.RealClientMode = _config.ZZ
// Create a quest data buffer large enough for BackportQuest to work with.
// The function reads a uint32 from data[0:4] as offset, then works at offset+96.
// We need at least offset + 96 + 108 + 6*8 bytes.
// Set offset (wp base) = 0, so wp starts at 96, rp at 100.
data := make([]byte, 512)
binary.LittleEndian.PutUint32(data[0:4], 0) // offset = 0
// Fill some data at the rp positions so we can verify copies
for i := 100; i < 400; i++ {
data[i] = byte(i & 0xFF)
}
result := BackportQuest(data)
if result == nil {
t.Fatal("BackportQuest returned nil")
}
if len(result) != len(data) {
t.Errorf("BackportQuest changed data length: got %d, want %d", len(result), len(data))
}
}
func TestBackportQuest_S6Mode(t *testing.T) {
oldConfig := _config.ErupeConfig
defer func() { _config.ErupeConfig = oldConfig }()
_config.ErupeConfig = &_config.Config{}
_config.ErupeConfig.RealClientMode = _config.S6
data := make([]byte, 512)
binary.LittleEndian.PutUint32(data[0:4], 0)
for i := 0; i < len(data); i++ {
data[i+4] = byte(i % 256)
if i+4 >= len(data)-1 {
break
}
}
// Set some values at data[8:12] so we can check they get copied to data[16:20]
binary.LittleEndian.PutUint32(data[8:12], 0xDEADBEEF)
result := BackportQuest(data)
if result == nil {
t.Fatal("BackportQuest returned nil")
}
// In S6 mode, data[16:20] should be copied from data[8:12]
got := binary.LittleEndian.Uint32(result[16:20])
if got != 0xDEADBEEF {
t.Errorf("S6 mode: data[16:20] = 0x%X, want 0xDEADBEEF", got)
}
}
func TestBackportQuest_G91Mode_PatternReplacement(t *testing.T) {
oldConfig := _config.ErupeConfig
defer func() { _config.ErupeConfig = oldConfig }()
_config.ErupeConfig = &_config.Config{}
_config.ErupeConfig.RealClientMode = _config.G91
data := make([]byte, 512)
binary.LittleEndian.PutUint32(data[0:4], 0)
// Insert an armor sphere pattern at a known location
// Pattern: 0x0A, 0x00, 0x01, 0x33 -> should replace bytes at +2 with 0xD7, 0x00
offset := 300
data[offset] = 0x0A
data[offset+1] = 0x00
data[offset+2] = 0x01
data[offset+3] = 0x33
result := BackportQuest(data)
// After BackportQuest, the pattern's last 2 bytes should be replaced
if result[offset+2] != 0xD7 || result[offset+3] != 0x00 {
t.Errorf("G91 pattern replacement failed: got [0x%X, 0x%X], want [0xD7, 0x00]",
result[offset+2], result[offset+3])
}
}
func TestBackportQuest_F5Mode(t *testing.T) {
oldConfig := _config.ErupeConfig
defer func() { _config.ErupeConfig = oldConfig }()
_config.ErupeConfig = &_config.Config{}
_config.ErupeConfig.RealClientMode = _config.F5
data := make([]byte, 512)
binary.LittleEndian.PutUint32(data[0:4], 0)
result := BackportQuest(data)
if result == nil {
t.Fatal("BackportQuest returned nil")
}
}
func TestBackportQuest_G101Mode(t *testing.T) {
oldConfig := _config.ErupeConfig
defer func() { _config.ErupeConfig = oldConfig }()
_config.ErupeConfig = &_config.Config{}
_config.ErupeConfig.RealClientMode = _config.G101
data := make([]byte, 512)
binary.LittleEndian.PutUint32(data[0:4], 0)
result := BackportQuest(data)
if result == nil {
t.Fatal("BackportQuest returned nil")
}
}

View File

@@ -0,0 +1,53 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetRengokuRankingRank(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRengokuRankingRank{
AckHandle: 12345,
}
handleMsgMhfGetRengokuRankingRank(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")
}
}
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)
}
}

View File

@@ -0,0 +1,113 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestReserveHandlersWithAck(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Test handleMsgSysReserve188
handleMsgSysReserve188(session, &mhfpacket.MsgSysReserve188{AckHandle: 12345})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Reserve188: response should have data")
}
default:
t.Error("Reserve188: no response queued")
}
// Test handleMsgSysReserve18B
handleMsgSysReserve18B(session, &mhfpacket.MsgSysReserve18B{AckHandle: 12345})
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Reserve18B: response should have data")
}
default:
t.Error("Reserve18B: no response queued")
}
}
func TestReserveEmptyHandlers(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
handler func(s *Session, p mhfpacket.MHFPacket)
pkt mhfpacket.MHFPacket
}{
{"Reserve55", handleMsgSysReserve55, &mhfpacket.MsgSysReserve55{}},
{"Reserve56", handleMsgSysReserve56, &mhfpacket.MsgSysReserve56{}},
{"Reserve57", handleMsgSysReserve57, &mhfpacket.MsgSysReserve57{}},
{"Reserve01", handleMsgSysReserve01, &mhfpacket.MsgSysReserve01{}},
{"Reserve02", handleMsgSysReserve02, &mhfpacket.MsgSysReserve02{}},
{"Reserve03", handleMsgSysReserve03, &mhfpacket.MsgSysReserve03{}},
{"Reserve04", handleMsgSysReserve04, &mhfpacket.MsgSysReserve04{}},
{"Reserve05", handleMsgSysReserve05, &mhfpacket.MsgSysReserve05{}},
{"Reserve06", handleMsgSysReserve06, &mhfpacket.MsgSysReserve06{}},
{"Reserve07", handleMsgSysReserve07, &mhfpacket.MsgSysReserve07{}},
{"Reserve0C", handleMsgSysReserve0C, &mhfpacket.MsgSysReserve0C{}},
{"Reserve0D", handleMsgSysReserve0D, &mhfpacket.MsgSysReserve0D{}},
{"Reserve0E", handleMsgSysReserve0E, &mhfpacket.MsgSysReserve0E{}},
{"Reserve4A", handleMsgSysReserve4A, &mhfpacket.MsgSysReserve4A{}},
{"Reserve4B", handleMsgSysReserve4B, &mhfpacket.MsgSysReserve4B{}},
{"Reserve4C", handleMsgSysReserve4C, &mhfpacket.MsgSysReserve4C{}},
{"Reserve4D", handleMsgSysReserve4D, &mhfpacket.MsgSysReserve4D{}},
{"Reserve4E", handleMsgSysReserve4E, &mhfpacket.MsgSysReserve4E{}},
{"Reserve4F", handleMsgSysReserve4F, &mhfpacket.MsgSysReserve4F{}},
{"Reserve5C", handleMsgSysReserve5C, &mhfpacket.MsgSysReserve5C{}},
{"Reserve5E", handleMsgSysReserve5E, &mhfpacket.MsgSysReserve5E{}},
{"Reserve5F", handleMsgSysReserve5F, &mhfpacket.MsgSysReserve5F{}},
{"Reserve71", handleMsgSysReserve71, &mhfpacket.MsgSysReserve71{}},
{"Reserve72", handleMsgSysReserve72, &mhfpacket.MsgSysReserve72{}},
{"Reserve73", handleMsgSysReserve73, &mhfpacket.MsgSysReserve73{}},
{"Reserve74", handleMsgSysReserve74, &mhfpacket.MsgSysReserve74{}},
{"Reserve75", handleMsgSysReserve75, &mhfpacket.MsgSysReserve75{}},
{"Reserve76", handleMsgSysReserve76, &mhfpacket.MsgSysReserve76{}},
{"Reserve77", handleMsgSysReserve77, &mhfpacket.MsgSysReserve77{}},
{"Reserve78", handleMsgSysReserve78, &mhfpacket.MsgSysReserve78{}},
{"Reserve79", handleMsgSysReserve79, &mhfpacket.MsgSysReserve79{}},
{"Reserve7A", handleMsgSysReserve7A, &mhfpacket.MsgSysReserve7A{}},
{"Reserve7B", handleMsgSysReserve7B, &mhfpacket.MsgSysReserve7B{}},
{"Reserve7C", handleMsgSysReserve7C, &mhfpacket.MsgSysReserve7C{}},
{"Reserve7E", handleMsgSysReserve7E, &mhfpacket.MsgSysReserve7E{}},
{"Reserve10F", handleMsgMhfReserve10F, &mhfpacket.MsgMhfReserve10F{}},
{"Reserve180", handleMsgSysReserve180, &mhfpacket.MsgSysReserve180{}},
{"Reserve18E", handleMsgSysReserve18E, &mhfpacket.MsgSysReserve18E{}},
{"Reserve18F", handleMsgSysReserve18F, &mhfpacket.MsgSysReserve18F{}},
{"Reserve19E", handleMsgSysReserve19E, &mhfpacket.MsgSysReserve19E{}},
{"Reserve19F", handleMsgSysReserve19F, &mhfpacket.MsgSysReserve19F{}},
{"Reserve1A4", handleMsgSysReserve1A4, &mhfpacket.MsgSysReserve1A4{}},
{"Reserve1A6", handleMsgSysReserve1A6, &mhfpacket.MsgSysReserve1A6{}},
{"Reserve1A7", handleMsgSysReserve1A7, &mhfpacket.MsgSysReserve1A7{}},
{"Reserve1A8", handleMsgSysReserve1A8, &mhfpacket.MsgSysReserve1A8{}},
{"Reserve1A9", handleMsgSysReserve1A9, &mhfpacket.MsgSysReserve1A9{}},
{"Reserve1AA", handleMsgSysReserve1AA, &mhfpacket.MsgSysReserve1AA{}},
{"Reserve1AB", handleMsgSysReserve1AB, &mhfpacket.MsgSysReserve1AB{}},
{"Reserve1AC", handleMsgSysReserve1AC, &mhfpacket.MsgSysReserve1AC{}},
{"Reserve1AD", handleMsgSysReserve1AD, &mhfpacket.MsgSysReserve1AD{}},
{"Reserve1AE", handleMsgSysReserve1AE, &mhfpacket.MsgSysReserve1AE{}},
{"Reserve1AF", handleMsgSysReserve1AF, &mhfpacket.MsgSysReserve1AF{}},
{"Reserve19B", handleMsgSysReserve19B, &mhfpacket.MsgSysReserve19B{}},
{"Reserve192", handleMsgSysReserve192, &mhfpacket.MsgSysReserve192{}},
{"Reserve193", handleMsgSysReserve193, &mhfpacket.MsgSysReserve193{}},
{"Reserve194", handleMsgSysReserve194, &mhfpacket.MsgSysReserve194{}},
}
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, tt.pkt)
})
}
}

View File

@@ -0,0 +1,126 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetAdditionalBeatReward(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetAdditionalBeatReward{
AckHandle: 12345,
}
handleMsgMhfGetAdditionalBeatReward(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 TestHandleMsgMhfGetUdRankingRewardList(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdRankingRewardList{
AckHandle: 12345,
}
handleMsgMhfGetUdRankingRewardList(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 TestHandleMsgMhfGetRewardSong(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetRewardSong{
AckHandle: 12345,
}
handleMsgMhfGetRewardSong(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 TestHandleMsgMhfUseRewardSong(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfUseRewardSong panicked: %v", r)
}
}()
handleMsgMhfUseRewardSong(session, nil)
}
func TestHandleMsgMhfAddRewardSongCount(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfAddRewardSongCount panicked: %v", r)
}
}()
handleMsgMhfAddRewardSongCount(session, nil)
}
func TestHandleMsgMhfAcquireMonthlyReward(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireMonthlyReward{
AckHandle: 12345,
}
handleMsgMhfAcquireMonthlyReward(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 TestHandleMsgMhfAcceptReadReward(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfAcceptReadReward panicked: %v", r)
}
}()
handleMsgMhfAcceptReadReward(session, nil)
}

View File

@@ -0,0 +1,447 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgSysCreateSemaphore(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysCreateSemaphore{
AckHandle: 12345,
Unk0: 0,
}
handleMsgSysCreateSemaphore(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")
}
}
func TestHandleMsgSysDeleteSemaphore_NoSemaphores(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysDeleteSemaphore{
SemaphoreID: 12345,
}
// Should not panic when no semaphores exist
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDeleteSemaphore panicked: %v", r)
}
}()
handleMsgSysDeleteSemaphore(session, pkt)
}
func TestHandleMsgSysDeleteSemaphore_WithSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create a semaphore
sema := NewSemaphore(session, "test_sema", 4)
server.semaphore["test_sema"] = sema
pkt := &mhfpacket.MsgSysDeleteSemaphore{
SemaphoreID: sema.id,
}
handleMsgSysDeleteSemaphore(session, pkt)
// Semaphore should be deleted
if _, exists := server.semaphore["test_sema"]; exists {
t.Error("Semaphore should be deleted")
}
}
func TestHandleMsgSysCreateAcquireSemaphore_NewSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 12345,
Unk0: 0,
PlayerCount: 4,
SemaphoreID: "test_semaphore",
}
handleMsgSysCreateAcquireSemaphore(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")
}
// Verify semaphore was created
if _, exists := server.semaphore["test_semaphore"]; !exists {
t.Error("Semaphore should be created")
}
}
func TestHandleMsgSysCreateAcquireSemaphore_ExistingSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Pre-create semaphore
sema := NewSemaphore(session, "existing_sema", 4)
server.semaphore["existing_sema"] = sema
pkt := &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 12345,
Unk0: 0,
PlayerCount: 4,
SemaphoreID: "existing_sema",
}
handleMsgSysCreateAcquireSemaphore(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")
}
// Verify client was added to semaphore
if len(sema.clients) == 0 {
t.Error("Session should be added to semaphore")
}
}
func TestHandleMsgSysCreateAcquireSemaphore_RavienteSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Test raviente semaphore (special prefix)
pkt := &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 12345,
Unk0: 0,
PlayerCount: 32,
SemaphoreID: "hs_l0u3B51", // Raviente prefix + suffix
}
handleMsgSysCreateAcquireSemaphore(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")
}
// Verify raviente semaphore was created with special settings
if sema, exists := server.semaphore["hs_l0u3B51"]; !exists {
t.Error("Raviente semaphore should be created")
} else if sema.maxPlayers != 127 {
t.Errorf("Raviente semaphore maxPlayers = %d, want 127", sema.maxPlayers)
}
}
func TestHandleMsgSysCreateAcquireSemaphore_Full(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
// Create semaphore with 1 player max
session1 := createMockSession(1, server)
sema := NewSemaphore(session1, "full_sema", 1)
server.semaphore["full_sema"] = sema
// Fill the semaphore
sema.clients[session1] = session1.charID
// Try to acquire with another session
session2 := createMockSession(2, server)
pkt := &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: 12345,
Unk0: 0,
PlayerCount: 1,
SemaphoreID: "full_sema",
}
handleMsgSysCreateAcquireSemaphore(session2, pkt)
// Should still respond (with failure indication)
select {
case p := <-session2.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data even for full semaphore")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgSysAcquireSemaphore_Exists(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create semaphore
sema := NewSemaphore(session, "acquire_test", 4)
server.semaphore["acquire_test"] = sema
pkt := &mhfpacket.MsgSysAcquireSemaphore{
AckHandle: 12345,
SemaphoreID: "acquire_test",
}
handleMsgSysAcquireSemaphore(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")
}
// Verify host was set
if sema.host != session {
t.Error("Session should be set as semaphore host")
}
}
func TestHandleMsgSysAcquireSemaphore_NotExists(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysAcquireSemaphore{
AckHandle: 12345,
SemaphoreID: "nonexistent",
}
handleMsgSysAcquireSemaphore(session, pkt)
// Should respond with 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")
}
}
func TestHandleMsgSysReleaseSemaphore(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (mostly empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysReleaseSemaphore panicked: %v", r)
}
}()
pkt := &mhfpacket.MsgSysReleaseSemaphore{}
handleMsgSysReleaseSemaphore(session, pkt)
}
func TestHandleMsgSysCheckSemaphore_Exists(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create semaphore
sema := NewSemaphore(session, "check_test", 4)
server.semaphore["check_test"] = sema
pkt := &mhfpacket.MsgSysCheckSemaphore{
AckHandle: 12345,
SemaphoreID: "check_test",
}
handleMsgSysCheckSemaphore(session, pkt)
// Verify response indicates semaphore exists
select {
case p := <-session.sendPackets:
if len(p.data) < 4 {
t.Error("Response packet should have at least 4 bytes")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgSysCheckSemaphore_NotExists(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysCheckSemaphore{
AckHandle: 12345,
SemaphoreID: "nonexistent",
}
handleMsgSysCheckSemaphore(session, pkt)
// Verify response indicates semaphore does not exist
select {
case p := <-session.sendPackets:
if len(p.data) < 4 {
t.Error("Response packet should have at least 4 bytes")
}
default:
t.Error("No response packet queued")
}
}
func TestRemoveSessionFromSemaphore(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create semaphore and add session
sema := NewSemaphore(session, "remove_test", 4)
sema.clients[session] = session.charID
server.semaphore["remove_test"] = sema
// Remove session
removeSessionFromSemaphore(session)
// Verify session was removed
if _, exists := sema.clients[session]; exists {
t.Error("Session should be removed from clients")
}
}
func TestRemoveSessionFromSemaphore_MultipleSemaphores(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create multiple semaphores with the session
for i := 0; i < 3; i++ {
sema := NewSemaphore(session, "multi_test_"+string(rune('a'+i)), 4)
sema.clients[session] = session.charID
server.semaphore["multi_test_"+string(rune('a'+i))] = sema
}
// Remove session from all
removeSessionFromSemaphore(session)
// Verify session was removed from all semaphores
for _, sema := range server.semaphore {
if _, exists := sema.clients[session]; exists {
t.Error("Session should be removed from all semaphore clients")
}
}
}
func TestDestructEmptySemaphores(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
session := createMockSession(1, server)
// Create empty semaphore
sema := NewSemaphore(session, "empty_sema", 4)
server.semaphore["empty_sema"] = sema
// Create non-empty semaphore
semaWithClients := NewSemaphore(session, "with_clients", 4)
semaWithClients.clients[session] = session.charID
server.semaphore["with_clients"] = semaWithClients
destructEmptySemaphores(session)
// Empty semaphore should be deleted
if _, exists := server.semaphore["empty_sema"]; exists {
t.Error("Empty semaphore should be deleted")
}
// Non-empty semaphore should remain
if _, exists := server.semaphore["with_clients"]; !exists {
t.Error("Non-empty semaphore should remain")
}
}
func TestSemaphoreHandlers_SequentialAcquire(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
// Sequentially try to create/acquire the same semaphore
// Note: the handler has race conditions when accessed concurrently
for i := 0; i < 5; i++ {
session := createMockSession(uint32(i), server)
pkt := &mhfpacket.MsgSysCreateAcquireSemaphore{
AckHandle: uint32(i),
Unk0: 0,
PlayerCount: 4,
SemaphoreID: "sequential_test",
}
handleMsgSysCreateAcquireSemaphore(session, pkt)
// Drain send queue
select {
case <-session.sendPackets:
default:
}
}
// Semaphore should exist
if _, exists := server.semaphore["sequential_test"]; !exists {
t.Error("Semaphore should exist after sequential acquires")
}
}
func TestSemaphoreHandlers_MultipleCheck(t *testing.T) {
server := createMockServer()
server.semaphore = make(map[string]*Semaphore)
// Create semaphore
helperSession := createMockSession(99, server)
sema := NewSemaphore(helperSession, "check_multiple", 4)
server.semaphore["check_multiple"] = sema
// Check the semaphore from multiple sessions sequentially
for i := 0; i < 5; i++ {
session := createMockSession(uint32(i), server)
pkt := &mhfpacket.MsgSysCheckSemaphore{
AckHandle: uint32(i),
SemaphoreID: "check_multiple",
}
handleMsgSysCheckSemaphore(session, pkt)
// Drain send queue
select {
case <-session.sendPackets:
default:
}
}
}

View File

@@ -0,0 +1,408 @@
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)
}
}
func TestGetRandomEntries_ZeroRolls(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0},
}
result, err := getRandomEntries(entries, 0, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 0 {
t.Errorf("expected 0 results, got %d", len(result))
}
}
func TestGetRandomEntries_SingleEntryNonBox(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0, ItemNumber: 100},
}
result, err := getRandomEntries(entries, 3, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 3 {
t.Errorf("expected 3 results, got %d", len(result))
}
for i, r := range result {
if r.ID != 1 {
t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
}
}
}
func TestGetRandomEntries_NonBoxAllowsDuplicates(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0},
}
result, err := getRandomEntries(entries, 5, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 5 {
t.Errorf("expected 5 results, got %d", len(result))
}
// All should be the same since there's only one entry
for i, r := range result {
if r.ID != 1 {
t.Errorf("result[%d].ID = %d, expected 1", i, r.ID)
}
}
}
func TestGetRandomEntries_BoxModeRemovesSelected(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0},
{ID: 2, Weight: 1.0},
{ID: 3, Weight: 1.0},
}
result, err := getRandomEntries(entries, 3, true)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 3 {
t.Errorf("expected 3 results, got %d", len(result))
}
// In box mode, all entries should be unique
seen := make(map[uint32]bool)
for _, r := range result {
if seen[r.ID] {
t.Errorf("duplicate entry in box mode: ID=%d", r.ID)
}
seen[r.ID] = true
}
}
func TestGetRandomEntries_BoxModeMatchingCount(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0},
{ID: 2, Weight: 1.0},
}
result, err := getRandomEntries(entries, 2, true)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 2 {
t.Errorf("expected 2 results, got %d", len(result))
}
// Should contain both entries exactly once
seen := make(map[uint32]bool)
for _, r := range result {
seen[r.ID] = true
}
if !seen[1] || !seen[2] {
t.Errorf("box mode should return all entries when rolls == len(entries)")
}
}
func TestGetRandomEntries_WeightedSelectionBias(t *testing.T) {
// Test that weighted selection respects weights
entries := []GachaEntry{
{ID: 1, Weight: 100.0}, // Very high weight
{ID: 2, Weight: 0.001}, // Very low weight
}
// Run many iterations
counts := make(map[uint32]int)
for i := 0; i < 1000; i++ {
result, _ := getRandomEntries(entries, 1, false)
if len(result) > 0 {
counts[result[0].ID]++
}
}
// ID 1 should be selected much more often
if counts[1] <= counts[2] {
t.Errorf("weighted selection not working: high weight count=%d, low weight count=%d",
counts[1], counts[2])
}
}
func TestGetRandomEntries_MultipleEntriesMultipleRolls(t *testing.T) {
entries := []GachaEntry{
{ID: 1, Weight: 1.0},
{ID: 2, Weight: 1.0},
{ID: 3, Weight: 1.0},
}
result, err := getRandomEntries(entries, 10, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 10 {
t.Errorf("expected 10 results, got %d", len(result))
}
// All results should have valid IDs
for i, r := range result {
if r.ID < 1 || r.ID > 3 {
t.Errorf("result[%d].ID = %d, expected 1, 2, or 3", i, r.ID)
}
}
}
func TestGetRandomEntries_PreservesEntryData(t *testing.T) {
entries := []GachaEntry{
{
ID: 1,
Weight: 1.0,
ItemNumber: 100,
ItemQuantity: 5,
Rarity: 3,
FrontierPoints: 500,
},
}
result, err := getRandomEntries(entries, 1, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(result) != 1 {
t.Fatalf("expected 1 result, got %d", len(result))
}
r := result[0]
if r.ItemNumber != 100 {
t.Errorf("ItemNumber = %d, expected 100", r.ItemNumber)
}
if r.ItemQuantity != 5 {
t.Errorf("ItemQuantity = %d, expected 5", r.ItemQuantity)
}
if r.Rarity != 3 {
t.Errorf("Rarity = %d, expected 3", r.Rarity)
}
if r.FrontierPoints != 500 {
t.Errorf("FrontierPoints = %d, expected 500", r.FrontierPoints)
}
}

View File

@@ -0,0 +1,313 @@
package channelserver
import (
"testing"
"time"
"erupe-ce/network/mhfpacket"
)
// Test simple handler patterns that don't require database
func TestHandlerMsgMhfSexChanger(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfSexChanger{
AckHandle: 12345,
}
// Should not panic
handleMsgMhfSexChanger(session, pkt)
// Should queue a response
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
func TestHandlerMsgMhfEnterTournamentQuest(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic with nil packet (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfEnterTournamentQuest panicked: %v", r)
}
}()
handleMsgMhfEnterTournamentQuest(session, nil)
}
func TestHandlerMsgMhfGetUdBonusQuestInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdBonusQuestInfo{
AckHandle: 12345,
}
handleMsgMhfGetUdBonusQuestInfo(session, pkt)
// Should queue a response
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
// Test that acknowledge handlers work correctly
func TestAckResponseFormats(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
handler func(s *Session, ackHandle uint32, data []byte)
}{
{"doAckBufSucceed", doAckBufSucceed},
{"doAckBufFail", doAckBufFail},
{"doAckSimpleSucceed", doAckSimpleSucceed},
{"doAckSimpleFail", doAckSimpleFail},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
testData := []byte{0x01, 0x02, 0x03, 0x04}
tt.handler(session, 99999, testData)
select {
case pkt := <-session.sendPackets:
if pkt.data == nil {
t.Error("Packet data should not be nil")
}
default:
t.Error("Handler should queue a packet")
}
})
}
}
func TestStubHandlers(t *testing.T) {
server := createMockServer()
tests := []struct {
name string
handler func(s *Session, ackHandle uint32)
}{
{"stubEnumerateNoResults", stubEnumerateNoResults},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
session := createMockSession(1, server)
tt.handler(session, 12345)
select {
case pkt := <-session.sendPackets:
if pkt.data == nil {
t.Error("Packet data should not be nil")
}
default:
t.Error("Stub handler should queue a packet")
}
})
}
}
// Test packet queueing
func TestSessionQueueSendMHF(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgSysAck{
AckHandle: 12345,
IsBufferResponse: false,
ErrorCode: 0,
AckData: []byte{0x00},
}
session.QueueSendMHF(pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Queued packet should have data")
}
default:
t.Error("QueueSendMHF should queue a packet")
}
}
func TestSessionQueueSendNonBlocking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
data := []byte{0x01, 0x02, 0x03, 0x04}
session.QueueSendNonBlocking(data)
select {
case p := <-session.sendPackets:
if len(p.data) != 4 {
t.Errorf("Queued data len = %d, want 4", len(p.data))
}
if p.nonBlocking != true {
t.Error("Packet should be marked as non-blocking")
}
default:
t.Error("QueueSendNonBlocking should queue data")
}
}
func TestSessionQueueSendNonBlocking_FullQueue(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Fill the queue
for i := 0; i < 20; i++ {
session.sendPackets <- packet{data: []byte{byte(i)}, nonBlocking: true}
}
// Non-blocking send should not block when queue is full
// It should drop the packet instead
done := make(chan bool, 1)
go func() {
session.QueueSendNonBlocking([]byte{0xFF})
done <- true
}()
// Wait for completion with a reasonable timeout
// The function should return immediately (dropping the packet)
select {
case <-done:
// Good - didn't block, function completed
case <-time.After(100 * time.Millisecond):
t.Error("QueueSendNonBlocking blocked on full queue")
}
}
// Additional handler tests for coverage
func TestHandleMsgMhfGetGuildWeeklyBonusMaster(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetGuildWeeklyBonusMaster{
AckHandle: 12345,
}
handleMsgMhfGetGuildWeeklyBonusMaster(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfGetGuildWeeklyBonusActiveCount(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount{
AckHandle: 12345,
}
handleMsgMhfGetGuildWeeklyBonusActiveCount(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
func TestHandleMsgMhfAddGuildWeeklyBonusExceptionalUser(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAddGuildWeeklyBonusExceptionalUser{
AckHandle: 12345,
}
handleMsgMhfAddGuildWeeklyBonusExceptionalUser(session, pkt)
select {
case p := <-session.sendPackets:
if len(p.data) == 0 {
t.Error("Response packet should have data")
}
default:
t.Error("No response packet queued")
}
}
func TestEmptyHandlers_NoDb(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Test handlers that are empty and should not panic
tests := []struct {
name string
handler func(s *Session, p mhfpacket.MHFPacket)
}{
{"handleMsgHead", handleMsgHead},
{"handleMsgSysExtendThreshold", handleMsgSysExtendThreshold},
{"handleMsgSysEnd", handleMsgSysEnd},
{"handleMsgSysNop", handleMsgSysNop},
{"handleMsgSysAck", handleMsgSysAck},
{"handleMsgSysUpdateRight", handleMsgSysUpdateRight},
{"handleMsgSysAuthQuery", handleMsgSysAuthQuery},
{"handleMsgSysAuthTerminal", handleMsgSysAuthTerminal},
{"handleMsgCaExchangeItem", handleMsgCaExchangeItem},
{"handleMsgMhfServerCommand", handleMsgMhfServerCommand},
{"handleMsgMhfSetLoginwindow", handleMsgMhfSetLoginwindow},
{"handleMsgSysTransBinary", handleMsgSysTransBinary},
{"handleMsgSysCollectBinary", handleMsgSysCollectBinary},
{"handleMsgSysGetState", handleMsgSysGetState},
{"handleMsgSysSerialize", handleMsgSysSerialize},
{"handleMsgSysEnumlobby", handleMsgSysEnumlobby},
{"handleMsgSysEnumuser", handleMsgSysEnumuser},
{"handleMsgSysInfokyserver", handleMsgSysInfokyserver},
{"handleMsgMhfGetCaUniqueID", handleMsgMhfGetCaUniqueID},
{"handleMsgMhfGetExtraInfo", handleMsgMhfGetExtraInfo},
{"handleMsgMhfGetCogInfo", handleMsgMhfGetCogInfo},
{"handleMsgMhfStampcardPrize", handleMsgMhfStampcardPrize},
{"handleMsgMhfKickExportForce", handleMsgMhfKickExportForce},
{"handleMsgSysSetStatus", handleMsgSysSetStatus},
{"handleMsgSysEcho", handleMsgSysEcho},
{"handleMsgMhfUseUdShopCoin", handleMsgMhfUseUdShopCoin},
{"handleMsgMhfEnterTournamentQuest", handleMsgMhfEnterTournamentQuest},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.handler(session, nil)
})
}
}

View File

@@ -0,0 +1,193 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetUdTacticsPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsPoint{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsPoint(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 TestHandleMsgMhfAddUdTacticsPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAddUdTacticsPoint{
AckHandle: 12345,
}
handleMsgMhfAddUdTacticsPoint(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 TestHandleMsgMhfGetUdTacticsRewardList(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsRewardList{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsRewardList(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 TestHandleMsgMhfGetUdTacticsFollower(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsFollower{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsFollower(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 TestHandleMsgMhfGetUdTacticsBonusQuest(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsBonusQuest{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsBonusQuest(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 TestHandleMsgMhfGetUdTacticsFirstQuestBonus(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsFirstQuestBonus{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsFirstQuestBonus(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 TestHandleMsgMhfGetUdTacticsRemainingPoint(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsRemainingPoint{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsRemainingPoint(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 TestHandleMsgMhfGetUdTacticsRanking(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetUdTacticsRanking{
AckHandle: 12345,
}
handleMsgMhfGetUdTacticsRanking(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 TestHandleMsgMhfSetUdTacticsFollower(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfSetUdTacticsFollower panicked: %v", r)
}
}()
handleMsgMhfSetUdTacticsFollower(session, nil)
}
func TestHandleMsgMhfGetUdTacticsLog(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgMhfGetUdTacticsLog panicked: %v", r)
}
}()
handleMsgMhfGetUdTacticsLog(session, nil)
}

View File

@@ -0,0 +1,268 @@
package channelserver
import (
"testing"
"erupe-ce/network"
)
func TestHandlerTableInitialized(t *testing.T) {
if handlerTable == nil {
t.Fatal("handlerTable should be initialized by init()")
}
}
func TestHandlerTableHasEntries(t *testing.T) {
if len(handlerTable) == 0 {
t.Error("handlerTable should have entries")
}
// Should have many handlers
if len(handlerTable) < 100 {
t.Errorf("handlerTable has %d entries, expected 100+", len(handlerTable))
}
}
func TestHandlerTableSystemPackets(t *testing.T) {
// Test that key system packets have handlers
systemPackets := []network.PacketID{
network.MSG_HEAD,
network.MSG_SYS_END,
network.MSG_SYS_NOP,
network.MSG_SYS_ACK,
network.MSG_SYS_LOGIN,
network.MSG_SYS_LOGOUT,
network.MSG_SYS_PING,
network.MSG_SYS_TIME,
}
for _, opcode := range systemPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for %s", opcode)
}
})
}
}
func TestHandlerTableStagePackets(t *testing.T) {
// Test stage-related packet handlers
stagePackets := []network.PacketID{
network.MSG_SYS_CREATE_STAGE,
network.MSG_SYS_STAGE_DESTRUCT,
network.MSG_SYS_ENTER_STAGE,
network.MSG_SYS_BACK_STAGE,
network.MSG_SYS_MOVE_STAGE,
network.MSG_SYS_LEAVE_STAGE,
network.MSG_SYS_LOCK_STAGE,
network.MSG_SYS_UNLOCK_STAGE,
}
for _, opcode := range stagePackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for stage packet %s", opcode)
}
})
}
}
func TestHandlerTableBinaryPackets(t *testing.T) {
// Test binary message handlers
binaryPackets := []network.PacketID{
network.MSG_SYS_CAST_BINARY,
network.MSG_SYS_CASTED_BINARY,
network.MSG_SYS_SET_STAGE_BINARY,
network.MSG_SYS_GET_STAGE_BINARY,
}
for _, opcode := range binaryPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for binary packet %s", opcode)
}
})
}
}
func TestHandlerTableReservedPackets(t *testing.T) {
// Reserved packets should still have handlers (usually no-ops)
reservedPackets := []network.PacketID{
network.MSG_SYS_reserve01,
network.MSG_SYS_reserve02,
network.MSG_SYS_reserve03,
network.MSG_SYS_reserve04,
network.MSG_SYS_reserve05,
network.MSG_SYS_reserve06,
network.MSG_SYS_reserve07,
}
for _, opcode := range reservedPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for reserved packet %s", opcode)
}
})
}
}
func TestHandlerFuncType(t *testing.T) {
// Verify all handlers are valid functions
for opcode, handler := range handlerTable {
if handler == nil {
t.Errorf("handler for %s is nil", opcode)
}
}
}
func TestHandlerTableObjectPackets(t *testing.T) {
objectPackets := []network.PacketID{
network.MSG_SYS_ADD_OBJECT,
network.MSG_SYS_DEL_OBJECT,
network.MSG_SYS_DISP_OBJECT,
network.MSG_SYS_HIDE_OBJECT,
}
for _, opcode := range objectPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for object packet %s", opcode)
}
})
}
}
func TestHandlerTableClientPackets(t *testing.T) {
clientPackets := []network.PacketID{
network.MSG_SYS_SET_STATUS,
network.MSG_SYS_HIDE_CLIENT,
network.MSG_SYS_ENUMERATE_CLIENT,
}
for _, opcode := range clientPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for client packet %s", opcode)
}
})
}
}
func TestHandlerTableSemaphorePackets(t *testing.T) {
semaphorePackets := []network.PacketID{
network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE,
network.MSG_SYS_ACQUIRE_SEMAPHORE,
network.MSG_SYS_RELEASE_SEMAPHORE,
}
for _, opcode := range semaphorePackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for semaphore packet %s", opcode)
}
})
}
}
func TestHandlerTableMHFPackets(t *testing.T) {
// Test some core MHF packets have handlers
mhfPackets := []network.PacketID{
network.MSG_MHF_SAVEDATA,
network.MSG_MHF_LOADDATA,
}
for _, opcode := range mhfPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for MHF packet %s", opcode)
}
})
}
}
func TestHandlerTableEnumeratePackets(t *testing.T) {
enumPackets := []network.PacketID{
network.MSG_SYS_ENUMERATE_CLIENT,
network.MSG_SYS_ENUMERATE_STAGE,
}
for _, opcode := range enumPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for enumerate packet %s", opcode)
}
})
}
}
func TestHandlerTableLogPackets(t *testing.T) {
logPackets := []network.PacketID{
network.MSG_SYS_TERMINAL_LOG,
network.MSG_SYS_ISSUE_LOGKEY,
network.MSG_SYS_RECORD_LOG,
}
for _, opcode := range logPackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for log packet %s", opcode)
}
})
}
}
func TestHandlerTableFilePackets(t *testing.T) {
filePackets := []network.PacketID{
network.MSG_SYS_GET_FILE,
}
for _, opcode := range filePackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for file packet %s", opcode)
}
})
}
}
func TestHandlerTableEchoPacket(t *testing.T) {
if _, ok := handlerTable[network.MSG_SYS_ECHO]; !ok {
t.Error("handler missing for MSG_SYS_ECHO")
}
}
func TestHandlerTableReserveStagePackets(t *testing.T) {
reservePackets := []network.PacketID{
network.MSG_SYS_RESERVE_STAGE,
network.MSG_SYS_UNRESERVE_STAGE,
network.MSG_SYS_SET_STAGE_PASS,
network.MSG_SYS_WAIT_STAGE_BINARY,
}
for _, opcode := range reservePackets {
t.Run(opcode.String(), func(t *testing.T) {
if _, ok := handlerTable[opcode]; !ok {
t.Errorf("handler missing for reserve stage packet %s", opcode)
}
})
}
}
func TestHandlerTableThresholdPacket(t *testing.T) {
if _, ok := handlerTable[network.MSG_SYS_EXTEND_THRESHOLD]; !ok {
t.Error("handler missing for MSG_SYS_EXTEND_THRESHOLD")
}
}
func TestHandlerTableNoNilValues(t *testing.T) {
nilCount := 0
for opcode, handler := range handlerTable {
if handler == nil {
nilCount++
t.Errorf("nil handler for opcode %s", opcode)
}
}
if nilCount > 0 {
t.Errorf("found %d nil handlers in handlerTable", nilCount)
}
}

View File

@@ -0,0 +1,91 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfInfoTournament_Type0(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfInfoTournament{
AckHandle: 12345,
Unk0: 0,
}
handleMsgMhfInfoTournament(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")
}
}
func TestHandleMsgMhfInfoTournament_Type1(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfInfoTournament{
AckHandle: 12345,
Unk0: 1,
}
handleMsgMhfInfoTournament(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")
}
}
func TestHandleMsgMhfEntryTournament(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfEntryTournament{
AckHandle: 12345,
}
handleMsgMhfEntryTournament(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 TestHandleMsgMhfAcquireTournament(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfAcquireTournament{
AckHandle: 12345,
}
handleMsgMhfAcquireTournament(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")
}
}

View File

@@ -0,0 +1,156 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgMhfGetTenrouirai_Type1(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetTenrouirai{
AckHandle: 12345,
Unk0: 1,
}
handleMsgMhfGetTenrouirai(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")
}
}
func TestHandleMsgMhfGetTenrouirai_Default(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetTenrouirai{
AckHandle: 12345,
Unk0: 0,
Unk1: 0,
}
handleMsgMhfGetTenrouirai(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")
}
}
func TestHandleMsgMhfPostTowerInfo(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostTowerInfo{
AckHandle: 12345,
}
handleMsgMhfPostTowerInfo(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")
}
}
func TestHandleMsgMhfPostTenrouirai(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPostTenrouirai{
AckHandle: 12345,
}
handleMsgMhfPostTenrouirai(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")
}
}
func TestHandleMsgMhfGetBreakSeibatuLevelReward(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetBreakSeibatuLevelReward{
AckHandle: 12345,
}
handleMsgMhfGetBreakSeibatuLevelReward(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 TestHandleMsgMhfGetWeeklySeibatuRankingReward(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfGetWeeklySeibatuRankingReward{
AckHandle: 12345,
}
handleMsgMhfGetWeeklySeibatuRankingReward(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")
}
}
func TestHandleMsgMhfPresentBox(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfPresentBox{
AckHandle: 12345,
}
handleMsgMhfPresentBox(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")
}
}

View File

@@ -0,0 +1,128 @@
package channelserver
import (
"testing"
"erupe-ce/network/mhfpacket"
)
func TestHandleMsgSysInsertUser(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysInsertUser panicked: %v", r)
}
}()
handleMsgSysInsertUser(session, nil)
}
func TestHandleMsgSysDeleteUser(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysDeleteUser panicked: %v", r)
}
}()
handleMsgSysDeleteUser(session, nil)
}
func TestHandleMsgSysNotifyUserBinary(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should not panic (empty handler)
defer func() {
if r := recover(); r != nil {
t.Errorf("handleMsgSysNotifyUserBinary panicked: %v", r)
}
}()
handleMsgSysNotifyUserBinary(session, nil)
}
func TestHandleMsgSysGetUserBinary_FromCache(t *testing.T) {
server := createMockServer()
server.userBinaryParts = make(map[userBinaryPartID][]byte)
session := createMockSession(1, server)
// Pre-populate cache
key := userBinaryPartID{charID: 100, index: 1}
server.userBinaryParts[key] = []byte{0x01, 0x02, 0x03, 0x04}
pkt := &mhfpacket.MsgSysGetUserBinary{
AckHandle: 12345,
CharID: 100,
BinaryType: 1,
}
handleMsgSysGetUserBinary(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")
}
}
func TestHandleMsgSysGetUserBinary_NotInCache(t *testing.T) {
server := createMockServer()
server.userBinaryParts = make(map[userBinaryPartID][]byte)
session := createMockSession(1, server)
// Don't populate cache - will fall back to DB (which is nil in test)
pkt := &mhfpacket.MsgSysGetUserBinary{
AckHandle: 12345,
CharID: 100,
BinaryType: 1,
}
// This will panic when trying to access nil db, which is expected
// in the test environment without database setup
defer func() {
if r := recover(); r != nil {
// Expected - no database in test
t.Log("Expected panic due to nil database in test")
}
}()
handleMsgSysGetUserBinary(session, pkt)
}
func TestUserBinaryPartID_AsMapKey(t *testing.T) {
// Test that userBinaryPartID works as map key
parts := make(map[userBinaryPartID][]byte)
key1 := userBinaryPartID{charID: 1, index: 0}
key2 := userBinaryPartID{charID: 1, index: 1}
key3 := userBinaryPartID{charID: 2, index: 0}
parts[key1] = []byte{0x01}
parts[key2] = []byte{0x02}
parts[key3] = []byte{0x03}
if len(parts) != 3 {
t.Errorf("Expected 3 parts, got %d", len(parts))
}
if parts[key1][0] != 0x01 {
t.Error("Key1 data mismatch")
}
if parts[key2][0] != 0x02 {
t.Error("Key2 data mismatch")
}
if parts[key3][0] != 0x03 {
t.Error("Key3 data mismatch")
}
}

View File

@@ -0,0 +1,208 @@
package channelserver
import (
"testing"
)
func TestStubEnumerateNoResults(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Call stubEnumerateNoResults - it queues a packet
stubEnumerateNoResults(session, 12345)
// Verify packet was queued
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty")
}
default:
t.Error("No packet was queued")
}
}
func TestDoAckBufSucceed(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
testData := []byte{0x01, 0x02, 0x03, 0x04}
doAckBufSucceed(session, 12345, testData)
// Verify packet was queued
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty")
}
default:
t.Error("No packet was queued")
}
}
func TestDoAckBufFail(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
testData := []byte{0x01, 0x02, 0x03, 0x04}
doAckBufFail(session, 12345, testData)
// Verify packet was queued
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty")
}
default:
t.Error("No packet was queued")
}
}
func TestDoAckSimpleSucceed(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
testData := []byte{0x00, 0x00, 0x00, 0x00}
doAckSimpleSucceed(session, 12345, testData)
// Verify packet was queued
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty")
}
default:
t.Error("No packet was queued")
}
}
func TestDoAckSimpleFail(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
testData := []byte{0x00, 0x00, 0x00, 0x00}
doAckSimpleFail(session, 12345, testData)
// Verify packet was queued
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty")
}
default:
t.Error("No packet was queued")
}
}
func TestDoAck_EmptyData(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should work with empty data
doAckBufSucceed(session, 0, []byte{})
select {
case pkt := <-session.sendPackets:
// Empty data is valid
_ = pkt
default:
t.Error("No packet was queued with empty data")
}
}
func TestDoAck_NilData(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Should work with nil data
doAckBufSucceed(session, 0, nil)
select {
case pkt := <-session.sendPackets:
// Nil data is valid
_ = pkt
default:
t.Error("No packet was queued with nil data")
}
}
func TestDoAck_LargeData(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Test with large data
largeData := make([]byte, 65536)
for i := range largeData {
largeData[i] = byte(i % 256)
}
doAckBufSucceed(session, 99999, largeData)
select {
case pkt := <-session.sendPackets:
if len(pkt.data) == 0 {
t.Error("Packet data should not be empty for large data")
}
default:
t.Error("No packet was queued with large data")
}
}
func TestDoAck_AckHandleZero(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Test with ack handle 0
doAckSimpleSucceed(session, 0, []byte{0x00})
select {
case pkt := <-session.sendPackets:
_ = pkt
default:
t.Error("No packet was queued with zero ack handle")
}
}
func TestDoAck_AckHandleMax(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
// Test with max uint32 ack handle
doAckSimpleSucceed(session, 0xFFFFFFFF, []byte{0x00})
select {
case pkt := <-session.sendPackets:
_ = pkt
default:
t.Error("No packet was queued with max ack handle")
}
}
// Test that handlers don't panic with empty packets
func TestEmptyHandlers(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
tests := []struct {
name string
handler func(s *Session, p interface{})
}{
{"handleMsgHead", func(s *Session, p interface{}) { handleMsgHead(s, nil) }},
{"handleMsgSysExtendThreshold", func(s *Session, p interface{}) { handleMsgSysExtendThreshold(s, nil) }},
{"handleMsgSysEnd", func(s *Session, p interface{}) { handleMsgSysEnd(s, nil) }},
{"handleMsgSysNop", func(s *Session, p interface{}) { handleMsgSysNop(s, nil) }},
{"handleMsgSysAck", func(s *Session, p interface{}) { handleMsgSysAck(s, nil) }},
{"handleMsgSysAuthData", func(s *Session, p interface{}) { handleMsgSysAuthData(s, nil) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s panicked: %v", tt.name, r)
}
}()
tt.handler(session, nil)
})
}
}

View File

@@ -0,0 +1,94 @@
package channelserver
import (
"testing"
_config "erupe-ce/config"
)
func TestGetLangStrings_English(t *testing.T) {
server := &Server{
erupeConfig: &_config.Config{
Language: "en",
},
}
lang := getLangStrings(server)
if lang.language != "English" {
t.Errorf("language = %q, want %q", lang.language, "English")
}
// Verify key strings are not empty
if lang.cafe.reset == "" {
t.Error("cafe.reset should not be empty")
}
if lang.commands.disabled == "" {
t.Error("commands.disabled should not be empty")
}
if lang.commands.reload == "" {
t.Error("commands.reload should not be empty")
}
if lang.commands.ravi.noCommand == "" {
t.Error("commands.ravi.noCommand should not be empty")
}
if lang.guild.invite.title == "" {
t.Error("guild.invite.title should not be empty")
}
}
func TestGetLangStrings_Japanese(t *testing.T) {
server := &Server{
erupeConfig: &_config.Config{
Language: "jp",
},
}
lang := getLangStrings(server)
if lang.language != "日本語" {
t.Errorf("language = %q, want %q", lang.language, "日本語")
}
// Verify Japanese strings are different from English
enServer := &Server{
erupeConfig: &_config.Config{
Language: "en",
},
}
enLang := getLangStrings(enServer)
if lang.commands.reload == enLang.commands.reload {
t.Error("Japanese commands.reload should be different from English")
}
}
func TestGetLangStrings_DefaultToEnglish(t *testing.T) {
server := &Server{
erupeConfig: &_config.Config{
Language: "unknown_language",
},
}
lang := getLangStrings(server)
// Unknown language should default to English
if lang.language != "English" {
t.Errorf("Unknown language should default to English, got %q", lang.language)
}
}
func TestGetLangStrings_EmptyLanguage(t *testing.T) {
server := &Server{
erupeConfig: &_config.Config{
Language: "",
},
}
lang := getLangStrings(server)
// Empty language should default to English
if lang.language != "English" {
t.Errorf("Empty language should default to English, got %q", lang.language)
}
}

View File

@@ -0,0 +1,322 @@
package channelserver
import (
"sync"
"testing"
)
func TestObjectStruct(t *testing.T) {
obj := &Object{
id: 12345,
ownerCharID: 67890,
x: 100.5,
y: 50.25,
z: -10.0,
}
if obj.id != 12345 {
t.Errorf("Object id = %d, want 12345", obj.id)
}
if obj.ownerCharID != 67890 {
t.Errorf("Object ownerCharID = %d, want 67890", obj.ownerCharID)
}
if obj.x != 100.5 {
t.Errorf("Object x = %f, want 100.5", obj.x)
}
if obj.y != 50.25 {
t.Errorf("Object y = %f, want 50.25", obj.y)
}
if obj.z != -10.0 {
t.Errorf("Object z = %f, want -10.0", obj.z)
}
}
func TestObjectRWMutex(t *testing.T) {
obj := &Object{
id: 1,
ownerCharID: 100,
x: 0,
y: 0,
z: 0,
}
// Test read lock
obj.RLock()
_ = obj.x
obj.RUnlock()
// Test write lock
obj.Lock()
obj.x = 100.0
obj.Unlock()
if obj.x != 100.0 {
t.Errorf("Object x = %f, want 100.0 after write", obj.x)
}
}
func TestObjectConcurrentAccess(t *testing.T) {
obj := &Object{
id: 1,
ownerCharID: 100,
x: 0,
y: 0,
z: 0,
}
var wg sync.WaitGroup
// Concurrent writers
for i := 0; i < 10; i++ {
wg.Add(1)
go func(val float32) {
defer wg.Done()
for j := 0; j < 100; j++ {
obj.Lock()
obj.x = val
obj.y = val
obj.z = val
obj.Unlock()
}
}(float32(i))
}
// Concurrent readers
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
obj.RLock()
_ = obj.x
_ = obj.y
_ = obj.z
obj.RUnlock()
}
}()
}
wg.Wait()
}
func TestStageBinaryKeyStruct(t *testing.T) {
key1 := stageBinaryKey{id0: 1, id1: 2}
key2 := stageBinaryKey{id0: 1, id1: 3}
key3 := stageBinaryKey{id0: 1, id1: 2}
// Different keys
if key1 == key2 {
t.Error("key1 and key2 should be different")
}
// Same keys
if key1 != key3 {
t.Error("key1 and key3 should be equal")
}
}
func TestStageBinaryKeyAsMapKey(t *testing.T) {
data := make(map[stageBinaryKey][]byte)
key1 := stageBinaryKey{id0: 0, id1: 0}
key2 := stageBinaryKey{id0: 0, id1: 1}
key3 := stageBinaryKey{id0: 1, id1: 0}
data[key1] = []byte{0x01}
data[key2] = []byte{0x02}
data[key3] = []byte{0x03}
if len(data) != 3 {
t.Errorf("Expected 3 entries, got %d", len(data))
}
if data[key1][0] != 0x01 {
t.Errorf("data[key1] = 0x%02X, want 0x01", data[key1][0])
}
if data[key2][0] != 0x02 {
t.Errorf("data[key2] = 0x%02X, want 0x02", data[key2][0])
}
if data[key3][0] != 0x03 {
t.Errorf("data[key3] = 0x%02X, want 0x03", data[key3][0])
}
}
func TestNewStageDefaults(t *testing.T) {
stage := NewStage("test_stage_001")
if stage.id != "test_stage_001" {
t.Errorf("stage.id = %s, want test_stage_001", stage.id)
}
if stage.maxPlayers != 127 {
t.Errorf("stage.maxPlayers = %d, want 127 (default)", stage.maxPlayers)
}
if stage.objectIndex != 0 {
t.Errorf("stage.objectIndex = %d, want 0", stage.objectIndex)
}
if stage.clients == nil {
t.Error("stage.clients should be initialized")
}
if stage.reservedClientSlots == nil {
t.Error("stage.reservedClientSlots should be initialized")
}
if stage.objects == nil {
t.Error("stage.objects should be initialized")
}
if stage.rawBinaryData == nil {
t.Error("stage.rawBinaryData should be initialized")
}
if stage.host != nil {
t.Error("stage.host should be nil initially")
}
if stage.password != "" {
t.Errorf("stage.password should be empty, got %s", stage.password)
}
}
func TestStageReservedClientSlots(t *testing.T) {
stage := NewStage("test")
// Reserve some slots
stage.reservedClientSlots[100] = true
stage.reservedClientSlots[200] = false // ready status doesn't matter for presence
stage.reservedClientSlots[300] = true
if len(stage.reservedClientSlots) != 3 {
t.Errorf("reservedClientSlots count = %d, want 3", len(stage.reservedClientSlots))
}
// Check ready status
if !stage.reservedClientSlots[100] {
t.Error("charID 100 should be ready")
}
if stage.reservedClientSlots[200] {
t.Error("charID 200 should not be ready")
}
}
func TestStageRawBinaryData(t *testing.T) {
stage := NewStage("test")
key := stageBinaryKey{id0: 5, id1: 10}
data := []byte{0xDE, 0xAD, 0xBE, 0xEF}
stage.rawBinaryData[key] = data
retrieved := stage.rawBinaryData[key]
if len(retrieved) != 4 {
t.Fatalf("retrieved data len = %d, want 4", len(retrieved))
}
if retrieved[0] != 0xDE || retrieved[3] != 0xEF {
t.Error("retrieved data doesn't match stored data")
}
}
func TestStageObjects(t *testing.T) {
stage := NewStage("test")
obj := &Object{
id: 1,
ownerCharID: 12345,
x: 100.0,
y: 200.0,
z: 300.0,
}
stage.objects[obj.id] = obj
if len(stage.objects) != 1 {
t.Errorf("objects count = %d, want 1", len(stage.objects))
}
retrieved := stage.objects[obj.id]
if retrieved.ownerCharID != 12345 {
t.Errorf("retrieved object ownerCharID = %d, want 12345", retrieved.ownerCharID)
}
}
func TestStageHost(t *testing.T) {
server := createMockServer()
stage := NewStage("test")
// Set host
host := createMockSession(100, server)
stage.host = host
if stage.host != host {
t.Error("stage host not set correctly")
}
if stage.host.charID != 100 {
t.Errorf("stage host charID = %d, want 100", stage.host.charID)
}
}
func TestStagePassword(t *testing.T) {
stage := NewStage("test")
// Set password
stage.password = "secret123"
if stage.password != "secret123" {
t.Errorf("stage password = %s, want secret123", stage.password)
}
}
func TestStageMaxPlayers(t *testing.T) {
stage := NewStage("test")
// Change max players
stage.maxPlayers = 16
if stage.maxPlayers != 16 {
t.Errorf("stage maxPlayers = %d, want 16", stage.maxPlayers)
}
}
func TestStageConcurrentClientAccess(t *testing.T) {
server := createMockServer()
stage := NewStage("test")
var wg sync.WaitGroup
// Concurrent client additions
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 10; j++ {
session := createMockSession(uint32(id*100+j), server)
stage.Lock()
stage.clients[session] = session.charID
stage.Unlock()
stage.Lock()
delete(stage.clients, session)
stage.Unlock()
}
}(i)
}
// Concurrent reads
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 50; j++ {
stage.RLock()
_ = len(stage.clients)
stage.RUnlock()
}
}()
}
wg.Wait()
}
func TestStageBroadcastMHF_EmptyStage(t *testing.T) {
stage := NewStage("test")
pkt := &mockPacket{opcode: 0x1234}
// Should not panic with empty stage
stage.BroadcastMHF(pkt, nil)
}

View File

@@ -0,0 +1,276 @@
package channelserver
import (
"sync"
"testing"
)
func TestNewSemaphore(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test_semaphore", 16)
if sema == nil {
t.Fatal("NewSemaphore() returned nil")
}
if sema.name != "test_semaphore" {
t.Errorf("name = %s, want test_semaphore", sema.name)
}
if sema.maxPlayers != 16 {
t.Errorf("maxPlayers = %d, want 16", sema.maxPlayers)
}
if sema.clients == nil {
t.Error("clients map should be initialized")
}
if sema.host != session {
t.Error("host should be set to the creating session")
}
}
func TestNewSemaphoreIDIncrement(t *testing.T) {
server := createMockServer()
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
session3 := createMockSession(3, server)
sema1 := NewSemaphore(session1, "sema1", 4)
sema2 := NewSemaphore(session2, "sema2", 4)
sema3 := NewSemaphore(session3, "sema3", 4)
// IDs should be set (may or may not be unique depending on session state)
if sema1.id == 0 && sema2.id == 0 && sema3.id == 0 {
t.Error("at least some semaphore IDs should be non-zero")
}
}
func TestSemaphoreClients(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test", 4)
session1 := createMockSession(100, server)
session2 := createMockSession(200, server)
// Add clients
sema.clients[session1] = session1.charID
sema.clients[session2] = session2.charID
if len(sema.clients) != 2 {
t.Errorf("clients count = %d, want 2", len(sema.clients))
}
// Verify client IDs
if sema.clients[session1] != 100 {
t.Errorf("clients[session1] = %d, want 100", sema.clients[session1])
}
if sema.clients[session2] != 200 {
t.Errorf("clients[session2] = %d, want 200", sema.clients[session2])
}
}
func TestSemaphoreRemoveClient(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test", 4)
clientSession := createMockSession(100, server)
sema.clients[clientSession] = clientSession.charID
// Remove client
delete(sema.clients, clientSession)
if len(sema.clients) != 0 {
t.Errorf("clients count = %d, want 0 after delete", len(sema.clients))
}
}
func TestSemaphoreMaxPlayers(t *testing.T) {
tests := []struct {
name string
maxPlayers uint16
}{
{"quest party", 4},
{"small event", 16},
{"raviente", 32},
{"large event", 64},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, tt.name, tt.maxPlayers)
if sema.maxPlayers != tt.maxPlayers {
t.Errorf("maxPlayers = %d, want %d", sema.maxPlayers, tt.maxPlayers)
}
})
}
}
func TestSemaphoreBroadcastMHF(t *testing.T) {
server := createMockServer()
hostSession := createMockSession(1, server)
sema := NewSemaphore(hostSession, "test", 4)
session1 := createMockSession(100, server)
session2 := createMockSession(200, server)
session3 := createMockSession(300, server)
sema.clients[session1] = session1.charID
sema.clients[session2] = session2.charID
sema.clients[session3] = session3.charID
pkt := &mockPacket{opcode: 0x1234}
// Broadcast excluding session1
sema.BroadcastMHF(pkt, session1)
// session2 and session3 should receive
select {
case data := <-session2.sendPackets:
if len(data.data) == 0 {
t.Error("session2 received empty data")
}
default:
t.Error("session2 did not receive broadcast")
}
select {
case data := <-session3.sendPackets:
if len(data.data) == 0 {
t.Error("session3 received empty data")
}
default:
t.Error("session3 did not receive broadcast")
}
// session1 should NOT receive (it was ignored)
select {
case <-session1.sendPackets:
t.Error("session1 should not receive broadcast (it was ignored)")
default:
// Expected - no data for session1
}
}
func TestSemaphoreBroadcastToAll(t *testing.T) {
server := createMockServer()
hostSession := createMockSession(1, server)
sema := NewSemaphore(hostSession, "test", 4)
session1 := createMockSession(100, server)
session2 := createMockSession(200, server)
sema.clients[session1] = session1.charID
sema.clients[session2] = session2.charID
pkt := &mockPacket{opcode: 0x1234}
// Broadcast to all (nil ignored session)
sema.BroadcastMHF(pkt, nil)
// Both should receive
count := 0
select {
case <-session1.sendPackets:
count++
default:
}
select {
case <-session2.sendPackets:
count++
default:
}
if count != 2 {
t.Errorf("expected 2 broadcasts, got %d", count)
}
}
func TestSemaphoreRWMutex(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test", 4)
// Test that RWMutex works
sema.RLock()
_ = len(sema.clients) // Read operation
sema.RUnlock()
sema.Lock()
sema.clients[createMockSession(100, server)] = 100 // Write operation
sema.Unlock()
}
func TestSemaphoreConcurrentAccess(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test", 100)
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 < 100; j++ {
s := createMockSession(uint32(id*100+j), server)
sema.Lock()
sema.clients[s] = s.charID
sema.Unlock()
sema.Lock()
delete(sema.clients, s)
sema.Unlock()
}
}(i)
}
// Concurrent readers
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
sema.RLock()
_ = len(sema.clients)
sema.RUnlock()
}
}()
}
wg.Wait()
}
func TestSemaphoreEmptyBroadcast(t *testing.T) {
server := createMockServer()
session := createMockSession(1, server)
sema := NewSemaphore(session, "test", 4)
pkt := &mockPacket{opcode: 0x1234}
// Should not panic with no clients
sema.BroadcastMHF(pkt, nil)
}
func TestSemaphoreNameString(t *testing.T) {
server := createMockServer()
tests := []string{
"quest_001",
"raviente_phase1",
"tournament_round3",
"diva_defense",
}
for _, id := range tests {
session := createMockSession(1, server)
sema := NewSemaphore(session, id, 4)
if sema.name != id {
t.Errorf("name = %s, want %s", sema.name, id)
}
}
}

View File

@@ -0,0 +1,290 @@
package channelserver
import (
"sync"
"testing"
)
func TestStageBroadcastMHF(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
// Add some sessions
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
session3 := createMockSession(3, server)
stage.clients[session1] = session1.charID
stage.clients[session2] = session2.charID
stage.clients[session3] = session3.charID
pkt := &mockPacket{opcode: 0x1234}
// Should not panic
stage.BroadcastMHF(pkt, session1)
// Verify session2 and session3 received data
select {
case data := <-session2.sendPackets:
if len(data.data) == 0 {
t.Error("session2 received empty data")
}
default:
t.Error("session2 did not receive data")
}
select {
case data := <-session3.sendPackets:
if len(data.data) == 0 {
t.Error("session3 received empty data")
}
default:
t.Error("session3 did not receive data")
}
}
func TestStageBroadcastMHF_NilClientContext(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
session2.clientContext = nil // Simulate corrupted session
stage.clients[session1] = session1.charID
stage.clients[session2] = session2.charID
pkt := &mockPacket{opcode: 0x1234}
// This should panic with the current implementation
defer func() {
if r := recover(); r != nil {
t.Logf("Caught expected panic: %v", r)
// Test passes - we've confirmed the bug exists
} else {
t.Log("No panic occurred - either the bug is fixed or test is wrong")
}
}()
stage.BroadcastMHF(pkt, nil)
}
// TestStageBroadcastMHF_ConcurrentModificationWithLock tests that proper locking
// prevents the race condition between BroadcastMHF and session removal
func TestStageBroadcastMHF_ConcurrentModificationWithLock(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
// Create many sessions
sessions := make([]*Session, 100)
for i := range sessions {
sessions[i] = createMockSession(uint32(i), server)
stage.clients[sessions[i]] = sessions[i].charID
}
pkt := &mockPacket{opcode: 0x1234}
var wg sync.WaitGroup
// Start goroutines that broadcast
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
stage.BroadcastMHF(pkt, nil)
}
}()
}
// Start goroutines that remove sessions WITH proper locking
// This simulates the fixed logoutPlayer behavior
for i := 0; i < 10; i++ {
wg.Add(1)
idx := i * 10
go func(startIdx int) {
defer wg.Done()
for j := 0; j < 10; j++ {
sessionIdx := startIdx + j
if sessionIdx < len(sessions) {
// Fixed: modifying stage.clients WITH lock
stage.Lock()
delete(stage.clients, sessions[sessionIdx])
stage.Unlock()
}
}
}(idx)
}
wg.Wait()
}
// TestStageBroadcastMHF_RaceDetectorWithLock verifies no race when
// modifications are done with proper locking
func TestStageBroadcastMHF_RaceDetectorWithLock(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
stage.clients[session1] = session1.charID
stage.clients[session2] = session2.charID
pkt := &mockPacket{opcode: 0x1234}
var wg sync.WaitGroup
// Goroutine 1: Continuously broadcast
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
stage.BroadcastMHF(pkt, nil)
}
}()
// Goroutine 2: Add and remove sessions WITH proper locking
// This simulates the fixed logoutPlayer behavior
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
newSession := createMockSession(uint32(100+i), server)
// Add WITH lock (fixed)
stage.Lock()
stage.clients[newSession] = newSession.charID
stage.Unlock()
// Remove WITH lock (fixed)
stage.Lock()
delete(stage.clients, newSession)
stage.Unlock()
}
}()
wg.Wait()
}
// TestNewStageBasic verifies Stage creation
func TestNewStageBasic(t *testing.T) {
stageID := "test_stage_001"
stage := NewStage(stageID)
if stage == nil {
t.Fatal("NewStage() returned nil")
}
if stage.id != stageID {
t.Errorf("stage.id = %s, want %s", stage.id, stageID)
}
if stage.clients == nil {
t.Error("stage.clients should not be nil")
}
if stage.reservedClientSlots == nil {
t.Error("stage.reservedClientSlots should not be nil")
}
if stage.objects == nil {
t.Error("stage.objects should not be nil")
}
}
// TestStageClientCount tests client counting
func TestStageClientCount(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
if len(stage.clients) != 0 {
t.Errorf("initial client count = %d, want 0", len(stage.clients))
}
// Add clients
session1 := createMockSession(1, server)
session2 := createMockSession(2, server)
stage.clients[session1] = session1.charID
if len(stage.clients) != 1 {
t.Errorf("client count after 1 add = %d, want 1", len(stage.clients))
}
stage.clients[session2] = session2.charID
if len(stage.clients) != 2 {
t.Errorf("client count after 2 adds = %d, want 2", len(stage.clients))
}
// Remove a client
delete(stage.clients, session1)
if len(stage.clients) != 1 {
t.Errorf("client count after 1 remove = %d, want 1", len(stage.clients))
}
}
// TestStageLockUnlock tests stage locking
func TestStageLockUnlock(t *testing.T) {
stage := NewStage("test_stage")
// Test lock/unlock without deadlock
stage.Lock()
stage.password = "test"
stage.Unlock()
stage.RLock()
password := stage.password
stage.RUnlock()
if password != "test" {
t.Error("stage password should be 'test'")
}
}
// TestStageHostSession tests host session tracking
func TestStageHostSession(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
session := createMockSession(1, server)
if stage.host != nil {
t.Error("initial host should be nil")
}
stage.host = session
if stage.host == nil {
t.Error("host should not be nil after setting")
}
if stage.host.charID != 1 {
t.Errorf("host.charID = %d, want 1", stage.host.charID)
}
}
// TestStageMultipleClients tests stage with multiple clients
func TestStageMultipleClients(t *testing.T) {
stage := NewStage("test_stage")
server := createMockServer()
// Add many clients
sessions := make([]*Session, 10)
for i := range sessions {
sessions[i] = createMockSession(uint32(i+1), server)
stage.clients[sessions[i]] = sessions[i].charID
}
if len(stage.clients) != 10 {
t.Errorf("client count = %d, want 10", len(stage.clients))
}
// Verify each client is tracked
for _, s := range sessions {
if _, ok := stage.clients[s]; !ok {
t.Errorf("session with charID %d not found in stage", s.charID)
}
}
}
// TestStageNewMaxPlayers tests default max players
func TestStageNewMaxPlayers(t *testing.T) {
stage := NewStage("test_stage")
// Default max players is 127
if stage.maxPlayers != 127 {
t.Errorf("initial maxPlayers = %d, want 127", stage.maxPlayers)
}
}

View File

@@ -0,0 +1,167 @@
package channelserver
import (
"testing"
"time"
)
func TestTimeAdjusted(t *testing.T) {
result := TimeAdjusted()
// Should return a time in UTC+9 timezone
_, offset := result.Zone()
expectedOffset := 9 * 60 * 60 // 9 hours in seconds
if offset != expectedOffset {
t.Errorf("TimeAdjusted() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
}
// The time should be close to current time (within a few seconds)
now := time.Now()
diff := result.Sub(now.In(time.FixedZone("UTC+9", 9*60*60)))
if diff < -time.Second || diff > time.Second {
t.Errorf("TimeAdjusted() time differs from expected by %v", diff)
}
}
func TestTimeMidnight(t *testing.T) {
midnight := TimeMidnight()
// Should be at midnight (hour=0, minute=0, second=0, nanosecond=0)
if midnight.Hour() != 0 {
t.Errorf("TimeMidnight() hour = %d, want 0", midnight.Hour())
}
if midnight.Minute() != 0 {
t.Errorf("TimeMidnight() minute = %d, want 0", midnight.Minute())
}
if midnight.Second() != 0 {
t.Errorf("TimeMidnight() second = %d, want 0", midnight.Second())
}
if midnight.Nanosecond() != 0 {
t.Errorf("TimeMidnight() nanosecond = %d, want 0", midnight.Nanosecond())
}
// Should be in UTC+9 timezone
_, offset := midnight.Zone()
expectedOffset := 9 * 60 * 60
if offset != expectedOffset {
t.Errorf("TimeMidnight() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
}
}
func TestTimeWeekStart(t *testing.T) {
weekStart := TimeWeekStart()
// Should be on Monday (weekday = 1)
if weekStart.Weekday() != time.Monday {
t.Errorf("TimeWeekStart() weekday = %v, want Monday", weekStart.Weekday())
}
// Should be at midnight
if weekStart.Hour() != 0 || weekStart.Minute() != 0 || weekStart.Second() != 0 {
t.Errorf("TimeWeekStart() should be at midnight, got %02d:%02d:%02d",
weekStart.Hour(), weekStart.Minute(), weekStart.Second())
}
// Should be in UTC+9 timezone
_, offset := weekStart.Zone()
expectedOffset := 9 * 60 * 60
if offset != expectedOffset {
t.Errorf("TimeWeekStart() zone offset = %d, want %d (UTC+9)", offset, expectedOffset)
}
// Week start should be before or equal to current midnight
midnight := TimeMidnight()
if weekStart.After(midnight) {
t.Errorf("TimeWeekStart() %v should be <= current midnight %v", weekStart, midnight)
}
}
func TestTimeWeekNext(t *testing.T) {
weekStart := TimeWeekStart()
weekNext := TimeWeekNext()
// TimeWeekNext should be exactly 7 days after TimeWeekStart
expectedNext := weekStart.Add(time.Hour * 24 * 7)
if !weekNext.Equal(expectedNext) {
t.Errorf("TimeWeekNext() = %v, want %v (7 days after WeekStart)", weekNext, expectedNext)
}
// Should also be on Monday
if weekNext.Weekday() != time.Monday {
t.Errorf("TimeWeekNext() weekday = %v, want Monday", weekNext.Weekday())
}
// Should be at midnight
if weekNext.Hour() != 0 || weekNext.Minute() != 0 || weekNext.Second() != 0 {
t.Errorf("TimeWeekNext() should be at midnight, got %02d:%02d:%02d",
weekNext.Hour(), weekNext.Minute(), weekNext.Second())
}
// Should be in the future relative to week start
if !weekNext.After(weekStart) {
t.Errorf("TimeWeekNext() %v should be after TimeWeekStart() %v", weekNext, weekStart)
}
}
func TestTimeWeekStartSundayEdge(t *testing.T) {
// When today is Sunday, the calculation should go back to last Monday
// This is tested indirectly by verifying the weekday is always Monday
weekStart := TimeWeekStart()
// Regardless of what day it is now, week start should be Monday
if weekStart.Weekday() != time.Monday {
t.Errorf("TimeWeekStart() on any day should return Monday, got %v", weekStart.Weekday())
}
}
func TestTimeMidnightSameDay(t *testing.T) {
adjusted := TimeAdjusted()
midnight := TimeMidnight()
// Midnight should be on the same day (year, month, day)
if midnight.Year() != adjusted.Year() ||
midnight.Month() != adjusted.Month() ||
midnight.Day() != adjusted.Day() {
t.Errorf("TimeMidnight() date = %v, want same day as TimeAdjusted() %v",
midnight.Format("2006-01-02"), adjusted.Format("2006-01-02"))
}
}
func TestTimeWeekDuration(t *testing.T) {
weekStart := TimeWeekStart()
weekNext := TimeWeekNext()
// Duration between week boundaries should be exactly 7 days
duration := weekNext.Sub(weekStart)
expectedDuration := time.Hour * 24 * 7
if duration != expectedDuration {
t.Errorf("Duration between WeekStart and WeekNext = %v, want %v", duration, expectedDuration)
}
}
func TestTimeZoneConsistency(t *testing.T) {
adjusted := TimeAdjusted()
midnight := TimeMidnight()
weekStart := TimeWeekStart()
weekNext := TimeWeekNext()
// All times should be in the same timezone (UTC+9)
times := []struct {
name string
time time.Time
}{
{"TimeAdjusted", adjusted},
{"TimeMidnight", midnight},
{"TimeWeekStart", weekStart},
{"TimeWeekNext", weekNext},
}
expectedOffset := 9 * 60 * 60
for _, tt := range times {
_, offset := tt.time.Zone()
if offset != expectedOffset {
t.Errorf("%s() zone offset = %d, want %d (UTC+9)", tt.name, offset, expectedOffset)
}
}
}

View File

@@ -0,0 +1,68 @@
package channelserver
import (
"net"
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network"
"erupe-ce/network/clientctx"
"go.uber.org/zap"
)
// mockPacket implements mhfpacket.MHFPacket for testing.
// Imported from v9.2.x-stable.
type mockPacket struct {
opcode uint16
}
func (m *mockPacket) Opcode() network.PacketID {
return network.PacketID(m.opcode)
}
func (m *mockPacket) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
if ctx == nil {
panic("clientContext is nil")
}
bf.WriteUint32(0x12345678)
return nil
}
func (m *mockPacket) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return nil
}
// createMockServer creates a minimal Server for testing.
// Imported from v9.2.x-stable and adapted for main.
func createMockServer() *Server {
logger, _ := zap.NewDevelopment()
s := &Server{
logger: logger,
erupeConfig: &_config.Config{},
stages: make(map[string]*Stage),
sessions: make(map[net.Conn]*Session),
raviente: &Raviente{
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
},
}
s.i18n = getLangStrings(s)
return s
}
// createMockSession creates a minimal Session for testing.
// Imported from v9.2.x-stable and adapted for main.
func createMockSession(charID uint32, server *Server) *Session {
logger, _ := zap.NewDevelopment()
return &Session{
charID: charID,
clientContext: &clientctx.ClientContext{},
sendPackets: make(chan packet, 20),
Name: "TestPlayer",
server: server,
logger: logger,
semaphoreID: make([]uint16, 2),
}
}