From b2d8f2843e87daa425d6304715a42021f41d7274 Mon Sep 17 00:00:00 2001 From: Alexander Rudenko Date: Wed, 18 Dec 2024 11:30:09 +0300 Subject: [PATCH] auth repo test --- .../auth/repository/auth/check_credentials.go | 4 +- internal/auth/repository/auth/create_user.go | 4 +- .../auth/get_user_by_email_or_username.go | 2 +- .../auth/repository/auth/get_user_by_id.go | 4 +- .../auth/tests/check_credentials_test.go | 74 ++++++++++++ .../repository/auth/tests/create_user_test.go | 98 ++++++++++++++++ .../get_user_by_email_or_username_test.go | 110 ++++++++++++++++++ .../auth/tests/get_user_by_id_test.go | 75 ++++++++++++ .../auth/repository/auth/tests/users_test.go | 99 ++++++++++++++++ internal/auth/repository/auth/users.go | 23 ++-- 10 files changed, 479 insertions(+), 14 deletions(-) create mode 100644 internal/auth/repository/auth/tests/check_credentials_test.go create mode 100644 internal/auth/repository/auth/tests/create_user_test.go create mode 100644 internal/auth/repository/auth/tests/get_user_by_email_or_username_test.go create mode 100644 internal/auth/repository/auth/tests/get_user_by_id_test.go create mode 100644 internal/auth/repository/auth/tests/users_test.go diff --git a/internal/auth/repository/auth/check_credentials.go b/internal/auth/repository/auth/check_credentials.go index 11bdc27..78a51ab 100644 --- a/internal/auth/repository/auth/check_credentials.go +++ b/internal/auth/repository/auth/check_credentials.go @@ -17,7 +17,7 @@ const checkCredentialsQuery = ` func (d UserDB) CheckCredentials(ctx context.Context, username, password string) (models.User, error) { var userInfo UserInfo - err := d.pool.QueryRow(ctx, checkCredentialsQuery, username, password).Scan( + err := d.Pool.QueryRow(ctx, checkCredentialsQuery, username, password).Scan( &userInfo.ID, &userInfo.Username, &userInfo.Email, @@ -30,6 +30,6 @@ func (d UserDB) CheckCredentials(ctx context.Context, username, password string) } return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, err) } - user := toDomainUser(userInfo) + user := ToDomainUser(userInfo) return user, nil } diff --git a/internal/auth/repository/auth/create_user.go b/internal/auth/repository/auth/create_user.go index ef7b1af..9a4491d 100644 --- a/internal/auth/repository/auth/create_user.go +++ b/internal/auth/repository/auth/create_user.go @@ -14,7 +14,7 @@ const createUserQuery = ` func (d *UserDB) CreateUser(ctx context.Context, user models.User) (models.User, error) { var userInfo UserInfo - err := d.pool.QueryRow(ctx, createUserQuery, + err := d.Pool.QueryRow(ctx, createUserQuery, user.Username, user.Email, user.Password, @@ -31,6 +31,6 @@ func (d *UserDB) CreateUser(ctx context.Context, user models.User) (models.User, userInfo.Email = user.Email userInfo.ImageURL = &user.ImageURL - newUser := toDomainUser(userInfo) + newUser := ToDomainUser(userInfo) return newUser, nil } diff --git a/internal/auth/repository/auth/get_user_by_email_or_username.go b/internal/auth/repository/auth/get_user_by_email_or_username.go index 184a69e..d3ab556 100644 --- a/internal/auth/repository/auth/get_user_by_email_or_username.go +++ b/internal/auth/repository/auth/get_user_by_email_or_username.go @@ -25,7 +25,7 @@ func (d *UserDB) UserExists(ctx context.Context, user models.User) (bool, error) args = append(args, user.ID) } - err := d.pool.QueryRow(ctx, query, args...).Scan(&exists) + err := d.Pool.QueryRow(ctx, query, args...).Scan(&exists) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return false, nil diff --git a/internal/auth/repository/auth/get_user_by_id.go b/internal/auth/repository/auth/get_user_by_id.go index 58ab437..3a2a520 100644 --- a/internal/auth/repository/auth/get_user_by_id.go +++ b/internal/auth/repository/auth/get_user_by_id.go @@ -14,7 +14,7 @@ const getUserByIDQuery = `SELECT id, username, email, url_to_avatar FROM "USER" func (d UserDB) GetUserByID(ctx context.Context, ID int) (models.User, error) { var userInfo UserInfo - err := d.pool.QueryRow(ctx, getUserByIDQuery, ID).Scan( + err := d.Pool.QueryRow(ctx, getUserByIDQuery, ID).Scan( &userInfo.ID, &userInfo.Username, &userInfo.Email, @@ -25,6 +25,6 @@ func (d UserDB) GetUserByID(ctx context.Context, ID int) (models.User, error) { return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, models.ErrUserNotFound) } - user := toDomainUser(userInfo) + user := ToDomainUser(userInfo) return user, err } diff --git a/internal/auth/repository/auth/tests/check_credentials_test.go b/internal/auth/repository/auth/tests/check_credentials_test.go new file mode 100644 index 0000000..fe1f2ad --- /dev/null +++ b/internal/auth/repository/auth/tests/check_credentials_test.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + "fmt" + "github.com/jackc/pgx/v5" + "github.com/pashagolub/pgxmock/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "kudago/internal/auth/repository/auth" + "testing" + + "kudago/internal/models" +) + +func TestUserDB_CheckCredentials(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := []struct { + name string + username string + password string + mockSetup func(m pgxmock.PgxConnIface) + expectedUser models.User + expectErr bool + }{ + { + name: "Пользователь не найден", + username: "testuser", + password: "wrongpassword", + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id, username, email, created_at, url_to_avatar`). + WithArgs("testuser", "wrongpassword"). + WillReturnError(pgx.ErrNoRows) + }, + expectedUser: models.User{}, + expectErr: true, + }, + { + name: "Ошибка при выполнении запроса", + username: "testuser", + password: "password123", + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id, username, email, created_at, url_to_avatar`). + WithArgs("testuser", "password123"). + WillReturnError(fmt.Errorf("database error")) + }, + expectedUser: models.User{}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockConn, err := pgxmock.NewConn() + require.NoError(t, err) + defer mockConn.Close(ctx) + tt.mockSetup(mockConn) + db := userRepository.UserDB{Pool: mockConn} + user, err := db.CheckCredentials(ctx, tt.username, tt.password) + + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedUser, user) + } + }) + } +} diff --git a/internal/auth/repository/auth/tests/create_user_test.go b/internal/auth/repository/auth/tests/create_user_test.go new file mode 100644 index 0000000..98acaff --- /dev/null +++ b/internal/auth/repository/auth/tests/create_user_test.go @@ -0,0 +1,98 @@ +package repository + +import ( + "context" + "fmt" + "github.com/pashagolub/pgxmock/v4" + "kudago/internal/auth/repository/auth" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "kudago/internal/models" +) + +func TestUserDB_CreateUser(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := []struct { + name string + user models.User + mockSetup func(m pgxmock.PgxConnIface) + expectedUser models.User + expectErr bool + }{ + { + name: "Успешное создание пользователя", + user: models.User{ + Username: "newuser", + Email: "newuser@example.com", + Password: "password123", + ImageURL: "http://example.com/avatar.jpg", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`INSERT INTO "USER" \(username, email, password_hash, url_to_avatar\)`). + WithArgs("newuser", "newuser@example.com", "password123", "http://example.com/avatar.jpg"). + WillReturnRows(pgxmock.NewRows([]string{"id", "created_at"}). + AddRow(1, time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC))) + }, + expectedUser: models.User{ + ID: 1, + Username: "newuser", + Email: "newuser@example.com", + Password: "", + ImageURL: "http://example.com/avatar.jpg", + }, + expectErr: false, + }, + { + name: "Ошибка при создании пользователя", + user: models.User{ + Username: "newuser", + Email: "newuser@example.com", + Password: "password123", + ImageURL: "http://example.com/avatar.jpg", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + // Мокируем ошибку при выполнении запроса + m.ExpectQuery(`INSERT INTO "USER" \(username, email, password_hash, url_to_avatar\)`). + WithArgs("newuser", "newuser@example.com", "password123", "http://example.com/avatar.jpg"). + WillReturnError(fmt.Errorf("database error")) + }, + expectedUser: models.User{}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Создание мокированного соединения + mockConn, err := pgxmock.NewConn() + require.NoError(t, err) + defer mockConn.Close(ctx) + + // Настройка моков + tt.mockSetup(mockConn) + + // Инициализация репозитория с мокированным соединением + db := userRepository.UserDB{Pool: mockConn} + + // Вызов функции + user, err := db.CreateUser(ctx, tt.user) + + // Проверка ошибок + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedUser, user) + } + }) + } +} diff --git a/internal/auth/repository/auth/tests/get_user_by_email_or_username_test.go b/internal/auth/repository/auth/tests/get_user_by_email_or_username_test.go new file mode 100644 index 0000000..567daca --- /dev/null +++ b/internal/auth/repository/auth/tests/get_user_by_email_or_username_test.go @@ -0,0 +1,110 @@ +package repository + +import ( + "context" + "fmt" + "github.com/jackc/pgx/v5" + "github.com/pashagolub/pgxmock/v4" + "kudago/internal/auth/repository/auth" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "kudago/internal/models" +) + +func TestUserDB_UserExists(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := []struct { + name string + user models.User + mockSetup func(m pgxmock.PgxConnIface) + expected bool + expectErr bool + }{ + { + name: "Пользователь существует по username", + user: models.User{ + Username: "existinguser", + Email: "existinguser@example.com", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id FROM "USER" WHERE \(username = \$1 OR email = \$2\)`). + WithArgs("existinguser", "existinguser@example.com"). + WillReturnRows(pgxmock.NewRows([]string{"id"}).AddRow(1)) + }, + expected: true, + expectErr: false, + }, + { + name: "Пользователь не существует", + user: models.User{ + Username: "nonexistinguser", + Email: "nonexistinguser@example.com", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id FROM "USER" WHERE \(username = \$1 OR email = \$2\)`). + WithArgs("nonexistinguser", "nonexistinguser@example.com"). + WillReturnError(pgx.ErrNoRows) + }, + expected: false, + expectErr: false, + }, + { + name: "Ошибка при выполнении запроса", + user: models.User{ + Username: "user_with_error", + Email: "user_with_error@example.com", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id FROM "USER" WHERE \(username = \$1 OR email = \$2\)`). + WithArgs("user_with_error", "user_with_error@example.com"). + WillReturnError(fmt.Errorf("database error")) + }, + expected: false, + expectErr: true, + }, + { + name: "Пользователь существует, но исключаем его по ID", + user: models.User{ + ID: 2, + Username: "existinguser", + Email: "existinguser@example.com", + }, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id FROM "USER" WHERE \(username = \$1 OR email = \$2\) AND id != \$3`). + WithArgs("existinguser", "existinguser@example.com", 2). + WillReturnRows(pgxmock.NewRows([]string{"id"}).AddRow(1)) + }, + expected: true, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockConn, err := pgxmock.NewConn() + require.NoError(t, err) + defer mockConn.Close(ctx) + + tt.mockSetup(mockConn) + + db := userRepository.UserDB{Pool: mockConn} + + exists, err := db.UserExists(ctx, tt.user) + + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, exists) + } + }) + } +} diff --git a/internal/auth/repository/auth/tests/get_user_by_id_test.go b/internal/auth/repository/auth/tests/get_user_by_id_test.go new file mode 100644 index 0000000..6cb635e --- /dev/null +++ b/internal/auth/repository/auth/tests/get_user_by_id_test.go @@ -0,0 +1,75 @@ +package repository + +import ( + "context" + "fmt" + "github.com/jackc/pgx/v5" + "github.com/pashagolub/pgxmock/v4" + "kudago/internal/auth/repository/auth" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "kudago/internal/models" +) + +func TestUserDB_GetUserByID(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := []struct { + name string + ID int + mockSetup func(m pgxmock.PgxConnIface) + expected models.User + expectErr bool + }{ + { + name: "Пользователь не найден", + ID: 2, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id, username, email, url_to_avatar FROM "USER" WHERE id=\$1`). + WithArgs(2). + WillReturnError(pgx.ErrNoRows) + }, + expected: models.User{}, + expectErr: true, + }, + { + name: "Ошибка при запросе", + ID: 3, + mockSetup: func(m pgxmock.PgxConnIface) { + m.ExpectQuery(`SELECT id, username, email, url_to_avatar FROM "USER" WHERE id=\$1`). + WithArgs(3). + WillReturnError(fmt.Errorf("database error")) + }, + expected: models.User{}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + mockConn, err := pgxmock.NewConn() + require.NoError(t, err) + defer mockConn.Close(ctx) + + tt.mockSetup(mockConn) + + db := userRepository.UserDB{Pool: mockConn} + + user, err := db.GetUserByID(ctx, tt.ID) + + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, user) + } + }) + } +} diff --git a/internal/auth/repository/auth/tests/users_test.go b/internal/auth/repository/auth/tests/users_test.go new file mode 100644 index 0000000..0f41308 --- /dev/null +++ b/internal/auth/repository/auth/tests/users_test.go @@ -0,0 +1,99 @@ +package repository + +import ( + "kudago/internal/auth/repository/auth" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "kudago/internal/models" +) + +func TestUserDB_nilIfEmpty(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value string + expected *string + }{ + { + name: "Empty string", + value: "", + expected: nil, + }, + { + name: "Non-empty string", + value: "http://example.com/avatar.jpg", + expected: stringPtr("http://example.com/avatar.jpg"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := userRepository.NilIfEmpty(tt.value) + + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestUserDB_toDomainUser(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + userInfo userRepository.UserInfo + expected models.User + }{ + { + name: "User with Image URL", + userInfo: userRepository.UserInfo{ + ID: 1, + Username: "testuser", + Email: "testuser@example.com", + ImageURL: stringPtr("http://example.com/avatar.jpg"), + CreatedAt: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + expected: models.User{ + ID: 1, + Username: "testuser", + Email: "testuser@example.com", + ImageURL: "http://example.com/avatar.jpg", + }, + }, + { + name: "User without Image URL", + userInfo: userRepository.UserInfo{ + ID: 2, + Username: "anotheruser", + Email: "anotheruser@example.com", + ImageURL: nil, + CreatedAt: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + expected: models.User{ + ID: 2, + Username: "anotheruser", + Email: "anotheruser@example.com", + ImageURL: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := userRepository.ToDomainUser(tt.userInfo) + + assert.Equal(t, tt.expected, result) + }) + } +} + +func stringPtr(s string) *string { + return &s +} diff --git a/internal/auth/repository/auth/users.go b/internal/auth/repository/auth/users.go index ea620fe..c27e2dd 100644 --- a/internal/auth/repository/auth/users.go +++ b/internal/auth/repository/auth/users.go @@ -1,11 +1,12 @@ package userRepository import ( + "context" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" "time" "kudago/internal/models" - - "github.com/jackc/pgx/v5/pgxpool" ) type UserInfo struct { @@ -18,23 +19,31 @@ type UserInfo struct { } type UserDB struct { - pool *pgxpool.Pool + Pool Pool +} + +type Pool interface { + Begin(ctx context.Context) (pgx.Tx, error) + Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) + Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, args ...any) pgx.Row + SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults } -func NewDB(pool *pgxpool.Pool) *UserDB { +func NewDB(pool Pool) *UserDB { return &UserDB{ - pool: pool, + Pool: pool, } } -func nilIfEmpty(value string) *string { +func NilIfEmpty(value string) *string { if value == "" { return nil } return &value } -func toDomainUser(user UserInfo) models.User { +func ToDomainUser(user UserInfo) models.User { var imageURL string if user.ImageURL == nil { imageURL = ""