mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
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.
This commit is contained in:
@@ -19,7 +19,8 @@ type Server struct {
|
||||
sync.Mutex
|
||||
logger *zap.Logger
|
||||
erupeConfig *cfg.Config
|
||||
db *sqlx.DB
|
||||
serverRepo EntranceServerRepo
|
||||
sessionRepo EntranceSessionRepo
|
||||
listener net.Listener
|
||||
isShuttingDown bool
|
||||
}
|
||||
@@ -36,7 +37,10 @@ func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
logger: config.Logger,
|
||||
erupeConfig: config.ErupeConfig,
|
||||
db: config.DB,
|
||||
}
|
||||
if config.DB != nil {
|
||||
s.serverRepo = NewEntranceServerRepository(config.DB)
|
||||
s.sessionRepo = NewEntranceSessionRepository(config.DB)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ func encodeServerInfo(config *cfg.Config, s *Server, local bool) []byte {
|
||||
bf.WriteUint16(uint16(channelIdx | 16))
|
||||
bf.WriteUint16(ci.MaxPlayers)
|
||||
var currentPlayers uint16
|
||||
_ = s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers)
|
||||
if s.serverRepo != nil {
|
||||
currentPlayers, _ = s.serverRepo.GetCurrentPlayers(sid)
|
||||
}
|
||||
bf.WriteUint16(currentPlayers)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
@@ -164,12 +166,10 @@ func makeUsrResp(pkt []byte, s *Server) []byte {
|
||||
for i := 0; i < int(userEntries); i++ {
|
||||
cid := bf.ReadUint32()
|
||||
var sid uint16
|
||||
err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid)
|
||||
if err != nil {
|
||||
resp.WriteUint16(0)
|
||||
} else {
|
||||
resp.WriteUint16(sid)
|
||||
if s.sessionRepo != nil {
|
||||
sid, _ = s.sessionRepo.GetServerIDForCharacter(cid)
|
||||
}
|
||||
resp.WriteUint16(sid)
|
||||
resp.WriteUint16(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,99 @@ func TestClanMemberLimitsBoundsChecking(t *testing.T) {
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
|
||||
19
server/entranceserver/repo_interfaces.go
Normal file
19
server/entranceserver/repo_interfaces.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package entranceserver
|
||||
|
||||
// Repository interfaces decouple entrance server business logic from concrete
|
||||
// PostgreSQL implementations, enabling mock/stub injection for unit tests.
|
||||
|
||||
// EntranceServerRepo defines the contract for server-related data access
|
||||
// used by the entrance server when building server list responses.
|
||||
type EntranceServerRepo interface {
|
||||
// GetCurrentPlayers returns the current player count for a given server ID.
|
||||
GetCurrentPlayers(serverID int) (uint16, error)
|
||||
}
|
||||
|
||||
// EntranceSessionRepo defines the contract for session-related data access
|
||||
// used by the entrance server when resolving user locations.
|
||||
type EntranceSessionRepo interface {
|
||||
// GetServerIDForCharacter returns the server ID where the given character
|
||||
// is currently signed in, or 0 if not found.
|
||||
GetServerIDForCharacter(charID uint32) (uint16, error)
|
||||
}
|
||||
21
server/entranceserver/repo_mocks_test.go
Normal file
21
server/entranceserver/repo_mocks_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package entranceserver
|
||||
|
||||
// mockEntranceServerRepo implements EntranceServerRepo for testing.
|
||||
type mockEntranceServerRepo struct {
|
||||
currentPlayers uint16
|
||||
currentPlayersErr error
|
||||
}
|
||||
|
||||
func (m *mockEntranceServerRepo) GetCurrentPlayers(_ int) (uint16, error) {
|
||||
return m.currentPlayers, m.currentPlayersErr
|
||||
}
|
||||
|
||||
// mockEntranceSessionRepo implements EntranceSessionRepo for testing.
|
||||
type mockEntranceSessionRepo struct {
|
||||
serverID uint16
|
||||
serverIDErr error
|
||||
}
|
||||
|
||||
func (m *mockEntranceSessionRepo) GetServerIDForCharacter(_ uint32) (uint16, error) {
|
||||
return m.serverID, m.serverIDErr
|
||||
}
|
||||
22
server/entranceserver/repo_server.go
Normal file
22
server/entranceserver/repo_server.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package entranceserver
|
||||
|
||||
import "github.com/jmoiron/sqlx"
|
||||
|
||||
// EntranceServerRepository implements EntranceServerRepo with PostgreSQL.
|
||||
type EntranceServerRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewEntranceServerRepository creates a new EntranceServerRepository.
|
||||
func NewEntranceServerRepository(db *sqlx.DB) *EntranceServerRepository {
|
||||
return &EntranceServerRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *EntranceServerRepository) GetCurrentPlayers(serverID int) (uint16, error) {
|
||||
var currentPlayers uint16
|
||||
err := r.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", serverID).Scan(¤tPlayers)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return currentPlayers, nil
|
||||
}
|
||||
22
server/entranceserver/repo_session.go
Normal file
22
server/entranceserver/repo_session.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package entranceserver
|
||||
|
||||
import "github.com/jmoiron/sqlx"
|
||||
|
||||
// EntranceSessionRepository implements EntranceSessionRepo with PostgreSQL.
|
||||
type EntranceSessionRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewEntranceSessionRepository creates a new EntranceSessionRepository.
|
||||
func NewEntranceSessionRepository(db *sqlx.DB) *EntranceSessionRepository {
|
||||
return &EntranceSessionRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *EntranceSessionRepository) GetServerIDForCharacter(charID uint32) (uint16, error) {
|
||||
var sid uint16
|
||||
err := r.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", charID).Scan(&sid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sid, nil
|
||||
}
|
||||
Reference in New Issue
Block a user