/* * Copyright 2024 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package handlers import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "d7y.io/dragonfly/v2/manager/models" "d7y.io/dragonfly/v2/manager/service/mocks" "d7y.io/dragonfly/v2/manager/types" ) var ( mockUserReqBody = ` { "bio": "bio", "email": "test@test.com", "phone": "12345678900" }` mockUserSignupReqBody = ` { "bio": "bio", "email": "test@test.com", "name": "name", "password": "987654321", "phone": "12345678900" }` mockResetPasswordReqBody = ` { "new_password": "123456789", "old_password": "987654321" }` mockUpdateUserRequest = types.UpdateUserRequest{ Email: "test@test.com", Phone: "12345678900", BIO: "bio", } mockUserSignupRequest = types.SignUpRequest{ SignInRequest: types.SignInRequest{ Name: "name", Password: "987654321", }, Email: "test@test.com", Phone: "12345678900", BIO: "bio", } mockResetPasswordRequest = types.ResetPasswordRequest{ OldPassword: "987654321", NewPassword: "123456789", } mockUserModel = &models.User{ BaseModel: mockBaseModel, Email: "test@test.com", Name: "name", Phone: "1234567890", BIO: "bio", } ) func mockUserRouter(h *Handlers) *gin.Engine { r := gin.Default() apiv1 := r.Group("/api/v1") u := apiv1.Group("/users") u.PATCH(":id", h.UpdateUser) u.GET(":id", h.GetUser) u.GET("", h.GetUsers) u.POST("signup", h.SignUp) u.GET("signin/:name", h.OauthSignin) u.GET("signin/:name/callback", h.OauthSigninCallback(nil)) u.POST(":id/reset_password", h.ResetPassword) u.GET(":id/roles", h.GetRolesForUser) u.PUT(":id/roles/:role", h.AddRoleToUser) u.DELETE(":id/roles/:role", h.DeleteRoleForUser) return r } func TestHandlers_UpdateUser(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity caused by uri", req: httptest.NewRequest(http.MethodPatch, "/api/v1/users/test", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "unprocessable entity caused by body", req: httptest.NewRequest(http.MethodPatch, "/api/v1/users/2", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodPatch, "/api/v1/users/2", strings.NewReader(mockUserReqBody)), mock: func(ms *mocks.MockServiceMockRecorder) { ms.UpdateUser(gomock.Any(), gomock.Eq(uint(2)), gomock.Eq(mockUpdateUserRequest)).Return(mockUserModel, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) user := models.User{} err := json.Unmarshal(w.Body.Bytes(), &user) assert.NoError(err) assert.Equal(mockUserModel, &user) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_GetUser(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodGet, "/api/v1/users/test", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodGet, "/api/v1/users/2", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.GetUser(gomock.Any(), gomock.Eq(uint(2))).Return(mockUserModel, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) user := models.User{} err := json.Unmarshal(w.Body.Bytes(), &user) assert.NoError(err) assert.Equal(mockUserModel, &user) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_GetUsers(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodGet, "/api/v1/users?page=-1", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodGet, "/api/v1/users?name=name", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.GetUsers(gomock.Any(), gomock.Eq(types.GetUsersQuery{ Name: "name", Page: 1, PerPage: 10, })).Return([]models.User{*mockUserModel}, int64(1), nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) user := models.User{} err := json.Unmarshal(w.Body.Bytes()[1:w.Body.Len()-1], &user) assert.NoError(err) assert.Equal(mockUserModel, &user) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_SignUp(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodPost, "/api/v1/users/signup", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodPost, "/api/v1/users/signup", strings.NewReader(mockUserSignupReqBody)), mock: func(ms *mocks.MockServiceMockRecorder) { ms.SignUp(gomock.Any(), gomock.Eq(mockUserSignupRequest)).Return(mockUserModel, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) user := models.User{} err := json.Unmarshal(w.Body.Bytes(), &user) assert.NoError(err) assert.Equal(mockUserModel, &user) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_ResetPassword(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity caused by uri", req: httptest.NewRequest(http.MethodPost, "/api/v1/users/test/reset_password", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "unprocessable entity caused by body", req: httptest.NewRequest(http.MethodPost, "/api/v1/users/2/reset_password", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodPost, "/api/v1/users/2/reset_password", strings.NewReader(mockResetPasswordReqBody)), mock: func(ms *mocks.MockServiceMockRecorder) { ms.ResetPassword(gomock.Any(), uint(2), gomock.Eq(mockResetPasswordRequest)).Return(nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_OauthSignin(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "success", req: httptest.NewRequest(http.MethodGet, "/api/v1/users/signin/name", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.OauthSignin(gomock.Any(), "name").Return("", nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusFound, w.Code) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_GetRolesForUser(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodGet, "/api/v1/users/test/roles", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodGet, "/api/v1/users/2/roles", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.GetRolesForUser(gomock.Any(), gomock.Eq(uint(2))).Return([]string{"maintainer"}, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) assert.Equal(w.Body.String(), `["maintainer"]`) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_AddRoleToUser(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodPut, "/api/v1/users/test/roles/maintainer", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodPut, "/api/v1/users/2/roles/maintainer", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.AddRoleForUser(gomock.Any(), gomock.Eq(types.AddRoleForUserParams{ ID: 2, Role: "maintainer", })).Return(true, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } } func TestHandlers_DeleteRoleForUser(t *testing.T) { tests := []struct { name string req *http.Request mock func(ms *mocks.MockServiceMockRecorder) expect func(t *testing.T, w *httptest.ResponseRecorder) }{ { name: "unprocessable entity", req: httptest.NewRequest(http.MethodDelete, "/api/v1/users/test/roles/maintainer", nil), mock: func(ms *mocks.MockServiceMockRecorder) {}, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusUnprocessableEntity, w.Code) }, }, { name: "success", req: httptest.NewRequest(http.MethodDelete, "/api/v1/users/2/roles/maintainer", nil), mock: func(ms *mocks.MockServiceMockRecorder) { ms.DeleteRoleForUser(gomock.Any(), gomock.Eq(types.DeleteRoleForUserParams{ ID: 2, Role: "maintainer", })).Return(true, nil).Times(1) }, expect: func(t *testing.T, w *httptest.ResponseRecorder) { assert := assert.New(t) assert.Equal(http.StatusOK, w.Code) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() svc := mocks.NewMockService(ctl) w := httptest.NewRecorder() h := New(svc) mockRouter := mockUserRouter(h) tc.mock(svc.EXPECT()) mockRouter.ServeHTTP(w, tc.req) tc.expect(t, w) }) } }