package user import ( "database/sql" "regexp" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "veza-backend-api/internal/database" ) // Helper to setup mock DB func setupMockDB(t *testing.T) (*database.DB, sqlmock.Sqlmock) { db, mock, err := sqlmock.New() require.NoError(t, err) dbWrapper := &database.DB{ DB: db, } return dbWrapper, mock } func TestService_GetUserByID_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() expectedUser := UserResponse{ ID: userID, Username: "testuser", Email: "test@example.com", Role: "user", IsActive: true, CreatedAt: time.Now(), } rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio", "role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}). AddRow(userID, expectedUser.Email, nil, nil, expectedUser.Username, nil, nil, expectedUser.Role, expectedUser.IsActive, false, nil, expectedUser.CreatedAt, time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, email, first_name, last_name, username, avatar, bio, role, is_active, is_verified, last_login_at, created_at, updated_at FROM users WHERE id = $1 AND is_active = true`)). WithArgs(userID). WillReturnRows(rows) // Execute user, err := service.GetUserByID(userID) // Assert assert.NoError(t, err) assert.NotNil(t, user) assert.Equal(t, userID, user.ID) assert.Equal(t, "testuser", user.Username) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_GetUserByID_NotFound(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() mock.ExpectQuery(regexp.QuoteMeta(`FROM users`)). WithArgs(userID). WillReturnError(sql.ErrNoRows) // Execute user, err := service.GetUserByID(userID) // Assert assert.Error(t, err) assert.Equal(t, "user not found", err.Error()) assert.Nil(t, user) } func TestService_CreateUser_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) req := CreateUserRequest{ Username: "newuser", Email: "new@example.com", Password: "password123", } userID := uuid.New() rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "role", "is_active", "is_verified", "created_at", "updated_at"}). AddRow(userID, req.Email, nil, nil, req.Username, "user", true, false, time.Now(), time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO users`)). WithArgs(req.Email, sqlmock.AnyArg(), "", "", req.Username, "user"). WillReturnRows(rows) // Execute user, err := service.CreateUser(req) // Assert assert.NoError(t, err) assert.NotNil(t, user) assert.Equal(t, userID, user.ID) assert.Equal(t, "newuser", user.Username) } func TestService_DeleteUser_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() mock.ExpectExec(regexp.QuoteMeta(`UPDATE users SET is_active = false`)). WithArgs(userID). WillReturnResult(sqlmock.NewResult(1, 1)) // Execute err := service.DeleteUser(userID) // Assert assert.NoError(t, err) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_DeleteUser_NotFound(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() mock.ExpectExec(regexp.QuoteMeta(`UPDATE users SET is_active = false`)). WithArgs(userID). WillReturnResult(sqlmock.NewResult(1, 0)) // 0 rows affected // Execute err := service.DeleteUser(userID) // Assert assert.Error(t, err) assert.Equal(t, "user not found", err.Error()) } func TestService_RecoverAccount_NotFound(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) email := "deleted@example.com" mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, password_hash, deleted_at`)). WithArgs(email). WillReturnError(sql.ErrNoRows) err := service.RecoverAccount(email, "password") assert.Error(t, err) assert.Equal(t, "no deleted account found for this email", err.Error()) } func TestService_UpdateUser_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() newFirst := "Jane" newLast := "Doe" req := UpdateUserRequest{ FirstName: &newFirst, LastName: &newLast, } expectedUser := UserResponse{ ID: userID, Username: "testuser", Email: "test@example.com", FirstName: sql.NullString{String: "Jane", Valid: true}, LastName: sql.NullString{String: "Doe", Valid: true}, UpdatedAt: time.Now(), } rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio", "role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}). AddRow(userID, expectedUser.Email, expectedUser.FirstName, expectedUser.LastName, expectedUser.Username, nil, nil, "user", true, false, nil, time.Now(), expectedUser.UpdatedAt) mock.ExpectQuery(regexp.QuoteMeta(`UPDATE users`)). WithArgs("Jane", "Doe", userID). WillReturnRows(rows) user, err := service.UpdateUser(userID, req) assert.NoError(t, err) assert.NotNil(t, user) assert.Equal(t, "Jane", user.FirstName.String) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_GetUsers_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) // Mock count query mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2)) // Mock select query rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio", "role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}). AddRow(uuid.New(), "user1@example.com", nil, nil, "user1", nil, nil, "user", true, false, nil, time.Now(), time.Now()). AddRow(uuid.New(), "user2@example.com", nil, nil, "user2", nil, nil, "user", true, false, nil, time.Now(), time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, email, first_name, last_name, username, avatar, bio, role, is_active, is_verified, last_login_at, created_at, updated_at FROM users`)). WithArgs(20, 0). // Limit 20, Offset 0 WillReturnRows(rows) users, total, err := service.GetUsers(1, 20, "") assert.NoError(t, err) assert.Equal(t, 2, total) assert.Len(t, users, 2) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_UpdateUserPreferences_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() // Expect GetUserPreferences first prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "contrast", "density", "accent_hue", "font_size", "updated_at"}). AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", "normal", "comfortable", 220, 16, time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)). WithArgs(userID). WillReturnRows(prefRows) // Expect Upsert theme := "dark" req := UserPreferencesRequest{ Theme: &theme, } mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO user_preferences`)). WithArgs(userID, "dark", "en", "UTC", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "normal", "comfortable", 220, 16, sqlmock.AnyArg()). WillReturnResult(sqlmock.NewResult(1, 1)) pref, err := service.UpdateUserPreferences(userID, req) assert.NoError(t, err) assert.NotNil(t, pref) assert.Equal(t, "dark", pref.Theme) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_ChangePassword_UserNotFound(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() mock.ExpectQuery(regexp.QuoteMeta(`SELECT password_hash FROM users`)). WithArgs(userID). WillReturnError(sql.ErrNoRows) err := service.ChangePassword(userID, "old", "new") assert.Error(t, err) assert.Equal(t, "user not found", err.Error()) } func TestService_GetUserStats_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) // Expect 4 queries mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE is_active = true`)). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(100)) mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE is_active = true AND is_verified = true`)). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(50)) mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)). // Active users query WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(10)) mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)). // New users query WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(5)) stats, err := service.GetUserStats() assert.NoError(t, err) assert.Equal(t, 100, stats["total_users"]) assert.Equal(t, 50, stats["verified_users"]) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_RequestDataDeletion_UserNotFound(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() mock.ExpectQuery(regexp.QuoteMeta(`SELECT password_hash FROM users`)). WithArgs(userID). WillReturnError(sql.ErrNoRows) err := service.RequestDataDeletion(userID, "pass", "reason") assert.Error(t, err) assert.Equal(t, "user not found", err.Error()) } func TestService_GetAccountStatus_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() rows := sqlmock.NewRows([]string{"id", "is_active", "is_verified", "created_at", "deleted_at", "deletion_reason", "recovery_deadline"}). AddRow(userID, true, true, time.Now(), nil, "", nil) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, is_active`)). WithArgs(userID). WillReturnRows(rows) status, err := service.GetAccountStatus(userID) assert.NoError(t, err) assert.Equal(t, "active", status.Status) assert.NoError(t, mock.ExpectationsWereMet()) } func TestService_ExportUserData_Success(t *testing.T) { dbWrapper, mock := setupMockDB(t) defer dbWrapper.Close() service := NewService(dbWrapper) userID := uuid.New() // 1. GetUserByID userRows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio", "role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}). AddRow(userID, "test@example.com", nil, nil, "test", nil, nil, "user", true, true, nil, time.Now(), time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id`)). WithArgs(userID). WillReturnRows(userRows) // 2. GetUserPreferences prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "contrast", "density", "accent_hue", "font_size", "updated_at"}). AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", "normal", "comfortable", 220, 16, time.Now()) mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)). WithArgs(userID). WillReturnRows(prefRows) export, err := service.ExportUserData(userID) assert.NoError(t, err) assert.NotNil(t, export) assert.Equal(t, userID, export.UserID) assert.NoError(t, mock.ExpectationsWereMet()) }