package entranceserver import ( "fmt" "strings" "testing" "go.uber.org/zap" cfg "erupe-ce/config" ) // TestEncodeServerInfo_EmptyClanMemberLimits verifies the crash is FIXED when ClanMemberLimits is empty // Previously panicked: runtime error: index out of range [-1] // From erupe.log.1:659922 // After fix: Should handle empty array gracefully with default value (60) func TestEncodeServerInfo_EmptyClanMemberLimits(t *testing.T) { config := &cfg.Config{ RealClientMode: cfg.Z1, Host: "127.0.0.1", Entrance: cfg.Entrance{ Enabled: true, Port: 53310, Entries: []cfg.EntranceServerInfo{ { Name: "TestServer", Description: "Test", IP: "127.0.0.1", Type: 0, Recommended: 0, AllowedClientFlags: 0xFFFFFFFF, Channels: []cfg.EntranceChannelInfo{ { Port: 54001, MaxPlayers: 100, }, }, }, }, }, GameplayOptions: cfg.GameplayOptions{ ClanMemberLimits: [][]uint8{}, // Empty array - should now use default (60) instead of panicking }, } server := &Server{ logger: zap.NewNop(), erupeConfig: config, } // Set up defer to catch ANY panic - we should NOT get array bounds panic anymore defer func() { if r := recover(); r != nil { // If panic occurs, it should NOT be from array access panicStr := fmt.Sprintf("%v", r) if strings.Contains(panicStr, "index out of range") { t.Errorf("Array bounds panic NOT fixed! Still getting: %v", r) } else { // Other panic is acceptable (network, DB, etc) - we only care about array bounds t.Logf("Non-array-bounds panic (acceptable): %v", r) } } }() // This should NOT panic on array bounds anymore - should use default value 60 result := encodeServerInfo(config, server, true) if len(result) > 0 { t.Log("✅ encodeServerInfo handled empty ClanMemberLimits without array bounds panic") } } // TestClanMemberLimitsBoundsChecking verifies bounds checking logic for ClanMemberLimits // Tests the specific logic that was fixed without needing full database setup func TestClanMemberLimitsBoundsChecking(t *testing.T) { // Test the bounds checking logic directly testCases := []struct { name string clanMemberLimits [][]uint8 expectedValue uint8 expectDefault bool }{ {"empty array", [][]uint8{}, 60, true}, {"single row with 2 columns", [][]uint8{{1, 50}}, 50, false}, {"single row with 1 column", [][]uint8{{1}}, 60, true}, {"multiple rows, last has 2 columns", [][]uint8{{1, 10}, {2, 20}, {3, 60}}, 60, false}, {"multiple rows, last has 1 column", [][]uint8{{1, 10}, {2, 20}, {3}}, 60, true}, {"multiple rows with valid data", [][]uint8{{1, 10}, {2, 20}, {3, 30}, {4, 40}, {5, 50}}, 50, false}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Replicate the bounds checking logic from the fix var maxClanMembers uint8 = 60 if len(tc.clanMemberLimits) > 0 { lastRow := tc.clanMemberLimits[len(tc.clanMemberLimits)-1] if len(lastRow) > 1 { maxClanMembers = lastRow[1] } } // Verify correct behavior if maxClanMembers != tc.expectedValue { t.Errorf("Expected value %d, got %d", tc.expectedValue, maxClanMembers) } if tc.expectDefault && maxClanMembers != 60 { t.Errorf("Expected default value 60, got %d", maxClanMembers) } t.Logf("✅ %s: Safe bounds access, value = %d", tc.name, maxClanMembers) }) } } // TestEncodeServerInfo_WithMockRepo tests encodeServerInfo with a mock server repo func TestEncodeServerInfo_WithMockRepo(t *testing.T) { config := &cfg.Config{ RealClientMode: cfg.Z1, Host: "127.0.0.1", Entrance: cfg.Entrance{ Enabled: true, Port: 53310, Entries: []cfg.EntranceServerInfo{ { Name: "TestServer", Description: "Test", IP: "127.0.0.1", Type: 0, Recommended: 0, AllowedClientFlags: 0xFFFFFFFF, Channels: []cfg.EntranceChannelInfo{ { Port: 54001, MaxPlayers: 100, }, }, }, }, }, GameplayOptions: cfg.GameplayOptions{ ClanMemberLimits: [][]uint8{{1, 60}}, }, } server := &Server{ logger: zap.NewNop(), erupeConfig: config, serverRepo: &mockEntranceServerRepo{currentPlayers: 42}, } result := encodeServerInfo(config, server, true) if len(result) == 0 { t.Error("encodeServerInfo returned empty result") } } // TestMakeUsrResp_WithMockRepo tests makeUsrResp with a mock session repo func TestMakeUsrResp_WithMockRepo(t *testing.T) { config := &cfg.Config{ RealClientMode: cfg.Z1, } server := &Server{ logger: zap.NewNop(), erupeConfig: config, sessionRepo: &mockEntranceSessionRepo{serverID: 1234}, } // Build a minimal USR request packet: // 4 bytes ALL+ prefix, 1 byte 0x00, 2 bytes entry count, then 4 bytes per entry (char ID) pkt := []byte{ 'A', 'L', 'L', '+', 0x00, 0x00, 0x01, // 1 entry 0x00, 0x00, 0x00, 0x01, // char_id = 1 } result := makeUsrResp(pkt, server) if len(result) == 0 { t.Error("makeUsrResp returned empty result") } } // TestMakeUsrResp_NilSessionRepo tests makeUsrResp when sessionRepo is nil func TestMakeUsrResp_NilSessionRepo(t *testing.T) { config := &cfg.Config{ RealClientMode: cfg.Z1, } server := &Server{ logger: zap.NewNop(), erupeConfig: config, } pkt := []byte{ 'A', 'L', 'L', '+', 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, } result := makeUsrResp(pkt, server) if len(result) == 0 { t.Error("makeUsrResp returned empty result") } } // TestEncodeServerInfo_MissingSecondColumnClanMemberLimits tests accessing [last][1] when [last] is too small // Previously panicked: runtime error: index out of range [1] // After fix: Should handle missing column gracefully with default value (60) func TestEncodeServerInfo_MissingSecondColumnClanMemberLimits(t *testing.T) { config := &cfg.Config{ RealClientMode: cfg.Z1, Host: "127.0.0.1", Entrance: cfg.Entrance{ Enabled: true, Port: 53310, Entries: []cfg.EntranceServerInfo{ { Name: "TestServer", Description: "Test", IP: "127.0.0.1", Type: 0, Recommended: 0, AllowedClientFlags: 0xFFFFFFFF, Channels: []cfg.EntranceChannelInfo{ { Port: 54001, MaxPlayers: 100, }, }, }, }, }, GameplayOptions: cfg.GameplayOptions{ ClanMemberLimits: [][]uint8{ {1}, // Only 1 element, code used to panic accessing [1] }, }, } server := &Server{ logger: zap.NewNop(), erupeConfig: config, } defer func() { if r := recover(); r != nil { panicStr := fmt.Sprintf("%v", r) if strings.Contains(panicStr, "index out of range") { t.Errorf("Array bounds panic NOT fixed! Still getting: %v", r) } else { t.Logf("Non-array-bounds panic (acceptable): %v", r) } } }() // This should NOT panic on array bounds anymore - should use default value 60 result := encodeServerInfo(config, server, true) if len(result) > 0 { t.Log("✅ encodeServerInfo handled missing ClanMemberLimits column without array bounds panic") } }