Skip to content

Commit

Permalink
add unit tests for create_user_rpc (techschool#87)
Browse files Browse the repository at this point in the history
Co-authored-by: phamlequang <phamlequang@gmail.com>
  • Loading branch information
techschool and phamlequang authored Apr 2, 2023
1 parent d85d528 commit 81749ac
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ server:

mock:
mockgen -package mockdb -destination db/mock/store.go github.com/techschool/simplebank/db/sqlc Store
mockgen -package mockwk -destination worker/mock/distributor.go github.com/techschool/simplebank/worker TaskDistributor

proto:
rm -f pb/*.go
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ This course is designed with a lot of details, so that everyone, even with very
- Lecture #59: [How to send emails in Go via Gmail](https://www.youtube.com/watch?v=L9TbZxpykLQ&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE&index=59)
- Lecture #60: [How to skip test in Go and config test flag in vscode](https://www.youtube.com/watch?v=0UwZGM9iqTE&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE&index=60)
- Lecture #61: [Email verification in Go: design DB and send email](https://www.youtube.com/watch?v=lEHkwDPHrcc&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE&index=61)
- Lecture #62: [Implement email verification API in Go](https://www.youtube.com/watch?v=50ZN-4UNwnY&list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE&index=62)

## Simple bank service

Expand Down
23 changes: 23 additions & 0 deletions gapi/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gapi

import (
"testing"
"time"

"github.com/stretchr/testify/require"
db "github.com/techschool/simplebank/db/sqlc"
"github.com/techschool/simplebank/util"
"github.com/techschool/simplebank/worker"
)

func newTestServer(t *testing.T, store db.Store, taskDistributor worker.TaskDistributor) *Server {
config := util.Config{
TokenSymmetricKey: util.RandomString(32),
AccessTokenDuration: time.Minute,
}

server, err := NewServer(config, store, taskDistributor)
require.NoError(t, err)

return server
}
213 changes: 213 additions & 0 deletions gapi/rpc_create_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package gapi

import (
"context"
"database/sql"
"fmt"
"reflect"
"testing"

"github.com/golang/mock/gomock"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
mockdb "github.com/techschool/simplebank/db/mock"
db "github.com/techschool/simplebank/db/sqlc"
"github.com/techschool/simplebank/pb"
"github.com/techschool/simplebank/util"
"github.com/techschool/simplebank/worker"
mockwk "github.com/techschool/simplebank/worker/mock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type eqCreateUserTxParamsMatcher struct {
arg db.CreateUserTxParams
password string
user db.User
}

func (expected eqCreateUserTxParamsMatcher) Matches(x interface{}) bool {
actualArg, ok := x.(db.CreateUserTxParams)
if !ok {
return false
}

err := util.CheckPassword(expected.password, actualArg.HashedPassword)
if err != nil {
return false
}

expected.arg.HashedPassword = actualArg.HashedPassword
if !reflect.DeepEqual(expected.arg.CreateUserParams, actualArg.CreateUserParams) {
return false
}

err = actualArg.AfterCreate(expected.user)
return err == nil
}

func (e eqCreateUserTxParamsMatcher) String() string {
return fmt.Sprintf("matches arg %v and password %v", e.arg, e.password)
}

func EqCreateUserTxParams(arg db.CreateUserTxParams, password string, user db.User) gomock.Matcher {
return eqCreateUserTxParamsMatcher{arg, password, user}
}

func randomUser(t *testing.T) (user db.User, password string) {
password = util.RandomString(6)
hashedPassword, err := util.HashPassword(password)
require.NoError(t, err)

user = db.User{
Username: util.RandomOwner(),
HashedPassword: hashedPassword,
FullName: util.RandomOwner(),
Email: util.RandomEmail(),
}
return
}

func TestCreateUserAPI(t *testing.T) {
user, password := randomUser(t)

testCases := []struct {
name string
req *pb.CreateUserRequest
buildStubs func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor)
checkResponse func(t *testing.T, res *pb.CreateUserResponse, err error)
}{
{
name: "OK",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: user.Email,
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
arg := db.CreateUserTxParams{
CreateUserParams: db.CreateUserParams{
Username: user.Username,
FullName: user.FullName,
Email: user.Email,
},
}
store.EXPECT().
CreateUserTx(gomock.Any(), EqCreateUserTxParams(arg, password, user)).
Times(1).
Return(db.CreateUserTxResult{User: user}, nil)

taskPayload := &worker.PayloadSendVerifyEmail{
Username: user.Username,
}
taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), taskPayload, gomock.Any()).
Times(1).
Return(nil)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.NoError(t, err)
require.NotNil(t, res)
createdUser := res.GetUser()
require.Equal(t, user.Username, createdUser.Username)
require.Equal(t, user.FullName, createdUser.FullName)
require.Equal(t, user.Email, createdUser.Email)
},
},
{
name: "InternalError",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: user.Email,
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
store.EXPECT().
CreateUserTx(gomock.Any(), gomock.Any()).
Times(1).
Return(db.CreateUserTxResult{}, sql.ErrConnDone)

taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), gomock.Any(), gomock.Any()).
Times(0)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.Internal, st.Code())
},
},
{
name: "DuplicateUsername",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: user.Email,
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
store.EXPECT().
CreateUserTx(gomock.Any(), gomock.Any()).
Times(1).
Return(db.CreateUserTxResult{}, &pq.Error{Code: "23505"})

taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), gomock.Any(), gomock.Any()).
Times(0)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.AlreadyExists, st.Code())
},
},
{
name: "InvalidEmail",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: "invalid-email",
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
store.EXPECT().
CreateUserTx(gomock.Any(), gomock.Any()).
Times(0)

taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), gomock.Any(), gomock.Any()).
Times(0)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.InvalidArgument, st.Code())
},
},
}

for i := range testCases {
tc := testCases[i]

t.Run(tc.name, func(t *testing.T) {
storeCtrl := gomock.NewController(t)
defer storeCtrl.Finish()
store := mockdb.NewMockStore(storeCtrl)

taskCtrl := gomock.NewController(t)
defer taskCtrl.Finish()
taskDistributor := mockwk.NewMockTaskDistributor(taskCtrl)

tc.buildStubs(store, taskDistributor)
server := newTestServer(t, store, taskDistributor)

res, err := server.CreateUser(context.Background(), tc.req)
tc.checkResponse(t, res, err)
})
}
}
55 changes: 55 additions & 0 deletions worker/mock/distributor.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 81749ac

Please sign in to comment.