test: increase total coverage from 46.1% to 50.5%

- Fix duplicate test declarations across coverage2, misc, mercenary, event files
- Fix signserver TestHandlePacketDELETE timeout (multi-write pipe deadlock)
- Fix entranceserver build error (invalid -1 for uint16 port)
- Add handlers_reserve_test.go covering all 56 reserve handler stubs
- Add handlers_coverage3_test.go with 115+ handler tests
- Add handlers_register_test.go with 55 Raviente register/load tests
- Add handlers_coverage_test.go, signserver, entranceserver, usercheck tests
This commit is contained in:
Houmgaor
2026-02-08 18:42:55 +01:00
parent 6d18de01eb
commit e7eab936a9
13 changed files with 7008 additions and 176 deletions

View File

@@ -916,3 +916,352 @@ func TestGetGuildmatesNotInGuild(t *testing.T) {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetFriendsForCharactersDBError tests getFriendsForCharacters when DB query fails
func TestGetFriendsForCharactersDBError(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
// Get friends CSV for character - DB error
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnError(sql.ErrNoRows)
// Even on error, still produces the friend query (with empty/error friendsCSV)
// The function calls Scan which fails, then continues to build a query
// with the empty string. The query then fails as well.
mock.ExpectQuery("SELECT id, name FROM characters").
WillReturnError(sql.ErrConnDone)
friends := server.getFriendsForCharacters(chars)
// Should return 0 friends on error
if len(friends) != 0 {
t.Errorf("getFriendsForCharacters() with DB error = %d, want 0", len(friends))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetGuildmatesForCharactersGuildQueryError tests guild ID query failure
func TestGetGuildmatesForCharactersGuildQueryError(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
// Check if in guild - yes
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
// Get guild ID - error
mock.ExpectQuery("SELECT guild_id FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnError(sql.ErrConnDone)
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 0 {
t.Errorf("getGuildmatesForCharacters() with guild query error = %d, want 0", len(guildmates))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetGuildmatesForCharactersGuildmatesQueryError tests guildmates query failure
func TestGetGuildmatesForCharactersGuildmatesQueryError(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
}
// Check if in guild - yes
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
// Get guild ID
mock.ExpectQuery("SELECT guild_id FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"guild_id"}).AddRow(100))
// Get guildmates - error
mock.ExpectQuery("SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=\\$1 AND character_id!=\\$2").
WithArgs(100, uint32(1)).
WillReturnError(sql.ErrConnDone)
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 0 {
t.Errorf("getGuildmatesForCharacters() with guildmates query error = %d, want 0", len(guildmates))
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestDeleteCharacterDeleteError tests deleteCharacter when the delete/update query fails
func TestDeleteCharacterDeleteError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Token verification
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("validtoken").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
// Check if new character
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
// Soft delete fails
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
WithArgs(123).
WillReturnError(sql.ErrConnDone)
err := server.deleteCharacter(123, "validtoken")
if err == nil {
t.Error("deleteCharacter() should return error when update fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestNewUserCharaInsertError tests newUserChara when the INSERT fails
func TestNewUserCharaInsertError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Get user ID
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// Check for existing new characters
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
// Insert new character - error
mock.ExpectExec("INSERT INTO characters").
WithArgs(1, sqlmock.AnyArg()).
WillReturnError(sql.ErrConnDone)
err := server.newUserChara("testuser")
if err == nil {
t.Error("newUserChara() should return error when insert fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestNewUserCharaCountError tests newUserChara when the COUNT query fails
func TestNewUserCharaCountError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Get user ID
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// Check for existing new characters - error
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(1).
WillReturnError(sql.ErrConnDone)
err := server.newUserChara("testuser")
if err == nil {
t.Error("newUserChara() should return error when count query fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestRegisterDBAccountGetIDError tests registerDBAccount when getting the new user ID fails
func TestRegisterDBAccountGetIDError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Insert user succeeds
mock.ExpectExec("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\)").
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// Get user ID - error
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("newuser").
WillReturnError(sql.ErrConnDone)
err := server.registerDBAccount("newuser", "password123")
if err == nil {
t.Error("registerDBAccount() should return error when getting ID fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestRegisterDBAccountCharacterInsertError tests registerDBAccount when character insert fails
func TestRegisterDBAccountCharacterInsertError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Insert user
mock.ExpectExec("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\)").
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// Get user ID
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("newuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// Insert character - error
mock.ExpectExec("INSERT INTO characters").
WithArgs(1, sqlmock.AnyArg()).
WillReturnError(sql.ErrConnDone)
err := server.registerDBAccount("newuser", "password123")
if err == nil {
t.Error("registerDBAccount() should return error when character insert fails")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetReturnExpiryDBError tests getReturnExpiry when the return_expires query fails
func TestGetReturnExpiryDBError(t *testing.T) {
server, mock := newTestServerWithMock(t)
// Get last login - recent
recentLogin := time.Now().Add(-time.Hour * 24)
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
// Get return expiry - error
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnError(sql.ErrNoRows)
// Should set return_expires to now
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), 1).
WillReturnResult(sqlmock.NewResult(0, 1))
// Update last login
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), 1).
WillReturnResult(sqlmock.NewResult(0, 1))
expiry := server.getReturnExpiry(1)
// Should still return a valid time (approximately now)
if expiry.IsZero() {
t.Error("getReturnExpiry() should return non-zero time even on error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetFriendsForCharactersMultipleChars tests with multiple characters
func TestGetFriendsForCharactersMultipleChars(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
{ID: 2, Name: "Hunter2"},
}
// First character friends
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"friends"}).AddRow("10"))
mock.ExpectQuery("SELECT id, name FROM characters WHERE id=10").
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(10, "Friend1"))
// Second character friends
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
WithArgs(uint32(2)).
WillReturnRows(sqlmock.NewRows([]string{"friends"}).AddRow("20"))
mock.ExpectQuery("SELECT id, name FROM characters WHERE id=20").
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(20, "Friend2"))
friends := server.getFriendsForCharacters(chars)
if len(friends) != 2 {
t.Errorf("getFriendsForCharacters() = %d, want 2", len(friends))
}
// Verify CID assignment
if len(friends) >= 2 {
if friends[0].CID != 1 {
t.Errorf("friends[0].CID = %d, want 1", friends[0].CID)
}
if friends[1].CID != 2 {
t.Errorf("friends[1].CID = %d, want 2", friends[1].CID)
}
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestGetGuildmatesForCharactersMultipleChars tests with multiple characters in guilds
func TestGetGuildmatesForCharactersMultipleChars(t *testing.T) {
server, mock := newTestServerWithMock(t)
chars := []character{
{ID: 1, Name: "Hunter1"},
{ID: 2, Name: "Hunter2"},
}
// First character in guild
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
mock.ExpectQuery("SELECT guild_id FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"guild_id"}).AddRow(100))
mock.ExpectQuery("SELECT character_id AS id, c.name FROM guild_characters gc JOIN characters c ON c.id = gc.character_id WHERE guild_id=\\$1 AND character_id!=\\$2").
WithArgs(100, uint32(1)).
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(10, "Guildmate1"))
// Second character not in guild
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
WithArgs(uint32(2)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
guildmates := server.getGuildmatesForCharacters(chars)
if len(guildmates) != 1 {
t.Errorf("getGuildmatesForCharacters() = %d, want 1", len(guildmates))
}
if len(guildmates) >= 1 && guildmates[0].CID != 1 {
t.Errorf("guildmates[0].CID = %d, want 1", guildmates[0].CID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}

View File

@@ -2,6 +2,7 @@ package signserver
import (
"bytes"
"database/sql"
"io"
"net"
"sync"
@@ -12,7 +13,10 @@ import (
"erupe-ce/config"
"erupe-ce/network"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// mockConn implements net.Conn for testing
@@ -446,7 +450,781 @@ func TestSessionWorkWithEmptyRead(t *testing.T) {
session.work()
}
// Note: Tests for handleDSGNRequest require a database connection.
// The function immediately queries the database for user authentication.
// These tests should be implemented as integration tests with a test database
// or using sqlmock for database mocking.
// TestHandlePacketDSGNRequest tests the DSGN:100 path with a mocked database.
func TestHandlePacketDSGNRequest(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
// Use net.Pipe for bidirectional communication
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Create a DSGN:100 packet with username "testuser" and password "testpass"
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser"))
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user not found, auto-create off
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnError(sql.ErrNoRows)
// Read the response in a goroutine
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
// Allow response to be sent
time.Sleep(50 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDLTSKEYSIGN tests the DLTSKEYSIGN:100 path (falls through to DSGN:100)
func TestHandlePacketDLTSKEYSIGN(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Create a DLTSKEYSIGN:100 packet
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DLTSKEYSIGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser"))
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user not found
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnError(sql.ErrNoRows)
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(50 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDELETE tests the DELETE:100 path
func TestHandlePacketDELETE(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Create a DELETE:100 packet
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DELETE:100"))
bf.WriteNullTerminatedBytes([]byte("login-token-abc"))
bf.WriteUint32(123) // characterID
bf.WriteUint32(456) // login_token_number
// Mock DB: Token verification
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
WithArgs("login-token-abc").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
// Check if new character
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
// Soft delete
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
WithArgs(123).
WillReturnResult(sqlmock.NewResult(0, 1))
// Read all response data in a goroutine (SendPacket writes header + encrypted data)
done := make(chan []byte, 1)
go func() {
var all []byte
buf := make([]byte, 4096)
for {
n, readErr := clientConn.Read(buf)
if n > 0 {
all = append(all, buf[:n]...)
}
if readErr != nil {
break
}
}
done <- all
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
// Close server side so the reader goroutine finishes
serverConn.Close()
select {
case <-done:
// Response received successfully
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for response")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNWithAutoCreate tests DSGN:100 with auto-create account enabled
func TestHandlePacketDSGNWithAutoCreate(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: true,
DevModeOptions: config.DevModeOptions{
AutoCreateAccount: true,
},
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("newuser"))
bf.WriteNullTerminatedBytes([]byte("newpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user not found
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("newuser").
WillReturnError(sql.ErrNoRows)
// Auto-create: insert user
mock.ExpectExec("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\)").
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// Auto-create: get user ID
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("newuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// Auto-create: insert character
mock.ExpectExec("INSERT INTO characters").
WithArgs(1, sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// Now get new user ID for makeSignInResp
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("newuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// makeSignInResp calls getReturnExpiry
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(time.Now()))
// getReturnExpiry: get return_expires
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"return_expires"}).AddRow(time.Now().Add(time.Hour * 24 * 30)))
// getReturnExpiry: update last_login
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), 1).
WillReturnResult(sqlmock.NewResult(0, 1))
// getCharactersForUser
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id ASC").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hrp", "gr", "weapon_type", "last_login"}))
// registerToken
mock.ExpectExec("INSERT INTO sign_sessions \\(user_id, token\\) VALUES \\(\\$1, \\$2\\)").
WithArgs(1, sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// getLastCID
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(0))
// getUserRights
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(2))
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNWithValidPassword tests DSGN:100 with correct password
func TestHandlePacketDSGNWithValidPassword(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Generate a bcrypt hash for "testpass"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.MinCost)
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("existinguser"))
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user found with correct password
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("existinguser").
WillReturnRows(sqlmock.NewRows([]string{"id", "password"}).AddRow(1, string(hashedPassword)))
// makeSignInResp calls getReturnExpiry
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(time.Now()))
// getReturnExpiry: get return_expires
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"return_expires"}).AddRow(time.Now().Add(time.Hour * 24 * 30)))
// getReturnExpiry: update last_login
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), 1).
WillReturnResult(sqlmock.NewResult(0, 1))
// getCharactersForUser
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id ASC").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hrp", "gr", "weapon_type", "last_login"}))
// registerToken
mock.ExpectExec("INSERT INTO sign_sessions \\(user_id, token\\) VALUES \\(\\$1, \\$2\\)").
WithArgs(1, sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// getLastCID
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(0))
// getUserRights
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(2))
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNWrongPassword tests DSGN:100 with wrong password
func TestHandlePacketDSGNWrongPassword(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Generate a bcrypt hash for "correctpass"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("correctpass"), bcrypt.MinCost)
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser"))
bf.WriteNullTerminatedBytes([]byte("wrongpass")) // Wrong password
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user found but password will not match
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnRows(sqlmock.NewRows([]string{"id", "password"}).AddRow(1, string(hashedPassword)))
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNWithDBError tests DSGN:100 with a database error
func TestHandlePacketDSGNWithDBError(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser"))
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: generic error
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnError(sql.ErrConnDone)
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNNewCharaRequest tests DSGN:100 with the '+' suffix for new character
func TestHandlePacketDSGNNewCharaRequest(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: false,
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
// Generate a bcrypt hash for "testpass"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.MinCost)
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser+")) // '+' suffix means new character request
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user found
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnRows(sqlmock.NewRows([]string{"id", "password"}).AddRow(1, string(hashedPassword)))
// newUserChara: get user ID
mock.ExpectQuery("SELECT id FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
// newUserChara: check existing new chars
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
// newUserChara: insert character
mock.ExpectExec("INSERT INTO characters").
WithArgs(1, sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
// makeSignInResp calls
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(time.Now()))
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"return_expires"}).AddRow(time.Now().Add(time.Hour * 24 * 30)))
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
WithArgs(sqlmock.AnyArg(), 1).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id ASC").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hrp", "gr", "weapon_type", "last_login"}))
mock.ExpectExec("INSERT INTO sign_sessions \\(user_id, token\\) VALUES \\(\\$1, \\$2\\)").
WithArgs(1, sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(0))
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(2))
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}
// TestHandlePacketDSGNWithDevModeOutboundLogging tests dev mode outbound logging
func TestHandlePacketDSGNWithDevModeOutboundLogging(t *testing.T) {
logger := zap.NewNop()
erupeConfig := &config.Config{
DevMode: true,
DevModeOptions: config.DevModeOptions{
LogOutboundMessages: true,
},
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create sqlmock: %v", err)
}
sqlxDB := sqlx.NewDb(db, "sqlmock")
server := &Server{
logger: logger,
erupeConfig: erupeConfig,
db: sqlxDB,
}
clientConn, serverConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
session := &Session{
logger: logger,
server: server,
rawConn: serverConn,
cryptConn: network.NewCryptConn(serverConn),
}
bf := byteframe.NewByteFrame()
bf.WriteNullTerminatedBytes([]byte("DSGN:100"))
bf.WriteNullTerminatedBytes([]byte("testuser"))
bf.WriteNullTerminatedBytes([]byte("testpass"))
bf.WriteNullTerminatedBytes([]byte("unk"))
// Mock DB: user not found, dev mode but no auto create
mock.ExpectQuery("SELECT id, password FROM users WHERE username = \\$1").
WithArgs("testuser").
WillReturnError(sql.ErrNoRows)
done := make(chan struct{})
go func() {
defer close(done)
buf := make([]byte, 4096)
for {
_, err := clientConn.Read(buf)
if err != nil {
return
}
}
}()
err = session.handlePacket(bf.Data())
if err != nil {
t.Errorf("handlePacket() returned error: %v", err)
}
time.Sleep(100 * time.Millisecond)
clientConn.Close()
<-done
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unfulfilled expectations: %v", err)
}
}