package handler import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "tss-rocks-be/ent" "tss-rocks-be/internal/config" "tss-rocks-be/internal/service/mock" "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" "golang.org/x/crypto/bcrypt" ) type AuthHandlerTestSuite struct { suite.Suite ctrl *gomock.Controller service *mock.MockService handler *Handler router *gin.Engine } func (s *AuthHandlerTestSuite) SetupTest() { s.ctrl = gomock.NewController(s.T()) s.service = mock.NewMockService(s.ctrl) s.handler = NewHandler(&config.Config{ JWT: config.JWTConfig{ Secret: "test-secret", }, }, s.service) s.router = gin.New() } func (s *AuthHandlerTestSuite) TearDownTest() { s.ctrl.Finish() } func TestAuthHandlerSuite(t *testing.T) { suite.Run(t, new(AuthHandlerTestSuite)) } func (s *AuthHandlerTestSuite) TestRegister() { testCases := []struct { name string request RegisterRequest setupMock func() expectedStatus int expectedError string }{ { name: "成功注册", request: RegisterRequest{ Username: "testuser", Email: "test@example.com", Password: "password123", Role: "contributor", }, setupMock: func() { s.service.EXPECT(). CreateUser(gomock.Any(), "testuser", "test@example.com", "password123", "contributor"). Return(&ent.User{ ID: 1, Username: "testuser", Email: "test@example.com", }, nil) s.service.EXPECT(). GetUserRoles(gomock.Any(), 1). Return([]*ent.Role{{ID: 1, Name: "contributor"}}, nil) }, expectedStatus: http.StatusCreated, }, { name: "无效的邮箱格式", request: RegisterRequest{ Username: "testuser", Email: "invalid-email", Password: "password123", Role: "contributor", }, setupMock: func() {}, expectedStatus: http.StatusBadRequest, expectedError: "Key: 'RegisterRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag", }, { name: "密码太短", request: RegisterRequest{ Username: "testuser", Email: "test@example.com", Password: "short", Role: "contributor", }, setupMock: func() {}, expectedStatus: http.StatusBadRequest, expectedError: "Key: 'RegisterRequest.Password' Error:Field validation for 'Password' failed on the 'min' tag", }, { name: "无效的角色", request: RegisterRequest{ Username: "testuser", Email: "test@example.com", Password: "password123", Role: "invalid-role", }, setupMock: func() {}, expectedStatus: http.StatusBadRequest, expectedError: "Key: 'RegisterRequest.Role' Error:Field validation for 'Role' failed on the 'oneof' tag", }, } for _, tc := range testCases { s.Run(tc.name, func() { // 设置 mock tc.setupMock() // 创建请求 reqBody, _ := json.Marshal(tc.request) req, _ := http.NewRequest(http.MethodPost, "/register", bytes.NewBuffer(reqBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = req // 执行请求 s.handler.Register(c) // 验证响应 s.Equal(tc.expectedStatus, w.Code) if tc.expectedError != "" { var response map[string]string err := json.Unmarshal(w.Body.Bytes(), &response) s.NoError(err) s.Contains(response["error"], tc.expectedError) } else { var response AuthResponse err := json.Unmarshal(w.Body.Bytes(), &response) s.NoError(err) s.NotEmpty(response.Token) } }) } } func (s *AuthHandlerTestSuite) TestLogin() { testCases := []struct { name string request LoginRequest setupMock func() expectedStatus int expectedError string }{ { name: "成功登录", request: LoginRequest{ Username: "testuser", Password: "password123", }, setupMock: func() { // 使用 bcrypt 生成正确的密码哈希 hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost) user := &ent.User{ ID: 1, Username: "testuser", PasswordHash: string(hashedPassword), } s.service.EXPECT(). GetUserByUsername(gomock.Any(), "testuser"). Return(user, nil) s.service.EXPECT(). GetUserRoles(gomock.Any(), user.ID). Return([]*ent.Role{{Name: "admin"}}, nil) }, expectedStatus: http.StatusOK, }, { name: "无效的用户名", request: LoginRequest{ Username: "invalid", Password: "password123", }, setupMock: func() { s.service.EXPECT(). GetUserByUsername(gomock.Any(), "invalid"). Return(nil, fmt.Errorf("user not found")) }, expectedStatus: http.StatusUnauthorized, expectedError: "Invalid username or password", }, { name: "用户不存在", request: LoginRequest{ Username: "nonexistent", Password: "password123", }, setupMock: func() { s.service.EXPECT(). GetUserByUsername(gomock.Any(), "nonexistent"). Return(nil, fmt.Errorf("user not found")) }, expectedStatus: http.StatusUnauthorized, expectedError: "Invalid username or password", }, { name: "密码错误", request: LoginRequest{ Username: "testuser", Password: "wrong-password", }, setupMock: func() { // 使用 bcrypt 生成正确的密码哈希 hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost) user := &ent.User{ ID: 1, Username: "testuser", PasswordHash: string(hashedPassword), } s.service.EXPECT(). GetUserByUsername(gomock.Any(), "testuser"). Return(user, nil) }, expectedStatus: http.StatusUnauthorized, expectedError: "Invalid username or password", }, { name: "获取用户角色失败", request: LoginRequest{ Username: "testuser", Password: "password123", }, setupMock: func() { // 使用 bcrypt 生成正确的密码哈希 hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost) user := &ent.User{ ID: 1, Username: "testuser", PasswordHash: string(hashedPassword), } s.service.EXPECT(). GetUserByUsername(gomock.Any(), "testuser"). Return(user, nil) s.service.EXPECT(). GetUserRoles(gomock.Any(), user.ID). Return(nil, fmt.Errorf("failed to get roles")) }, expectedStatus: http.StatusInternalServerError, expectedError: "Failed to get user roles", }, } for _, tc := range testCases { s.Run(tc.name, func() { // 设置 mock tc.setupMock() // 创建请求 reqBody, _ := json.Marshal(tc.request) req, _ := http.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(reqBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = req // 执行请求 s.handler.Login(c) // 验证响应 s.Equal(tc.expectedStatus, w.Code) if tc.expectedError != "" { var response map[string]string err := json.Unmarshal(w.Body.Bytes(), &response) s.NoError(err) s.Contains(response["error"], tc.expectedError) } else { var response AuthResponse err := json.Unmarshal(w.Body.Bytes(), &response) s.NoError(err) s.NotEmpty(response.Token) } }) } }