mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-27 10:03:06 +01:00
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:
@@ -6,8 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
cfg "erupe-ce/config"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -292,315 +291,276 @@ func TestMultipleMembers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a test server with mocked database
|
||||
func newTestServerWithMock(t *testing.T) (*Server, sqlmock.Sqlmock) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create sqlmock: %v", err)
|
||||
func TestGetCharactersForUser(t *testing.T) {
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
characters: []character{
|
||||
{ID: 1, IsFemale: false, Name: "Hunter1", HR: 100, GR: 50, WeaponType: 3, LastLogin: 1700000000},
|
||||
{ID: 2, IsFemale: true, Name: "Hunter2", HR: 200, GR: 100, WeaponType: 7, LastLogin: 1700000001},
|
||||
},
|
||||
}
|
||||
|
||||
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
db: sqlxDB,
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
return server, mock
|
||||
}
|
||||
|
||||
func TestGetCharactersForUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"}).
|
||||
AddRow(1, false, false, "Hunter1", "desc1", 100, 50, 3, 1700000000).
|
||||
AddRow(2, true, false, "Hunter2", "desc2", 200, 100, 7, 1700000001)
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(rows)
|
||||
|
||||
chars, err := server.getCharactersForUser(1)
|
||||
if err != nil {
|
||||
t.Errorf("getCharactersForUser() error: %v", err)
|
||||
}
|
||||
|
||||
if len(chars) != 2 {
|
||||
t.Errorf("getCharactersForUser() returned %d characters, want 2", len(chars))
|
||||
}
|
||||
|
||||
if chars[0].Name != "Hunter1" {
|
||||
t.Errorf("First character name = %s, want Hunter1", chars[0].Name)
|
||||
}
|
||||
|
||||
if chars[1].IsFemale != true {
|
||||
t.Error("Second character should be female")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharactersForUserNoCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
characters: []character{},
|
||||
}
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "is_female", "is_new_character", "name", "unk_desc_string", "hr", "gr", "weapon_type", "last_login"})
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(rows)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars, err := server.getCharactersForUser(1)
|
||||
if err != nil {
|
||||
t.Errorf("getCharactersForUser() error: %v", err)
|
||||
}
|
||||
|
||||
if len(chars) != 0 {
|
||||
t.Errorf("getCharactersForUser() returned %d characters, want 0", len(chars))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharactersForUserDBError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
getForUserErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT id, is_female, is_new_character, name, unk_desc_string, hr, gr, weapon_type, last_login FROM characters WHERE user_id = \\$1 AND deleted = false ORDER BY id").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
_, err := server.getCharactersForUser(1)
|
||||
if err == nil {
|
||||
t.Error("getCharactersForUser() should return error")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastCID(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
userRepo := &mockSignUserRepo{
|
||||
lastCharacter: 12345,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_character"}).AddRow(12345))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
lastCID := server.getLastCID(1)
|
||||
if lastCID != 12345 {
|
||||
t.Errorf("getLastCID() = %d, want 12345", lastCID)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastCIDNoResult(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
userRepo := &mockSignUserRepo{
|
||||
lastCharacterErr: sql.ErrNoRows,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT last_character FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
lastCID := server.getLastCID(1)
|
||||
if lastCID != 0 {
|
||||
t.Errorf("getLastCID() with no result = %d, want 0", lastCID)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserRights(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
userRepo := &mockSignUserRepo{
|
||||
rights: 30,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT rights FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"rights"}).AddRow(30))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
rights := server.getUserRights(1)
|
||||
if rights == 0 {
|
||||
t.Error("getUserRights() should return non-zero value")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiry(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
recentLogin := time.Now().Add(-time.Hour * 24)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
|
||||
userRepo := &mockSignUserRepo{
|
||||
lastLogin: recentLogin,
|
||||
returnExpiry: time.Now().Add(time.Hour * 24 * 30),
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(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(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
if expiry.Before(time.Now()) {
|
||||
t.Error("getReturnExpiry() should return future date")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !userRepo.updateLastLoginCalled {
|
||||
t.Error("getReturnExpiry() should update last login")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiryInactiveUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
oldLogin := time.Now().Add(-time.Hour * 24 * 100)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(oldLogin))
|
||||
userRepo := &mockSignUserRepo{
|
||||
lastLogin: oldLogin,
|
||||
}
|
||||
|
||||
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
if expiry.Before(time.Now()) {
|
||||
t.Error("getReturnExpiry() should return future date for inactive user")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !userRepo.updateReturnExpiryCalled {
|
||||
t.Error("getReturnExpiry() should update return expiry for inactive user")
|
||||
}
|
||||
if !userRepo.updateLastLoginCalled {
|
||||
t.Error("getReturnExpiry() should update last login")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnExpiryDBError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
|
||||
recentLogin := time.Now().Add(-time.Hour * 24)
|
||||
mock.ExpectQuery("SELECT COALESCE\\(last_login, now\\(\\)\\) FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"last_login"}).AddRow(recentLogin))
|
||||
userRepo := &mockSignUserRepo{
|
||||
lastLogin: recentLogin,
|
||||
returnExpiryErr: sql.ErrNoRows,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT return_expires FROM users WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
mock.ExpectExec("UPDATE users SET return_expires=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
mock.ExpectExec("UPDATE users SET last_login=\\$1 WHERE id=\\$2").
|
||||
WithArgs(sqlmock.AnyArg(), uint32(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
expiry := server.getReturnExpiry(1)
|
||||
|
||||
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)
|
||||
if !userRepo.updateReturnExpiryCalled {
|
||||
t.Error("getReturnExpiry() should update return expiry on fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserChara(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
newCharCount: 0,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
mock.ExpectExec("INSERT INTO characters").
|
||||
WithArgs(uint32(1), sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err != nil {
|
||||
t.Errorf("newUserChara() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !charRepo.createCalled {
|
||||
t.Error("newUserChara() should call CreateCharacter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaAlreadyHasNewChar(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
newCharCount: 1,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err != nil {
|
||||
t.Errorf("newUserChara() should return nil when user already has new char: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if charRepo.createCalled {
|
||||
t.Error("newUserChara() should not call CreateCharacter when user already has new char")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaCountError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
newCharCountErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.newUserChara(1)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserCharaInsertError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
newCharCount: 0,
|
||||
createErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM characters WHERE user_id = \\$1 AND is_new_character = true").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
mock.ExpectExec("INSERT INTO characters").
|
||||
WithArgs(uint32(1), sqlmock.AnyArg()).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.newUserChara(1)
|
||||
if err == nil {
|
||||
t.Error("newUserChara() should return error when insert fails")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDBAccount(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
userRepo := &mockSignUserRepo{
|
||||
registerUID: 1,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
|
||||
WithArgs("newuser", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
uid, err := server.registerDBAccount("newuser", "password123")
|
||||
if err != nil {
|
||||
@@ -609,128 +569,125 @@ func TestRegisterDBAccount(t *testing.T) {
|
||||
if uid != 1 {
|
||||
t.Errorf("registerDBAccount() uid = %d, want 1", uid)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !userRepo.registered {
|
||||
t.Error("registerDBAccount() should call Register")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDBAccountDuplicateUser(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
userRepo := &mockSignUserRepo{
|
||||
registerErr: sql.ErrNoRows,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("INSERT INTO users \\(username, password, return_expires\\) VALUES \\(\\$1, \\$2, \\$3\\) RETURNING id").
|
||||
WithArgs("existinguser", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
_, err := server.registerDBAccount("existinguser", "password123")
|
||||
if err == nil {
|
||||
t.Error("registerDBAccount() should return error for duplicate user")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacter(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: true,
|
||||
}
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
isNew: false,
|
||||
}
|
||||
|
||||
// validateToken: SELECT count(*) FROM sign_sessions WHERE token = $1
|
||||
// When tokenID=0, query has no AND clause but both args are still passed to QueryRow
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
|
||||
|
||||
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err != nil {
|
||||
t.Errorf("deleteCharacter() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !charRepo.softDeleteCalled {
|
||||
t.Error("deleteCharacter() should soft delete existing character")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNewCharacter(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: true,
|
||||
}
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
isNew: true,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec("DELETE FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err != nil {
|
||||
t.Errorf("deleteCharacter() error: %v", err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if !charRepo.hardDeleteCalled {
|
||||
t.Error("deleteCharacter() should hard delete new character")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacterInvalidToken(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: false,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("invalidtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
}
|
||||
|
||||
err := server.deleteCharacter(123, "invalidtoken", 0)
|
||||
if err == nil {
|
||||
t.Error("deleteCharacter() should return error for invalid token")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteCharacterDeleteError(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: true,
|
||||
}
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
isNew: false,
|
||||
softDeleteErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM sign_sessions WHERE token = \\$1").
|
||||
WithArgs("validtoken", uint32(0)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
mock.ExpectQuery("SELECT is_new_character FROM characters WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"is_new_character"}).AddRow(false))
|
||||
|
||||
mock.ExpectExec("UPDATE characters SET deleted = true WHERE id = \\$1").
|
||||
WithArgs(123).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
err := server.deleteCharacter(123, "validtoken", 0)
|
||||
if err == nil {
|
||||
t.Error("deleteCharacter() should return error when update fails")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFriendsForCharactersEmpty(t *testing.T) {
|
||||
server, _ := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{}
|
||||
|
||||
friends := server.getFriendsForCharacters(chars)
|
||||
if len(friends) != 0 {
|
||||
t.Errorf("getFriendsForCharacters() for empty chars = %d, want 0", len(friends))
|
||||
@@ -738,10 +695,15 @@ func TestGetFriendsForCharactersEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetGuildmatesForCharactersEmpty(t *testing.T) {
|
||||
server, _ := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{}
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 0 {
|
||||
t.Errorf("getGuildmatesForCharacters() for empty chars = %d, want 0", len(guildmates))
|
||||
@@ -749,79 +711,269 @@ func TestGetGuildmatesForCharactersEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFriendsForCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
friends: []members{
|
||||
{ID: 2, Name: "Friend1"},
|
||||
{ID: 3, Name: "Friend2"},
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT friends FROM characters WHERE id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"friends"}).AddRow("2,3"))
|
||||
|
||||
mock.ExpectQuery("SELECT id, name FROM characters WHERE id=2 OR id=3").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(2, "Friend1").
|
||||
AddRow(3, "Friend2"))
|
||||
|
||||
friends := server.getFriendsForCharacters(chars)
|
||||
if len(friends) != 2 {
|
||||
t.Errorf("getFriendsForCharacters() = %d, want 2", len(friends))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if friends[0].CID != 1 {
|
||||
t.Errorf("friends[0].CID = %d, want 1", friends[0].CID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesForCharacters(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
guildmates: []members{
|
||||
{ID: 2, Name: "Guildmate1"},
|
||||
{ID: 3, Name: "Guildmate2"},
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
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(2, "Guildmate1").
|
||||
AddRow(3, "Guildmate2"))
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 2 {
|
||||
t.Errorf("getGuildmatesForCharacters() = %d, want 2", len(guildmates))
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
if guildmates[0].CID != 1 {
|
||||
t.Errorf("guildmates[0].CID = %d, want 1", guildmates[0].CID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesNotInGuild(t *testing.T) {
|
||||
server, mock := newTestServerWithMock(t)
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
guildmates: nil,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{
|
||||
{ID: 1, Name: "Hunter1"},
|
||||
}
|
||||
|
||||
mock.ExpectQuery("SELECT count\\(\\*\\) FROM guild_characters WHERE character_id=\\$1").
|
||||
WithArgs(uint32(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 0 {
|
||||
t.Errorf("getGuildmatesForCharacters() for non-guild member = %d, want 0", len(guildmates))
|
||||
}
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("unfulfilled expectations: %v", err)
|
||||
func TestValidateLoginSuccess(t *testing.T) {
|
||||
// bcrypt hash for "password123"
|
||||
hash := "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
|
||||
userRepo := &mockSignUserRepo{
|
||||
credUID: 1,
|
||||
credPassword: hash,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
// Note: bcrypt verification will fail with this test hash since it's not a real hash of "password123"
|
||||
// The important thing is testing the flow, not actual bcrypt verification
|
||||
_, resp := server.validateLogin("testuser", "password123")
|
||||
// This will return SIGN_EPASS since the hash doesn't match, which is expected behavior
|
||||
if resp == SIGN_EABORT {
|
||||
t.Error("validateLogin() should not abort for valid credentials lookup")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLoginUserNotFound(t *testing.T) {
|
||||
userRepo := &mockSignUserRepo{
|
||||
credErr: sql.ErrNoRows,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
_, resp := server.validateLogin("unknown", "password")
|
||||
if resp != SIGN_EAUTH {
|
||||
t.Errorf("validateLogin() for unknown user = %d, want SIGN_EAUTH(%d)", resp, SIGN_EAUTH)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLoginAutoCreate(t *testing.T) {
|
||||
userRepo := &mockSignUserRepo{
|
||||
credErr: sql.ErrNoRows,
|
||||
registerUID: 42,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{
|
||||
AutoCreateAccount: true,
|
||||
},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
uid, resp := server.validateLogin("newuser", "password")
|
||||
if resp != SIGN_SUCCESS {
|
||||
t.Errorf("validateLogin() with auto-create = %d, want SIGN_SUCCESS(%d)", resp, SIGN_SUCCESS)
|
||||
}
|
||||
if uid != 42 {
|
||||
t.Errorf("validateLogin() uid = %d, want 42", uid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLoginDBError(t *testing.T) {
|
||||
userRepo := &mockSignUserRepo{
|
||||
credErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
_, resp := server.validateLogin("testuser", "password")
|
||||
if resp != SIGN_EABORT {
|
||||
t.Errorf("validateLogin() on DB error = %d, want SIGN_EABORT(%d)", resp, SIGN_EABORT)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTokenValid(t *testing.T) {
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: true,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
}
|
||||
|
||||
if !server.validateToken("validtoken", 0) {
|
||||
t.Error("validateToken() should return true for valid token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTokenInvalid(t *testing.T) {
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateResult: false,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
}
|
||||
|
||||
if server.validateToken("invalidtoken", 0) {
|
||||
t.Error("validateToken() should return false for invalid token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTokenDBError(t *testing.T) {
|
||||
sessionRepo := &mockSignSessionRepo{
|
||||
validateErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
sessionRepo: sessionRepo,
|
||||
}
|
||||
|
||||
if server.validateToken("token", 0) {
|
||||
t.Error("validateToken() should return false on DB error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserRightsZeroUID(t *testing.T) {
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
}
|
||||
|
||||
rights := server.getUserRights(0)
|
||||
if rights != 0 {
|
||||
t.Errorf("getUserRights(0) = %d, want 0", rights)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserRightsDBError(t *testing.T) {
|
||||
userRepo := &mockSignUserRepo{
|
||||
rightsErr: sql.ErrConnDone,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
userRepo: userRepo,
|
||||
}
|
||||
|
||||
rights := server.getUserRights(1)
|
||||
if rights != 0 {
|
||||
t.Errorf("getUserRights() on error = %d, want 0", rights)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFriendsForCharactersError(t *testing.T) {
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
getFriendsErr: errMockDB,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{{ID: 1, Name: "Hunter1"}}
|
||||
friends := server.getFriendsForCharacters(chars)
|
||||
if len(friends) != 0 {
|
||||
t.Errorf("getFriendsForCharacters() on error = %d, want 0", len(friends))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuildmatesForCharactersError(t *testing.T) {
|
||||
charRepo := &mockSignCharacterRepo{
|
||||
getGuildmatesErr: errMockDB,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
logger: zap.NewNop(),
|
||||
erupeConfig: &cfg.Config{},
|
||||
charRepo: charRepo,
|
||||
}
|
||||
|
||||
chars := []character{{ID: 1, Name: "Hunter1"}}
|
||||
guildmates := server.getGuildmatesForCharacters(chars)
|
||||
if len(guildmates) != 0 {
|
||||
t.Errorf("getGuildmatesForCharacters() on error = %d, want 0", len(guildmates))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user