package repository import ( "database/sql" "errors" "fmt" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "testing" "time" "git.urec56.ru/urec/chat_back_go/config" "git.urec56.ru/urec/chat_back_go/internal/database" mock_database "git.urec56.ru/urec/chat_back_go/internal/database/mocks" "git.urec56.ru/urec/chat_back_go/internal/domain" "git.urec56.ru/urec/chat_back_go/internal/logger" ) func Test_newUser(t *testing.T) { c := gomock.NewController(t) defer c.Finish() db, _, dbClose := mock_database.GetMockDBx(t) defer dbClose() log := logger.NewLogger(config.Config{Mode: "TEST"}) repo := newUserRepo(db, log) assert.Equal(t, &userRepository{db: db, l: log}, repo) } func TestUserRepository_GetByID(t *testing.T) { type mockBehavior func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) testTable := []struct { name string userID int mockBehavior mockBehavior dbRows *sqlmock.Rows dbErr error ud domain.User expectedUser domain.User expectedErr error }{ { name: "ok", userID: 1, mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnRows(rows) }, dbRows: sqlmock.NewRows( []string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }, ), ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedUser: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, }, { name: "user_not_found", mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnError(err) }, dbRows: sqlmock.NewRows( []string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }, ), dbErr: sql.ErrNoRows, expectedErr: domain.UserNotFoundError, }, { name: "internal_error", mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnError(err) }, dbRows: sqlmock.NewRows( []string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }, ), dbErr: errors.New("some error"), expectedErr: domain.InternalServerError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { db, mock, dbClose := mock_database.GetMockDBx(t) defer dbClose() tc.mockBehavior(mock, tc.userID, tc.dbRows.AddRow( tc.ud.ID, tc.ud.Role, tc.ud.Username, tc.ud.Email, tc.ud.HashedPassword, tc.ud.AvatarImage, tc.ud.BlackPhoenix, tc.ud.DateOfBirth, tc.ud.DateOfRegistration, ), tc.dbErr) repo := &userRepository{db: db} u, err := repo.GetByID(tc.userID) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) assert.NoError(t, mock.ExpectationsWereMet()) }) } } func TestUserRepository_GetAll(t *testing.T) { type mockBehavior func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error) testTable := []struct { name string username string mockBehavior mockBehavior dbRows *sqlmock.Rows dbErr error ud domain.User expectedUsers []domain.User expectedErr error }{ { name: "ok", mockBehavior: func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(fmt.Sprintf(`SELECT \* FROM users WHERE username ILIKE \$1 AND role != %d`, domain.AdminUser)).WithArgs(username).WillReturnRows(rows) }, dbRows: sqlmock.NewRows( []string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }, ), ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedUsers: []domain.User{ { ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, }, }, { name: "any_error", mockBehavior: func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(fmt.Sprintf(`SELECT \* FROM users WHERE username ILIKE \$1 AND role != %d`, domain.AdminUser)).WithArgs(username).WillReturnError(err) }, dbRows: sqlmock.NewRows( []string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }, ), dbErr: errors.New("some error"), expectedErr: domain.InternalServerError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { c := gomock.NewController(t) defer c.Finish() db, mock, dbClose := mock_database.GetMockDBx(t) defer dbClose() username := fmt.Sprint("%", tc.username, "%") tc.mockBehavior(mock, username, tc.dbRows.AddRow( tc.ud.ID, tc.ud.Role, tc.ud.Username, tc.ud.Email, tc.ud.HashedPassword, tc.ud.AvatarImage, tc.ud.BlackPhoenix, tc.ud.DateOfBirth, tc.ud.DateOfRegistration, ), tc.dbErr) log := logger.NewLogger(config.Config{Mode: "TEST"}) repo := &userRepository{db: db, l: log} users, err := repo.GetAll(tc.username) assert.Equal(t, tc.expectedUsers, users) assert.ErrorIs(t, err, tc.expectedErr) assert.NoError(t, mock.ExpectationsWereMet()) }) } } func TestUserRepository_FindOne(t *testing.T) { type mockBehavior func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) dbRows := sqlmock.NewRows([]string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }) okUd := domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, } okExpectedUser := domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, } testTable := []struct { name string username string email string mockBehavior mockBehavior dbRows *sqlmock.Rows dbErr error ud domain.User expectedUser domain.User expectedErr error }{ { name: "ok_1", username: "urec", email: "mail@mail.ru", mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE username = \$1 AND email = \$2 LIMIT 1`).WithArgs(username, email).WillReturnRows(rows) }, dbRows: dbRows, ud: okUd, expectedUser: okExpectedUser, }, { name: "ok_2", username: "urec", mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE username = \$1 LIMIT 1`).WithArgs(username).WillReturnRows(rows) }, dbRows: dbRows, ud: okUd, expectedUser: okExpectedUser, }, { name: "ok_3", email: "mail@mail.ru", mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users WHERE email = \$1 LIMIT 1`).WithArgs(email).WillReturnRows(rows) }, dbRows: dbRows, ud: okUd, expectedUser: okExpectedUser, }, { name: "ok_4", mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users LIMIT 1`).WillReturnRows(rows) }, dbRows: dbRows, ud: okUd, expectedUser: okExpectedUser, }, { name: "any_error", mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) { mock.ExpectQuery(`SELECT \* FROM users LIMIT 1`).WillReturnError(err) }, dbRows: dbRows, dbErr: errors.New("some error"), expectedErr: domain.InternalServerError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { db, mock, dbClose := mock_database.GetMockDBx(t) defer dbClose() tc.mockBehavior(mock, tc.username, tc.email, tc.dbRows.AddRow( tc.ud.ID, tc.ud.Role, tc.ud.Username, tc.ud.Email, tc.ud.HashedPassword, tc.ud.AvatarImage, tc.ud.BlackPhoenix, tc.ud.DateOfBirth, tc.ud.DateOfRegistration, ), tc.dbErr) repo := &userRepository{db: db} u, err := repo.FindOne(tc.username, tc.email) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) assert.NoError(t, mock.ExpectationsWereMet()) }) } } func TestUserRepository_Register(t *testing.T) { type mockParams struct { mock sqlmock.Sqlmock email, hashedPassword, username, avatarImage string dateOfBirth time.Time userID int rows *sqlmock.Rows err error } type mockBehavior func(p mockParams) dbRows := sqlmock.NewRows([]string{ "id", "role", "username", "email", "hashed_password", "avatar_image", "black_phoenix", "date_of_birth", "date_of_registration", }) testTable := []struct { name string mockParams mockParams mockBehavior mockBehavior ud domain.User expectedUser domain.User expectedErr error }{ { name: "ok", mockParams: mockParams{ rows: dbRows, }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows) p.mock.ExpectExec( `INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`, ).WithArgs(p.userID, p.avatarImage).WillReturnResult(sqlmock.NewResult(1, 1)) p.mock.ExpectCommit() }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedUser: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, }, { name: "user_already_exists", mockParams: mockParams{ rows: dbRows, err: database.IntegrityError, }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err) p.mock.ExpectRollback() }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.UserAlreadyExistsError, }, { name: "begin_error", mockParams: mockParams{ rows: dbRows, err: errors.New("some error"), }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin().WillReturnError(p.err) }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.InternalServerError, }, { name: "get_error", mockParams: mockParams{ rows: dbRows, err: errors.New("some error"), }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err) p.mock.ExpectRollback() }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.InternalServerError, }, { name: "exec_error", mockParams: mockParams{ rows: dbRows, err: errors.New("some error"), }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows) p.mock.ExpectExec( `INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`, ).WithArgs(p.userID, p.avatarImage).WillReturnError(p.err) p.mock.ExpectRollback() }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.InternalServerError, }, { name: "commit_error", mockParams: mockParams{ rows: dbRows, err: errors.New("some error"), }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows) p.mock.ExpectExec( `INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`, ).WithArgs(p.userID, p.avatarImage).WillReturnResult(sqlmock.NewResult(1, 1)) p.mock.ExpectCommit().WillReturnError(p.err) // p.mock.ExpectRollback().WillReturnError(sql.ErrTxDone) // с ним падает тест тк вызов роллбека не доходит // до драйвера и он его не регистрирует }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.InternalServerError, }, { name: "rollback_error", mockParams: mockParams{ rows: dbRows, err: database.IntegrityError, }, mockBehavior: func(p mockParams) { p.mock.ExpectBegin() p.mock.ExpectQuery( `INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`, ).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err) p.mock.ExpectRollback().WillReturnError(p.err) }, ud: domain.User{ ID: 1, Role: 1, Username: "urec", Email: "mail@mail.ru", HashedPassword: "hp", AvatarImage: "image", BlackPhoenix: false, DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)}, DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)}, }, expectedErr: domain.UserAlreadyExistsError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { c := gomock.NewController(t) defer c.Finish() db, mock, dbClose := mock_database.GetMockDBx(t) defer dbClose() log := logger.NewLogger(config.Config{Mode: "TEST"}) tc.mockParams.mock = mock tc.mockParams.rows.AddRow( tc.ud.ID, tc.ud.Role, tc.ud.Username, tc.ud.Email, tc.ud.HashedPassword, tc.ud.AvatarImage, tc.ud.BlackPhoenix, tc.ud.DateOfBirth, tc.ud.DateOfRegistration, ) tc.mockParams.email = tc.ud.Email tc.mockParams.hashedPassword = tc.ud.HashedPassword tc.mockParams.username = tc.ud.Username tc.mockParams.avatarImage = tc.ud.AvatarImage tc.mockParams.dateOfBirth = tc.ud.DateOfBirth.Time tc.mockParams.userID = tc.ud.ID tc.mockBehavior(tc.mockParams) repo := &userRepository{db: db, l: log} u, err := repo.Register(tc.mockParams.email, tc.mockParams.hashedPassword, tc.mockParams.username, tc.mockParams.dateOfBirth) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) assert.NoError(t, tc.mockParams.mock.ExpectationsWereMet()) }) } }