From ceeb7564d9a2a11e21545ca7ad9f4eb0d7303b7e Mon Sep 17 00:00:00 2001 From: Sina Chaichi Maleki Date: Fri, 15 May 2026 02:41:29 +0200 Subject: [PATCH] Add custom mock test for accurate test scenarios --- api/user_test.go | 226 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 api/user_test.go diff --git a/api/user_test.go b/api/user_test.go new file mode 100644 index 0000000..d4a5f24 --- /dev/null +++ b/api/user_test.go @@ -0,0 +1,226 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + mockdb "github.com/sinachaichi/gault/db/mock" + db "github.com/sinachaichi/gault/db/sqlc" + "github.com/sinachaichi/gault/util" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + + +type eqCreateUserParamsMatcher struct { + arg db.CreateUserParams + password string +} + +func (e eqCreateUserParamsMatcher) Matches(x interface{}) bool { + arg, ok := x.(db.CreateUserParams) + if !ok { + return false + } + + err := util.CheckPassword(e.password, arg.HashedPassword) + if err != nil { + return false + } + + e.arg.HashedPassword = arg.HashedPassword + return reflect.DeepEqual(e.arg, arg) +} + +func (e eqCreateUserParamsMatcher) String() string { + return fmt.Sprintf("matches arg %v and password %v", e.arg, e.password) +} + +func EqCreateUserParams(arg db.CreateUserParams, password string) gomock.Matcher { + return eqCreateUserParamsMatcher{arg, password} +} + +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 requireBodyMatchUser(t *testing.T, body *bytes.Buffer, user db.User) { + data, err := io.ReadAll(body) + require.NoError(t, err) + + var gotUser db.User + err = json.Unmarshal(data, &gotUser) + + require.NoError(t, err) + require.Equal(t, user.Username, gotUser.Username) + require.Equal(t, user.FullName, gotUser.FullName) + require.Equal(t, user.Email, gotUser.Email) + require.Empty(t, gotUser.HashedPassword) +} + +func TestCreateUserAPI(t *testing.T) { + user, password := randomUser(t) + + testCases := []struct { + name string + body gin.H + buildStubs func(store *mockdb.MockStore) + checkResponse func(recoder *httptest.ResponseRecorder) + }{ + { + name: "OK", + body: gin.H{ + "username": user.Username, + "password": password, + "full_name": user.FullName, + "email": user.Email, + }, + buildStubs: func(store *mockdb.MockStore) { + arg := db.CreateUserParams{ + Username: user.Username, + FullName: user.FullName, + Email: user.Email, + } + store.EXPECT(). + CreateUser(gomock.Any(), EqCreateUserParams(arg, password)). + Times(1). + Return(user, nil) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchUser(t, recorder.Body, user) + }, + }, + // { + // name: "InternalError", + // body: gin.H{ + // "username": user.Username, + // "password": password, + // "full_name": user.FullName, + // "email": user.Email, + // }, + // buildStubs: func(store *mockdb.MockStore) { + // store.EXPECT(). + // CreateUser(gomock.Any(), gomock.Any()). + // Times(1). + // Return(db.User{}, sql.ErrConnDone) + // }, + // checkResponse: func(recorder *httptest.ResponseRecorder) { + // require.Equal(t, http.StatusInternalServerError, recorder.Code) + // }, + // }, + // { + // name: "DuplicateUsername", + // body: gin.H{ + // "username": user.Username, + // "password": password, + // "full_name": user.FullName, + // "email": user.Email, + // }, + // buildStubs: func(store *mockdb.MockStore) { + // store.EXPECT(). + // CreateUser(gomock.Any(), gomock.Any()). + // Times(1). + // Return(db.User{}, &pq.Error{Code: "23505"}) + // }, + // checkResponse: func(recorder *httptest.ResponseRecorder) { + // require.Equal(t, http.StatusForbidden, recorder.Code) + // }, + // }, + // { + // name: "InvalidUsername", + // body: gin.H{ + // "username": "invalid-user#1", + // "password": password, + // "full_name": user.FullName, + // "email": user.Email, + // }, + // buildStubs: func(store *mockdb.MockStore) { + // store.EXPECT(). + // CreateUser(gomock.Any(), gomock.Any()). + // Times(0) + // }, + // checkResponse: func(recorder *httptest.ResponseRecorder) { + // require.Equal(t, http.StatusBadRequest, recorder.Code) + // }, + // }, + // { + // name: "InvalidEmail", + // body: gin.H{ + // "username": user.Username, + // "password": password, + // "full_name": user.FullName, + // "email": "invalid-email", + // }, + // buildStubs: func(store *mockdb.MockStore) { + // store.EXPECT(). + // CreateUser(gomock.Any(), gomock.Any()). + // Times(0) + // }, + // checkResponse: func(recorder *httptest.ResponseRecorder) { + // require.Equal(t, http.StatusBadRequest, recorder.Code) + // }, + // }, + // { + // name: "TooShortPassword", + // body: gin.H{ + // "username": user.Username, + // "password": "123", + // "full_name": user.FullName, + // "email": user.Email, + // }, + // buildStubs: func(store *mockdb.MockStore) { + // store.EXPECT(). + // CreateUser(gomock.Any(), gomock.Any()). + // Times(0) + // }, + // checkResponse: func(recorder *httptest.ResponseRecorder) { + // require.Equal(t, http.StatusBadRequest, recorder.Code) + // }, + // }, + } + + for i := range testCases { + tc := testCases[i] + + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + store := mockdb.NewMockStore(ctrl) + tc.buildStubs(store) + + server := NewServer(store) + recorder := httptest.NewRecorder() + + // Marshal body data to JSON + data, err := json.Marshal(tc.body) + require.NoError(t, err) + + url := "/users" + request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) + require.NoError(t, err) + + server.router.ServeHTTP(recorder, request) + tc.checkResponse(recorder) + }) + } +} +