package service import ( "encoding" "errors" "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/domain" "git.urec56.ru/urec/chat_back_go/internal/logger" mock_repository "git.urec56.ru/urec/chat_back_go/internal/repository/mocks" mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks" ) func Test_newUserService(t *testing.T) { c := gomock.NewController(t) defer c.Finish() repo := mock_repository.NewMockUser(c) auth := mock_service.NewMockAuth(c) log := logger.NewLogger(config.Config{Mode: "TEST"}) cache := mock_service.NewMockCache(c) serv := newUserService(repo, auth, log, cache) assert.EqualValues(t, &userService{ur: repo, auth: auth, l: log, cache: cache}, serv) } func TestUserService_Get(t *testing.T) { type repoBehavior func(r *mock_repository.MockUser, userID int, user domain.User, err error) type cacheGetBehavior func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) type cacheSetBehavior func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) type cacheGetData struct { container *domain.User err error } type cacheSetData struct { value domain.User duration time.Duration err error } testTable := []struct { name string repoBehavior repoBehavior cacheGetBehavior cacheGetBehavior cacheSetBehavior cacheSetBehavior cacheKey string cacheGetData cacheGetData cacheSetData cacheSetData userID int repoUser domain.User repoErr error logErr1 error logErr2 error expectedUser domain.User expectedErr error }{ { name: "user_in_cache", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {}, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).DoAndReturn( func(key string, v encoding.BinaryUnmarshaler, isFast bool) error { return v.UnmarshalBinary([]byte(`{"id": 1}`)) }, ) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) {}, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}}, userID: 1, expectedUser: domain.User{ID: 1}, }, { name: "no_user_in_cache", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).Return(err) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) { c.EXPECT().Set(key, v, ex).Return(err) }, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}, err: domain.NoKeyFoundError}, cacheSetData: cacheSetData{value: domain.User{ID: 1}, duration: time.Second * 20}, logErr1: domain.NoKeyFoundError, userID: 1, repoUser: domain.User{ID: 1}, expectedUser: domain.User{ID: 1}, }, { name: "user_not_found", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).Return(err) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) {}, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}, err: domain.NoKeyFoundError}, logErr1: domain.NoKeyFoundError, userID: 1, repoErr: domain.UserNotFoundError, expectedErr: domain.UserNotFoundError, }, { name: "cache_get_error", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).Return(err) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) { c.EXPECT().Set(key, v, ex).Return(err) }, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}, err: domain.InternalServerError}, cacheSetData: cacheSetData{value: domain.User{ID: 1}, duration: time.Second * 20}, logErr1: domain.InternalServerError, userID: 1, repoUser: domain.User{ID: 1}, expectedUser: domain.User{ID: 1}, }, { name: "cache_set_error", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).Return(err) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) { c.EXPECT().Set(key, v, ex).Return(err) }, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}, err: domain.NoKeyFoundError}, cacheSetData: cacheSetData{value: domain.User{ID: 1}, duration: time.Second * 20, err: domain.InternalServerError}, logErr1: domain.NoKeyFoundError, logErr2: domain.InternalServerError, userID: 1, repoUser: domain.User{ID: 1}, expectedUser: domain.User{ID: 1}, }, { name: "repo_error", repoBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, cacheGetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryUnmarshaler, err error) { c.EXPECT().Get(key, gomock.Any(), false).Return(err) }, cacheSetBehavior: func(c *mock_service.MockCache, key string, v encoding.BinaryMarshaler, ex time.Duration, err error) {}, cacheKey: "user: {'id': 1}", cacheGetData: cacheGetData{container: &domain.User{}, err: domain.NoKeyFoundError}, logErr1: domain.NoKeyFoundError, userID: 1, repoErr: domain.InternalServerError, expectedErr: domain.InternalServerError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { c := gomock.NewController(t) defer c.Finish() repo := mock_repository.NewMockUser(c) cache := mock_service.NewMockCache(c) log := logger.NewLogger(config.Config{Mode: "TEST"}) serv := userService{ur: repo, l: log, cache: cache} tc.repoBehavior(repo, tc.userID, tc.repoUser, tc.repoErr) tc.cacheGetBehavior(cache, tc.cacheKey, tc.cacheGetData.container, tc.cacheGetData.err) tc.cacheSetBehavior(cache, tc.cacheKey, &tc.cacheSetData.value, tc.cacheSetData.duration, tc.cacheSetData.err) u, err := serv.Get(tc.userID, false) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) }) } } func TestUserService_GetVerificated(t *testing.T) { type mockBehavior func(r *mock_repository.MockUser, userID int, user domain.User, err error) testTable := []struct { name string mockBehavior mockBehavior userID int expectedUser domain.User repoErr error expectedErr error }{ { name: "ok", mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, userID: 1, expectedUser: domain.User{ ID: 1, Role: domain.VerificatedUser, }, }, { name: "user_not_found", mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, userID: 1, repoErr: domain.UserNotFoundError, expectedErr: domain.UserNotFoundError, }, { name: "user_unverified", mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) { r.EXPECT().GetByID(userID).Return(user, err) }, userID: 1, expectedUser: domain.User{ ID: 1, Role: domain.UnverifiedUser, }, expectedErr: domain.UnverifiedUserError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { c := gomock.NewController(t) defer c.Finish() repo := mock_repository.NewMockUser(c) serv := userService{ur: repo} tc.mockBehavior(repo, tc.userID, tc.expectedUser, tc.repoErr) u, err := serv.GetVerificated(tc.userID) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) }) } } func TestUserService_GetAll(t *testing.T) { type mockBehavior func(r *mock_repository.MockUser, username string, users []domain.User, err error) testTable := []struct { name string mockBehavior mockBehavior username string expectedUsers []domain.User repoErr error expectedErr error }{ { name: "ok", mockBehavior: func(r *mock_repository.MockUser, username string, users []domain.User, err error) { r.EXPECT().GetAll(username).Return(users, err) }, expectedUsers: []domain.User{ {ID: 1}, }, }, { name: "internal_error", mockBehavior: func(r *mock_repository.MockUser, username string, users []domain.User, err error) { r.EXPECT().GetAll(username).Return(users, err) }, repoErr: domain.InternalServerError, expectedErr: domain.InternalServerError, }, } for _, tc := range testTable { t.Run(tc.name, func(t *testing.T) { c := gomock.NewController(t) defer c.Finish() repo := mock_repository.NewMockUser(c) serv := userService{ur: repo} tc.mockBehavior(repo, tc.username, tc.expectedUsers, tc.repoErr) u, err := serv.GetAll(tc.username) assert.Equal(t, tc.expectedUsers, u) assert.ErrorIs(t, err, tc.expectedErr) }) } } func TestUserService_FindOne(t *testing.T) { type mockBehavior func(r *mock_repository.MockUser, username, email string, user domain.User, err error) testTable := []struct { name string mockBehavior mockBehavior username string email string expectedUser domain.User repoErr error expectedErr error }{ { name: "ok", mockBehavior: func(r *mock_repository.MockUser, username, email string, user domain.User, err error) { r.EXPECT().FindOne(username, email).Return(user, err) }, expectedUser: domain.User{ ID: 1, Role: domain.UnverifiedUser, }, }, { name: "any_error", mockBehavior: func(r *mock_repository.MockUser, username, email string, user domain.User, err error) { r.EXPECT().FindOne(username, email).Return(user, err) }, repoErr: 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() repo := mock_repository.NewMockUser(c) serv := userService{ur: repo} tc.mockBehavior(repo, tc.username, tc.email, tc.expectedUser, tc.repoErr) u, err := serv.FindOne(tc.username, tc.email) assert.Equal(t, tc.expectedUser, u) assert.ErrorIs(t, err, tc.expectedErr) }) } } func TestUserService_Register(t *testing.T) { type hashMockBehavior func(s *mock_service.MockAuth, p, hp string, err error) type repoMockBehavior func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) type encodeMockBehavior func(s *mock_service.MockAuth, userID int, token string, err error) testTable := []struct { name string hashMockBehavior hashMockBehavior repoMockBehavior repoMockBehavior encodeMockBehavior encodeMockBehavior ud domain.UserRegister hp string repoUser domain.User hashErr error repoErr error encodeErr error expectedToken string expectedErr error }{ { name: "ok", hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) { s.EXPECT().HashPassword(p).Return(hp, err) }, repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) { r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err) }, encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) { s.EXPECT().EncodeAuthToken(userID).Return(token, err) }, ud: domain.UserRegister{ Email: "mail@mail.ru", Username: "username", Password: "pass", }, hp: "hp", repoUser: domain.User{ID: 1}, expectedToken: "token", }, { name: "user_already_exists", hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) { s.EXPECT().HashPassword(p).Return(hp, err) }, repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) { r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err) }, encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {}, repoErr: domain.UserAlreadyExistsError, expectedErr: domain.UserAlreadyExistsError, }, { name: "hash_failed", hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) { s.EXPECT().HashPassword(p).Return(hp, err) }, repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) { }, encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {}, hashErr: errors.New("some error"), expectedErr: domain.InternalServerError, }, { name: "db_error", hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) { s.EXPECT().HashPassword(p).Return(hp, err) }, repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) { r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err) }, encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {}, repoErr: errors.New("some error"), expectedErr: domain.InternalServerError, }, { name: "token_encoding_error", hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) { s.EXPECT().HashPassword(p).Return(hp, err) }, repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) { r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err) }, encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) { s.EXPECT().EncodeAuthToken(userID).Return(token, err) }, encodeErr: 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() repo := mock_repository.NewMockUser(c) auth := mock_service.NewMockAuth(c) serv := userService{ur: repo, auth: auth} tc.hashMockBehavior(auth, tc.ud.Password, tc.hp, tc.hashErr) tc.repoMockBehavior(repo, tc.ud.Email, tc.hp, tc.ud.Username, tc.ud.DateOfBirth.Time, tc.repoUser, tc.repoErr) tc.encodeMockBehavior(auth, tc.repoUser.ID, tc.expectedToken, tc.encodeErr) token, err := serv.Register(tc.ud) assert.Equal(t, tc.expectedToken, token) assert.ErrorIs(t, err, tc.expectedErr) }) } }