Files
Erupe/server/entranceserver/make_resp_test.go
Houmgaor 82b967b715 refactor: replace raw SQL with repository interfaces in entranceserver and API server
Extract all direct database calls from entranceserver (2 calls) and
API server (17 calls) into typed repository interfaces with PostgreSQL
implementations, matching the pattern established in signserver and
channelserver.

Entranceserver: EntranceServerRepo, EntranceSessionRepo
API server: APIUserRepo, APICharacterRepo, APISessionRepo

Also fix the 3 remaining fmt.Sprintf calls inside logger invocations
in handlers_commands.go and handlers_stage.go, replacing them with
structured zap fields.

Unskip 5 TestNewAuthData* tests that previously required a real
database — they now run with mock repos.
2026-02-22 17:04:58 +01:00

265 lines
7.0 KiB
Go

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