Skip to content

Commit

Permalink
add grpc unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
hafizxd committed Feb 25, 2024
1 parent 788714b commit f91b504
Show file tree
Hide file tree
Showing 5 changed files with 412 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ server:

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

db_docs:
dbdocs build doc/db.dbml
Expand Down
39 changes: 39 additions & 0 deletions gapi/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package gapi

import (
"context"
"fmt"
db "github.com/hafizxd/micro-bank/db/sqlc"
"github.com/hafizxd/micro-bank/token"
"github.com/hafizxd/micro-bank/util"
"github.com/hafizxd/micro-bank/worker"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
"testing"
"time"
)

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

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

return server
}

func newContextWithBearerToken(t *testing.T, tokenMaker token.Maker, username string, duration time.Duration) context.Context {
accessToken, _, err := tokenMaker.CreateToken(username, duration)
require.NoError(t, err)

bearerToken := fmt.Sprintf("%s %s", authorizationBearer, accessToken)
md := metadata.MD{
authorizationHeader: []string{
bearerToken,
},
}
return metadata.NewIncomingContext(context.Background(), md)
}
177 changes: 177 additions & 0 deletions gapi/rpc_create_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package gapi

import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/golang/mock/gomock"
mockdb "github.com/hafizxd/micro-bank/db/mock"
db "github.com/hafizxd/micro-bank/db/sqlc"
"github.com/hafizxd/micro-bank/pb"
"github.com/hafizxd/micro-bank/util"
"github.com/hafizxd/micro-bank/worker"
mockwk "github.com/hafizxd/micro-bank/worker/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"io"
"reflect"
"testing"
"time"
)

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 (expected eqCreateUserTxParamsMatcher) String() string {
return fmt.Sprintf("matches arg %v and password %v", expected.arg, expected.password)
}

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

func TestCreateUserApi(t *testing.T) {
user, password := createRandomUser(t)

testCases := []struct {
name string
requestBody *pb.CreateUserRequest
buildStubs func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor)
checkResponse func(t *testing.T, res *pb.CreateUserResponse, err error)
}{
{
name: "OK",
requestBody: &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)).
MaxTimes(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",
requestBody: &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()).
MaxTimes(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())
},
},
}

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

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

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

tc.buildStubs(store, taskDistributor)

server := newTestServer(t, store, taskDistributor)
res, err := server.CreateUser(context.Background(), tc.requestBody)
tc.checkResponse(t, res, err)
})
}
}

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

return db.User{
Username: util.RandomOwner(),
HashedPassword: hashedPassword,
FullName: util.RandomOwner(),
Email: util.RandomEmail(),
CreatedAt: time.Time{},
PasswordChangedAt: time.Time{},
}, password
}

func requireMatchBodyUser(t *testing.T, body *bytes.Buffer, user db.User) {
response, err := io.ReadAll(body)
require.NoError(t, err)

var gotUser db.User
err = json.Unmarshal(response, &gotUser)
require.NoError(t, err)
require.NotEmpty(t, gotUser)
require.Equal(t, user.Username, gotUser.Username)
}
139 changes: 139 additions & 0 deletions gapi/rpc_update_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package gapi

import (
"context"
"database/sql"
"github.com/golang/mock/gomock"
mockdb "github.com/hafizxd/micro-bank/db/mock"
db "github.com/hafizxd/micro-bank/db/sqlc"
"github.com/hafizxd/micro-bank/pb"
"github.com/hafizxd/micro-bank/token"
"github.com/hafizxd/micro-bank/util"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"testing"
"time"
)

func TestUpdateUserApi(t *testing.T) {
user, _ := createRandomUser(t)

newName := util.RandomOwner()
newEmail := util.RandomEmail()

testCases := []struct {
name string
requestBody *pb.UpdateUserRequest
buildStubs func(store *mockdb.MockStore)
buildContext func(t *testing.T, tokenMaker token.Maker) context.Context
checkResponse func(t *testing.T, res *pb.UpdateUserResponse, err error)
}{
{
name: "OK",
requestBody: &pb.UpdateUserRequest{
Username: user.Username,
FullName: &newName,
Email: &newEmail,
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.UpdateUserParams{
Username: user.Username,
FullName: sql.NullString{
String: newName,
Valid: true,
},
Email: sql.NullString{
String: newEmail,
Valid: true,
},
}
updatedUser := db.User{
Username: user.Username,
HashedPassword: user.HashedPassword,
FullName: newName,
Email: newEmail,
PasswordChangedAt: user.PasswordChangedAt,
CreatedAt: user.CreatedAt,
IsEmailVerified: user.IsEmailVerified,
}
store.EXPECT().
UpdateUser(gomock.Any(), gomock.Eq(arg)).
MaxTimes(1).
Return(updatedUser, nil)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, time.Minute)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.NoError(t, err)
require.NotNil(t, res)
updatedUser := res.GetUser()
require.Equal(t, user.Username, updatedUser.Username)
require.Equal(t, newName, updatedUser.FullName)
require.Equal(t, newEmail, updatedUser.Email)
},
},
{
name: "UserNotFound",
requestBody: &pb.UpdateUserRequest{
Username: user.Username,
FullName: &newName,
Email: &newEmail,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
UpdateUser(gomock.Any(), gomock.Any()).
MaxTimes(1).
Return(db.User{}, sql.ErrNoRows)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, time.Minute)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.NotFound, st.Code())
},
},
{
name: "ExpiredToken",
requestBody: &pb.UpdateUserRequest{
Username: user.Username,
FullName: &newName,
Email: &newEmail,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
UpdateUser(gomock.Any(), gomock.Any()).
MaxTimes(0)
},
buildContext: func(t *testing.T, tokenMaker token.Maker) context.Context {
return newContextWithBearerToken(t, tokenMaker, user.Username, -time.Minute)
},
checkResponse: func(t *testing.T, res *pb.UpdateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.Unauthenticated, st.Code())
},
},
}

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

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

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

ctx := tc.buildContext(t, server.tokenMaker)
res, err := server.UpdateUser(ctx, tc.requestBody)
tc.checkResponse(t, res, err)
})
}
}
Loading

0 comments on commit f91b504

Please sign in to comment.