mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
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:
454
server/channelserver/handlers_achievement_test.go
Normal file
454
server/channelserver/handlers_achievement_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
77
server/channelserver/handlers_bbs_test.go
Normal file
77
server/channelserver/handlers_bbs_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
110
server/channelserver/handlers_cafe_test.go
Normal file
110
server/channelserver/handlers_cafe_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
70
server/channelserver/handlers_campaign_test.go
Normal file
70
server/channelserver/handlers_campaign_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
141
server/channelserver/handlers_caravan_test.go
Normal file
141
server/channelserver/handlers_caravan_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
700
server/channelserver/handlers_core_test.go
Normal file
700
server/channelserver/handlers_core_test.go
Normal 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)
|
||||
}
|
||||
920
server/channelserver/handlers_coverage2_test.go
Normal file
920
server/channelserver/handlers_coverage2_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
1135
server/channelserver/handlers_coverage3_test.go
Normal file
1135
server/channelserver/handlers_coverage3_test.go
Normal file
File diff suppressed because it is too large
Load Diff
144
server/channelserver/handlers_coverage_test.go
Normal file
144
server/channelserver/handlers_coverage_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
1
server/channelserver/handlers_discord_test.go
Normal file
1
server/channelserver/handlers_discord_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package channelserver
|
||||
343
server/channelserver/handlers_diva_test.go
Normal file
343
server/channelserver/handlers_diva_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
258
server/channelserver/handlers_event_test.go
Normal file
258
server/channelserver/handlers_event_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
109
server/channelserver/handlers_festa_test.go
Normal file
109
server/channelserver/handlers_festa_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
249
server/channelserver/handlers_guild_icon_test.go
Normal file
249
server/channelserver/handlers_guild_icon_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
209
server/channelserver/handlers_guild_member_test.go
Normal file
209
server/channelserver/handlers_guild_member_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
83
server/channelserver/handlers_mail_test.go
Normal file
83
server/channelserver/handlers_mail_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMailStruct(t *testing.T) {
|
||||
mail := Mail{
|
||||
ID: 123,
|
||||
SenderID: 1000,
|
||||
RecipientID: 2000,
|
||||
Subject: "Test Subject",
|
||||
Body: "Test Body Content",
|
||||
Read: false,
|
||||
Deleted: false,
|
||||
Locked: true,
|
||||
AttachedItemReceived: false,
|
||||
AttachedItemID: 500,
|
||||
AttachedItemAmount: 10,
|
||||
CreatedAt: time.Now(),
|
||||
IsGuildInvite: false,
|
||||
IsSystemMessage: true,
|
||||
SenderName: "TestSender",
|
||||
}
|
||||
|
||||
if mail.ID != 123 {
|
||||
t.Errorf("ID = %d, want 123", mail.ID)
|
||||
}
|
||||
if mail.SenderID != 1000 {
|
||||
t.Errorf("SenderID = %d, want 1000", mail.SenderID)
|
||||
}
|
||||
if mail.RecipientID != 2000 {
|
||||
t.Errorf("RecipientID = %d, want 2000", mail.RecipientID)
|
||||
}
|
||||
if mail.Subject != "Test Subject" {
|
||||
t.Errorf("Subject = %s, want 'Test Subject'", mail.Subject)
|
||||
}
|
||||
if mail.Body != "Test Body Content" {
|
||||
t.Errorf("Body = %s, want 'Test Body Content'", mail.Body)
|
||||
}
|
||||
if mail.Read {
|
||||
t.Error("Read should be false")
|
||||
}
|
||||
if mail.Deleted {
|
||||
t.Error("Deleted should be false")
|
||||
}
|
||||
if !mail.Locked {
|
||||
t.Error("Locked should be true")
|
||||
}
|
||||
if mail.AttachedItemReceived {
|
||||
t.Error("AttachedItemReceived should be false")
|
||||
}
|
||||
if mail.AttachedItemID != 500 {
|
||||
t.Errorf("AttachedItemID = %d, want 500", mail.AttachedItemID)
|
||||
}
|
||||
if mail.AttachedItemAmount != 10 {
|
||||
t.Errorf("AttachedItemAmount = %d, want 10", mail.AttachedItemAmount)
|
||||
}
|
||||
if mail.IsGuildInvite {
|
||||
t.Error("IsGuildInvite should be false")
|
||||
}
|
||||
if !mail.IsSystemMessage {
|
||||
t.Error("IsSystemMessage should be true")
|
||||
}
|
||||
if mail.SenderName != "TestSender" {
|
||||
t.Errorf("SenderName = %s, want 'TestSender'", mail.SenderName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMailStruct_DefaultValues(t *testing.T) {
|
||||
mail := Mail{}
|
||||
|
||||
if mail.ID != 0 {
|
||||
t.Errorf("Default ID should be 0, got %d", mail.ID)
|
||||
}
|
||||
if mail.Subject != "" {
|
||||
t.Errorf("Default Subject should be empty, got %s", mail.Subject)
|
||||
}
|
||||
if mail.Read {
|
||||
t.Error("Default Read should be false")
|
||||
}
|
||||
}
|
||||
298
server/channelserver/handlers_mercenary_test.go
Normal file
298
server/channelserver/handlers_mercenary_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
601
server/channelserver/handlers_misc_test.go
Normal file
601
server/channelserver/handlers_misc_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
77
server/channelserver/handlers_mutex_test.go
Normal file
77
server/channelserver/handlers_mutex_test.go
Normal 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)
|
||||
}
|
||||
372
server/channelserver/handlers_object_test.go
Normal file
372
server/channelserver/handlers_object_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
128
server/channelserver/handlers_quest_backport_test.go
Normal file
128
server/channelserver/handlers_quest_backport_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
53
server/channelserver/handlers_rengoku_test.go
Normal file
53
server/channelserver/handlers_rengoku_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
113
server/channelserver/handlers_reserve_test.go
Normal file
113
server/channelserver/handlers_reserve_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
126
server/channelserver/handlers_reward_test.go
Normal file
126
server/channelserver/handlers_reward_test.go
Normal 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)
|
||||
}
|
||||
447
server/channelserver/handlers_semaphore_test.go
Normal file
447
server/channelserver/handlers_semaphore_test.go
Normal 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:
|
||||
}
|
||||
}
|
||||
}
|
||||
408
server/channelserver/handlers_shop_gacha_test.go
Normal file
408
server/channelserver/handlers_shop_gacha_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
313
server/channelserver/handlers_simple_test.go
Normal file
313
server/channelserver/handlers_simple_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
193
server/channelserver/handlers_tactics_test.go
Normal file
193
server/channelserver/handlers_tactics_test.go
Normal 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)
|
||||
}
|
||||
268
server/channelserver/handlers_test.go
Normal file
268
server/channelserver/handlers_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
91
server/channelserver/handlers_tournament_test.go
Normal file
91
server/channelserver/handlers_tournament_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
156
server/channelserver/handlers_tower_test.go
Normal file
156
server/channelserver/handlers_tower_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
128
server/channelserver/handlers_users_test.go
Normal file
128
server/channelserver/handlers_users_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
208
server/channelserver/handlers_util_test.go
Normal file
208
server/channelserver/handlers_util_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
94
server/channelserver/sys_language_test.go
Normal file
94
server/channelserver/sys_language_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
322
server/channelserver/sys_object_test.go
Normal file
322
server/channelserver/sys_object_test.go
Normal 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)
|
||||
}
|
||||
|
||||
276
server/channelserver/sys_semaphore_test.go
Normal file
276
server/channelserver/sys_semaphore_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
290
server/channelserver/sys_stage_test.go
Normal file
290
server/channelserver/sys_stage_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
167
server/channelserver/sys_time_test.go
Normal file
167
server/channelserver/sys_time_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
68
server/channelserver/test_helpers_test.go
Normal file
68
server/channelserver/test_helpers_test.go
Normal 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),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user