refactor(signserver): replace raw SQL with repository interfaces

Extract all direct database access into three repository interfaces
(SignUserRepo, SignCharacterRepo, SignSessionRepo) matching the
pattern established in channelserver. This surfaces 9 previously
silenced errors that are now logged with structured context, and
makes the sign server testable with mock repos instead of go-sqlmock.

Security fix: GetFriends now uses parameterized ANY($1) queries
instead of string-concatenated WHERE clauses (SQL injection vector).
This commit is contained in:
Houmgaor
2026-02-22 16:30:24 +01:00
parent 53b5bb3b96
commit b3f75232a3
11 changed files with 1193 additions and 435 deletions

View File

@@ -4,12 +4,33 @@ import (
"fmt"
"strings"
"testing"
"time"
"go.uber.org/zap"
cfg "erupe-ce/config"
)
// newMakeSignResponseServer creates a Server with mock repos for makeSignResponse tests.
func newMakeSignResponseServer(config *cfg.Config) *Server {
return &Server{
erupeConfig: config,
logger: zap.NewNop(),
charRepo: &mockSignCharacterRepo{
characters: []character{},
friends: nil,
guildmates: nil,
},
userRepo: &mockSignUserRepo{
returnExpiry: time.Now().Add(time.Hour * 24 * 30),
lastLogin: time.Now(),
},
sessionRepo: &mockSignSessionRepo{
registerUIDTokenID: 1,
},
}
}
// TestMakeSignResponse_EmptyCapLinkValues verifies the crash is FIXED when CapLink.Values is empty
// Previously panicked: runtime error: index out of range [0] with length 0
// From erupe.log.1:659796 and 659853
@@ -37,10 +58,7 @@ func TestMakeSignResponse_EmptyCapLinkValues(t *testing.T) {
session := &Session{
logger: zap.NewNop(),
server: &Server{
erupeConfig: config,
logger: zap.NewNop(),
},
server: newMakeSignResponseServer(config),
client: PC100,
}
@@ -61,7 +79,7 @@ func TestMakeSignResponse_EmptyCapLinkValues(t *testing.T) {
// This should NOT panic on array bounds anymore
result := session.makeSignResponse(0)
if len(result) > 0 {
t.Log("makeSignResponse handled empty CapLink.Values without array bounds panic")
t.Log("makeSignResponse handled empty CapLink.Values without array bounds panic")
}
}
@@ -89,10 +107,7 @@ func TestMakeSignResponse_InsufficientCapLinkValues(t *testing.T) {
session := &Session{
logger: zap.NewNop(),
server: &Server{
erupeConfig: config,
logger: zap.NewNop(),
},
server: newMakeSignResponseServer(config),
client: PC100,
}
@@ -110,7 +125,7 @@ func TestMakeSignResponse_InsufficientCapLinkValues(t *testing.T) {
// This should NOT panic on array bounds anymore
result := session.makeSignResponse(0)
if len(result) > 0 {
t.Log("makeSignResponse handled insufficient CapLink.Values without array bounds panic")
t.Log("makeSignResponse handled insufficient CapLink.Values without array bounds panic")
}
}
@@ -138,10 +153,7 @@ func TestMakeSignResponse_MissingCapLinkValues234(t *testing.T) {
session := &Session{
logger: zap.NewNop(),
server: &Server{
erupeConfig: config,
logger: zap.NewNop(),
},
server: newMakeSignResponseServer(config),
client: PC100,
}
@@ -159,7 +171,7 @@ func TestMakeSignResponse_MissingCapLinkValues234(t *testing.T) {
// This should NOT panic on array bounds anymore
result := session.makeSignResponse(0)
if len(result) > 0 {
t.Log("makeSignResponse handled missing CapLink.Values[2/3/4] without array bounds panic")
t.Log("makeSignResponse handled missing CapLink.Values[2/3/4] without array bounds panic")
}
}
@@ -207,7 +219,47 @@ func TestCapLinkValuesBoundsChecking(t *testing.T) {
}
}
t.Logf("%s: All 5 indices accessible without panic", tc.name)
t.Logf("%s: All 5 indices accessible without panic", tc.name)
})
}
}
// TestMakeSignResponse_FullFlow tests the complete makeSignResponse with mock repos.
func TestMakeSignResponse_FullFlow(t *testing.T) {
config := &cfg.Config{
DebugOptions: cfg.DebugOptions{
CapLink: cfg.CapLinkOptions{
Values: []uint16{0, 0, 0, 0, 0},
},
},
GameplayOptions: cfg.GameplayOptions{
MezFesSoloTickets: 100,
MezFesGroupTickets: 100,
},
}
server := newMakeSignResponseServer(config)
// Give the server some characters
server.charRepo = &mockSignCharacterRepo{
characters: []character{
{ID: 1, Name: "TestHunter", HR: 100, GR: 50, WeaponType: 3, LastLogin: 1700000000},
},
}
conn := newMockConn()
session := &Session{
logger: zap.NewNop(),
server: server,
rawConn: conn,
client: PC100,
}
result := session.makeSignResponse(1)
if len(result) == 0 {
t.Error("makeSignResponse() returned empty result")
}
// First byte should be SIGN_SUCCESS
if result[0] != uint8(SIGN_SUCCESS) {
t.Errorf("makeSignResponse() first byte = %d, want %d (SIGN_SUCCESS)", result[0], SIGN_SUCCESS)
}
}