[chore/backend] remove all test for now

This commit is contained in:
CDN 2025-02-22 02:11:27 +08:00
parent 3d19ef05b3
commit 1c9628124f
Signed by: CDN
GPG key ID: 0C656827F9F80080
28 changed files with 0 additions and 6780 deletions

View file

@ -1,312 +0,0 @@
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",
},
Auth: config.AuthConfig{
Registration: struct {
Enabled bool `yaml:"enabled"`
Message string `yaml:"message"`
}{
Enabled: true,
Message: "Registration is disabled",
},
},
}, s.service)
s.router = gin.New()
}
func (s *AuthHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestAuthHandlerSuite(t *testing.T) {
suite.Run(t, new(AuthHandlerTestSuite))
}
type ErrorResponse struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (s *AuthHandlerTestSuite) TestRegister() {
testCases := []struct {
name string
request RegisterRequest
setupMock func()
expectedStatus int
expectedError string
registration bool
}{
{
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,
registration: true,
},
{
name: "注册功能已禁用",
request: RegisterRequest{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
Role: "contributor",
},
setupMock: func() {},
expectedStatus: http.StatusForbidden,
expectedError: "Registration is disabled",
registration: false,
},
{
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",
registration: true,
},
{
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",
registration: true,
},
{
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",
registration: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置注册功能状态
s.handler.cfg.Auth.Registration.Enabled = tc.registration
// 设置 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 ErrorResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.Contains(response.Error.Message, tc.expectedError)
} else {
var response AuthResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.NotEmpty(response.Token)
}
})
}
}
func (s *AuthHandlerTestSuite) TestLogin() {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
testCases := []struct {
name string
request LoginRequest
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功登录",
request: LoginRequest{
Username: "testuser",
Password: "password123",
},
setupMock: func() {
s.service.EXPECT().
GetUserByUsername(gomock.Any(), "testuser").
Return(&ent.User{
ID: 1,
Username: "testuser",
PasswordHash: string(hashedPassword),
}, nil)
s.service.EXPECT().
GetUserRoles(gomock.Any(), 1).
Return([]*ent.Role{{ID: 1, Name: "contributor"}}, nil)
},
expectedStatus: http.StatusOK,
},
{
name: "无效的用户名",
request: LoginRequest{
Username: "te",
Password: "password123",
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedError: "Key: 'LoginRequest.Username' Error:Field validation for 'Username' failed on the 'min' tag",
},
{
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: "wrongpassword",
},
setupMock: func() {
s.service.EXPECT().
GetUserByUsername(gomock.Any(), "testuser").
Return(&ent.User{
ID: 1,
Username: "testuser",
PasswordHash: string(hashedPassword),
}, nil)
},
expectedStatus: http.StatusUnauthorized,
expectedError: "Invalid username or password",
},
{
name: "获取用户角色失败",
request: LoginRequest{
Username: "testuser",
Password: "password123",
},
setupMock: func() {
s.service.EXPECT().
GetUserByUsername(gomock.Any(), "testuser").
Return(&ent.User{
ID: 1,
Username: "testuser",
PasswordHash: string(hashedPassword),
}, nil)
s.service.EXPECT().
GetUserRoles(gomock.Any(), 1).
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 ErrorResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.Contains(response.Error.Message, tc.expectedError)
} else {
var response AuthResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.NotEmpty(response.Token)
}
})
}
}

View file

@ -1,481 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"tss-rocks-be/ent"
"tss-rocks-be/ent/categorycontent"
"tss-rocks-be/internal/config"
"tss-rocks-be/internal/service"
"tss-rocks-be/internal/service/mock"
"tss-rocks-be/internal/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"errors"
)
// Custom assertion function for comparing categories
func assertCategoryEqual(t assert.TestingT, expected, actual *ent.Category) bool {
if expected == nil && actual == nil {
return true
}
if expected == nil || actual == nil {
return assert.Fail(t, "One category is nil while the other is not")
}
// Compare only relevant fields, ignoring time fields
return assert.Equal(t, expected.ID, actual.ID) &&
assert.Equal(t, expected.Edges.Contents, actual.Edges.Contents)
}
// Custom assertion function for comparing category slices
func assertCategorySliceEqual(t assert.TestingT, expected, actual []*ent.Category) bool {
if len(expected) != len(actual) {
return assert.Fail(t, "Category slice lengths do not match")
}
for i := range expected {
if !assertCategoryEqual(t, expected[i], actual[i]) {
return false
}
}
return true
}
type CategoryHandlerTestSuite struct {
suite.Suite
ctrl *gomock.Controller
service *mock.MockService
handler *Handler
router *gin.Engine
}
func (s *CategoryHandlerTestSuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.service = mock.NewMockService(s.ctrl)
cfg := &config.Config{
JWT: config.JWTConfig{
Secret: "test-secret",
},
}
s.handler = NewHandler(cfg, s.service)
// Setup Gin router
gin.SetMode(gin.TestMode)
s.router = gin.New()
// Setup mock for GetTokenBlacklist
tokenBlacklist := &service.TokenBlacklist{}
s.service.EXPECT().
GetTokenBlacklist().
Return(tokenBlacklist).
AnyTimes()
s.handler.RegisterRoutes(s.router)
}
func (s *CategoryHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestCategoryHandlerSuite(t *testing.T) {
suite.Run(t, new(CategoryHandlerTestSuite))
}
// Test cases for ListCategories
func (s *CategoryHandlerTestSuite) TestListCategories() {
testCases := []struct {
name string
langCode string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success with default language",
langCode: "",
setupMock: func() {
s.service.EXPECT().
ListCategories(gomock.Any(), gomock.Eq("en")).
Return([]*ent.Category{
{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: "Test Description",
Slug: "test-category",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Category{
{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: "Test Description",
Slug: "test-category",
},
},
},
},
},
},
{
name: "Success with specific language",
langCode: "zh",
setupMock: func() {
s.service.EXPECT().
ListCategories(gomock.Any(), gomock.Eq("zh")).
Return([]*ent.Category{
{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("zh"),
Name: "测试分类",
Description: "测试描述",
Slug: "test-category",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Category{
{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("zh"),
Name: "测试分类",
Description: "测试描述",
Slug: "test-category",
},
},
},
},
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// Setup mock
tc.setupMock()
// Create request
url := "/api/v1/categories"
if tc.langCode != "" {
url += "?lang=" + tc.langCode
}
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
// Perform request
s.router.ServeHTTP(w, req)
// Assert response
assert.Equal(s.T(), tc.expectedStatus, w.Code)
if tc.expectedBody != nil {
var response []*ent.Category
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(s.T(), err)
assertCategorySliceEqual(s.T(), tc.expectedBody.([]*ent.Category), response)
}
})
}
}
// Test cases for GetCategory
func (s *CategoryHandlerTestSuite) TestGetCategory() {
testCases := []struct {
name string
langCode string
slug string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
langCode: "en",
slug: "test-category",
setupMock: func() {
s.service.EXPECT().
GetCategoryBySlug(gomock.Any(), gomock.Eq("en"), gomock.Eq("test-category")).
Return(&ent.Category{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: "Test Description",
Slug: "test-category",
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: &ent.Category{
ID: 1,
Edges: ent.CategoryEdges{
Contents: []*ent.CategoryContent{
{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: "Test Description",
Slug: "test-category",
},
},
},
},
},
{
name: "Not Found",
langCode: "en",
slug: "non-existent",
setupMock: func() {
s.service.EXPECT().
GetCategoryBySlug(gomock.Any(), gomock.Eq("en"), gomock.Eq("non-existent")).
Return(nil, types.ErrNotFound)
},
expectedStatus: http.StatusNotFound,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// Setup mock
tc.setupMock()
// Create request
url := "/api/v1/categories/" + tc.slug
if tc.langCode != "" {
url += "?lang=" + tc.langCode
}
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
// Perform request
s.router.ServeHTTP(w, req)
// Assert response
assert.Equal(s.T(), tc.expectedStatus, w.Code)
if tc.expectedBody != nil {
var response ent.Category
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(s.T(), err)
assertCategoryEqual(s.T(), tc.expectedBody.(*ent.Category), &response)
}
})
}
}
// Test cases for AddCategoryContent
func (s *CategoryHandlerTestSuite) TestAddCategoryContent() {
var description = "Test Description"
testCases := []struct {
name string
categoryID string
requestBody interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
categoryID: "1",
requestBody: AddCategoryContentRequest{
LanguageCode: "en",
Name: "Test Category",
Description: &description,
Slug: "test-category",
},
setupMock: func() {
s.service.EXPECT().
AddCategoryContent(
gomock.Any(),
1,
"en",
"Test Category",
description,
"test-category",
).
Return(&ent.CategoryContent{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: description,
Slug: "test-category",
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: &ent.CategoryContent{
LanguageCode: categorycontent.LanguageCode("en"),
Name: "Test Category",
Description: description,
Slug: "test-category",
},
},
{
name: "Invalid JSON",
categoryID: "1",
requestBody: "invalid json",
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
},
{
name: "Invalid Category ID",
categoryID: "invalid",
requestBody: AddCategoryContentRequest{
LanguageCode: "en",
Name: "Test Category",
Description: &description,
Slug: "test-category",
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
},
{
name: "Service Error",
categoryID: "1",
requestBody: AddCategoryContentRequest{
LanguageCode: "en",
Name: "Test Category",
Description: &description,
Slug: "test-category",
},
setupMock: func() {
s.service.EXPECT().
AddCategoryContent(
gomock.Any(),
1,
"en",
"Test Category",
description,
"test-category",
).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// Setup mock
tc.setupMock()
// Create request
var body []byte
var err error
if str, ok := tc.requestBody.(string); ok {
body = []byte(str)
} else {
body, err = json.Marshal(tc.requestBody)
s.NoError(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/categories/"+tc.categoryID+"/contents", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
// Perform request
s.router.ServeHTTP(w, req)
// Assert response
s.Equal(tc.expectedStatus, w.Code)
if tc.expectedBody != nil {
var response ent.CategoryContent
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.Equal(tc.expectedBody, &response)
}
})
}
}
// Test cases for CreateCategory
func (s *CategoryHandlerTestSuite) TestCreateCategory() {
testCases := []struct {
name string
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功创建分类",
setupMock: func() {
category := &ent.Category{
ID: 1,
}
s.service.EXPECT().
CreateCategory(gomock.Any()).
Return(category, nil)
},
expectedStatus: http.StatusCreated,
},
{
name: "创建分类失败",
setupMock: func() {
s.service.EXPECT().
CreateCategory(gomock.Any()).
Return(nil, errors.New("failed to create category"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to create category",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置 mock
tc.setupMock()
// 创建请求
req, _ := http.NewRequest(http.MethodPost, "/categories", nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// 执行请求
s.handler.CreateCategory(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.Equal(tc.expectedError, response["error"])
} else {
var response *ent.Category
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.NotNil(response)
s.Equal(1, response.ID)
}
})
}
}

View file

@ -1,456 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"tss-rocks-be/ent"
"tss-rocks-be/internal/config"
"tss-rocks-be/internal/service"
"tss-rocks-be/internal/service/mock"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"errors"
)
type ContributorHandlerTestSuite struct {
suite.Suite
ctrl *gomock.Controller
service *mock.MockService
handler *Handler
router *gin.Engine
}
func (s *ContributorHandlerTestSuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.service = mock.NewMockService(s.ctrl)
cfg := &config.Config{
JWT: config.JWTConfig{
Secret: "test-secret",
},
}
s.handler = NewHandler(cfg, s.service)
// Setup Gin router
gin.SetMode(gin.TestMode)
s.router = gin.New()
// Setup mock for GetTokenBlacklist
tokenBlacklist := &service.TokenBlacklist{}
s.service.EXPECT().
GetTokenBlacklist().
Return(tokenBlacklist).
AnyTimes()
s.handler.RegisterRoutes(s.router)
}
func (s *ContributorHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestContributorHandlerSuite(t *testing.T) {
suite.Run(t, new(ContributorHandlerTestSuite))
}
func (s *ContributorHandlerTestSuite) TestListContributors() {
testCases := []struct {
name string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
setupMock: func() {
s.service.EXPECT().
ListContributors(gomock.Any()).
Return([]*ent.Contributor{
{
ID: 1,
Name: "John Doe",
Edges: ent.ContributorEdges{
SocialLinks: []*ent.ContributorSocialLink{
{
Type: "github",
Value: "https://github.com/johndoe",
Edges: ent.ContributorSocialLinkEdges{},
},
},
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
{
ID: 2,
Name: "Jane Smith",
Edges: ent.ContributorEdges{
SocialLinks: []*ent.ContributorSocialLink{}, // Ensure empty SocialLinks array is present
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []gin.H{
{
"id": 1,
"name": "John Doe",
"created_at": time.Time{},
"updated_at": time.Time{},
"edges": gin.H{
"social_links": []gin.H{
{
"type": "github",
"value": "https://github.com/johndoe",
"edges": gin.H{},
},
},
},
},
{
"id": 2,
"name": "Jane Smith",
"created_at": time.Time{},
"updated_at": time.Time{},
"edges": gin.H{
"social_links": []gin.H{}, // Ensure empty SocialLinks array is present
},
},
},
},
{
name: "Service error",
setupMock: func() {
s.service.EXPECT().
ListContributors(gomock.Any()).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to list contributors"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
req := httptest.NewRequest(http.MethodGet, "/api/v1/contributors", nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *ContributorHandlerTestSuite) TestGetContributor() {
testCases := []struct {
name string
id string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
id: "1",
setupMock: func() {
s.service.EXPECT().
GetContributorByID(gomock.Any(), 1).
Return(&ent.Contributor{
ID: 1,
Name: "John Doe",
Edges: ent.ContributorEdges{
SocialLinks: []*ent.ContributorSocialLink{
{
Type: "github",
Value: "https://github.com/johndoe",
Edges: ent.ContributorSocialLinkEdges{},
},
},
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: gin.H{
"id": 1,
"name": "John Doe",
"created_at": time.Time{},
"updated_at": time.Time{},
"edges": gin.H{
"social_links": []gin.H{
{
"type": "github",
"value": "https://github.com/johndoe",
"edges": gin.H{},
},
},
},
},
},
{
name: "Invalid ID",
id: "invalid",
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Invalid contributor ID"},
},
{
name: "Service error",
id: "1",
setupMock: func() {
s.service.EXPECT().
GetContributorByID(gomock.Any(), 1).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to get contributor"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
req := httptest.NewRequest(http.MethodGet, "/api/v1/contributors/"+tc.id, nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *ContributorHandlerTestSuite) TestCreateContributor() {
testCases := []struct {
name string
body interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
body: CreateContributorRequest{
Name: "John Doe",
},
setupMock: func() {
name := "John Doe"
s.service.EXPECT().
CreateContributor(
gomock.Any(),
name,
nil,
nil,
).
Return(&ent.Contributor{
ID: 1,
Name: name,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: gin.H{
"id": 1,
"name": "John Doe",
"created_at": time.Time{},
"updated_at": time.Time{},
"edges": gin.H{},
},
},
{
name: "Invalid request body",
body: map[string]interface{}{
"name": "", // Empty name is not allowed
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Key: 'CreateContributorRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag"},
},
{
name: "Service error",
body: CreateContributorRequest{
Name: "John Doe",
},
setupMock: func() {
name := "John Doe"
s.service.EXPECT().
CreateContributor(
gomock.Any(),
name,
nil,
nil,
).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to create contributor"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
body, err := json.Marshal(tc.body)
s.NoError(err, "Failed to marshal request body")
req := httptest.NewRequest(http.MethodPost, "/api/v1/contributors", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *ContributorHandlerTestSuite) TestAddContributorSocialLink() {
testCases := []struct {
name string
id string
body interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
id: "1",
body: func() AddContributorSocialLinkRequest {
name := "johndoe"
return AddContributorSocialLinkRequest{
Type: "github",
Name: &name,
Value: "https://github.com/johndoe",
}
}(),
setupMock: func() {
name := "johndoe"
s.service.EXPECT().
AddContributorSocialLink(
gomock.Any(),
1,
"github",
name,
"https://github.com/johndoe",
).
Return(&ent.ContributorSocialLink{
Type: "github",
Name: name,
Value: "https://github.com/johndoe",
Edges: ent.ContributorSocialLinkEdges{},
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: gin.H{
"type": "github",
"name": "johndoe",
"value": "https://github.com/johndoe",
"edges": gin.H{},
},
},
{
name: "Invalid contributor ID",
id: "invalid",
body: func() AddContributorSocialLinkRequest {
name := "johndoe"
return AddContributorSocialLinkRequest{
Type: "github",
Name: &name,
Value: "https://github.com/johndoe",
}
}(),
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Invalid contributor ID"},
},
{
name: "Invalid request body",
id: "1",
body: map[string]interface{}{
"type": "", // Empty type is not allowed
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Key: 'AddContributorSocialLinkRequest.Type' Error:Field validation for 'Type' failed on the 'required' tag\nKey: 'AddContributorSocialLinkRequest.Value' Error:Field validation for 'Value' failed on the 'required' tag"},
},
{
name: "Service error",
id: "1",
body: func() AddContributorSocialLinkRequest {
name := "johndoe"
return AddContributorSocialLinkRequest{
Type: "github",
Name: &name,
Value: "https://github.com/johndoe",
}
}(),
setupMock: func() {
name := "johndoe"
s.service.EXPECT().
AddContributorSocialLink(
gomock.Any(),
1,
"github",
name,
"https://github.com/johndoe",
).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to add contributor social link"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
body, err := json.Marshal(tc.body)
s.NoError(err, "Failed to marshal request body")
req := httptest.NewRequest(http.MethodPost, "/api/v1/contributors/"+tc.id+"/social-links", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}

View file

@ -1,532 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"tss-rocks-be/ent"
"tss-rocks-be/internal/config"
"tss-rocks-be/internal/service"
"tss-rocks-be/internal/service/mock"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"errors"
"strings"
)
type DailyHandlerTestSuite struct {
suite.Suite
ctrl *gomock.Controller
service *mock.MockService
handler *Handler
router *gin.Engine
}
func (s *DailyHandlerTestSuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.service = mock.NewMockService(s.ctrl)
cfg := &config.Config{
JWT: config.JWTConfig{
Secret: "test-secret",
},
}
s.handler = NewHandler(cfg, s.service)
// Setup Gin router
gin.SetMode(gin.TestMode)
s.router = gin.New()
// Setup mock for GetTokenBlacklist
tokenBlacklist := &service.TokenBlacklist{}
s.service.EXPECT().
GetTokenBlacklist().
Return(tokenBlacklist).
AnyTimes()
s.handler.RegisterRoutes(s.router)
}
func (s *DailyHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestDailyHandlerSuite(t *testing.T) {
suite.Run(t, new(DailyHandlerTestSuite))
}
func (s *DailyHandlerTestSuite) TestListDailies() {
testCases := []struct {
name string
langCode string
categoryID string
limit string
offset string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success with default language",
langCode: "",
setupMock: func() {
s.service.EXPECT().
ListDailies(gomock.Any(), "en", nil, 10, 0).
Return([]*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
},
},
},
{
name: "Success with specific language",
langCode: "zh",
setupMock: func() {
s.service.EXPECT().
ListDailies(gomock.Any(), "zh", nil, 10, 0).
Return([]*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "zh",
Quote: "测试语录1",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "zh",
Quote: "测试语录1",
},
},
},
},
},
},
{
name: "Success with category filter",
categoryID: "1",
setupMock: func() {
categoryID := 1
s.service.EXPECT().
ListDailies(gomock.Any(), "en", &categoryID, 10, 0).
Return([]*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Daily{
{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
},
},
},
{
name: "Success with pagination",
limit: "2",
offset: "1",
setupMock: func() {
s.service.EXPECT().
ListDailies(gomock.Any(), "en", nil, 2, 1).
Return([]*ent.Daily{
{
ID: "daily2",
ImageURL: "https://example.com/image2.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 2",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Daily{
{
ID: "daily2",
ImageURL: "https://example.com/image2.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 2",
},
},
},
},
},
},
{
name: "Service Error",
setupMock: func() {
s.service.EXPECT().
ListDailies(gomock.Any(), "en", nil, 10, 0).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to list dailies"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
url := "/api/v1/dailies"
if tc.langCode != "" {
url += "?lang=" + tc.langCode
}
if tc.categoryID != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "category_id=" + tc.categoryID
}
if tc.limit != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "limit=" + tc.limit
}
if tc.offset != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "offset=" + tc.offset
}
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *DailyHandlerTestSuite) TestGetDaily() {
testCases := []struct {
name string
id string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
id: "daily1",
setupMock: func() {
s.service.EXPECT().
GetDailyByID(gomock.Any(), "daily1").
Return(&ent.Daily{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: &ent.Daily{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{
{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
},
},
},
{
name: "Service error",
id: "daily1",
setupMock: func() {
s.service.EXPECT().
GetDailyByID(gomock.Any(), "daily1").
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to get daily"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
req := httptest.NewRequest(http.MethodGet, "/api/v1/dailies/"+tc.id, nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *DailyHandlerTestSuite) TestCreateDaily() {
testCases := []struct {
name string
body interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
body: CreateDailyRequest{
ID: "daily1",
CategoryID: 1,
ImageURL: "https://example.com/image1.jpg",
},
setupMock: func() {
s.service.EXPECT().
CreateDaily(gomock.Any(), "daily1", 1, "https://example.com/image1.jpg").
Return(&ent.Daily{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{},
},
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: &ent.Daily{
ID: "daily1",
ImageURL: "https://example.com/image1.jpg",
Edges: ent.DailyEdges{
Category: &ent.Category{ID: 1},
Contents: []*ent.DailyContent{},
},
},
},
{
name: "Invalid request body",
body: map[string]interface{}{
"id": "daily1",
// Missing required fields
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Key: 'CreateDailyRequest.CategoryID' Error:Field validation for 'CategoryID' failed on the 'required' tag\nKey: 'CreateDailyRequest.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required' tag"},
},
{
name: "Service error",
body: CreateDailyRequest{
ID: "daily1",
CategoryID: 1,
ImageURL: "https://example.com/image1.jpg",
},
setupMock: func() {
s.service.EXPECT().
CreateDaily(gomock.Any(), "daily1", 1, "https://example.com/image1.jpg").
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to create daily"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
body, err := json.Marshal(tc.body)
s.NoError(err, "Failed to marshal request body")
req := httptest.NewRequest(http.MethodPost, "/api/v1/dailies", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
func (s *DailyHandlerTestSuite) TestAddDailyContent() {
testCases := []struct {
name string
dailyID string
body interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
dailyID: "daily1",
body: AddDailyContentRequest{
LanguageCode: "en",
Quote: "Test Quote 1",
},
setupMock: func() {
s.service.EXPECT().
AddDailyContent(gomock.Any(), "daily1", "en", "Test Quote 1").
Return(&ent.DailyContent{
LanguageCode: "en",
Quote: "Test Quote 1",
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: &ent.DailyContent{
LanguageCode: "en",
Quote: "Test Quote 1",
},
},
{
name: "Invalid request body",
dailyID: "daily1",
body: map[string]interface{}{
"language_code": "en",
// Missing required fields
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Key: 'AddDailyContentRequest.Quote' Error:Field validation for 'Quote' failed on the 'required' tag"},
},
{
name: "Service error",
dailyID: "daily1",
body: AddDailyContentRequest{
LanguageCode: "en",
Quote: "Test Quote 1",
},
setupMock: func() {
s.service.EXPECT().
AddDailyContent(gomock.Any(), "daily1", "en", "Test Quote 1").
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to add daily content"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
body, err := json.Marshal(tc.body)
s.NoError(err, "Failed to marshal request body")
req := httptest.NewRequest(http.MethodPost, "/api/v1/dailies/"+tc.dailyID+"/contents", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}

View file

@ -1,43 +0,0 @@
package handler
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestStringPtr(t *testing.T) {
testCases := []struct {
name string
input *string
expected string
}{
{
name: "nil pointer",
input: nil,
expected: "",
},
{
name: "empty string",
input: strPtr(""),
expected: "",
},
{
name: "non-empty string",
input: strPtr("test"),
expected: "test",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := stringPtr(tc.input)
assert.Equal(t, tc.expected, result)
})
}
}
// Helper function to create string pointer
func strPtr(s string) *string {
return &s
}

View file

@ -1,524 +0,0 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"strings"
"testing"
"tss-rocks-be/ent"
"tss-rocks-be/internal/config"
"tss-rocks-be/internal/service/mock"
"tss-rocks-be/internal/storage"
"net/textproto"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
)
type MediaHandlerTestSuite struct {
suite.Suite
ctrl *gomock.Controller
service *mock.MockService
handler *Handler
router *gin.Engine
}
func (s *MediaHandlerTestSuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.service = mock.NewMockService(s.ctrl)
s.handler = NewHandler(&config.Config{}, s.service)
s.router = gin.New()
}
func (s *MediaHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestMediaHandlerSuite(t *testing.T) {
suite.Run(t, new(MediaHandlerTestSuite))
}
func (s *MediaHandlerTestSuite) TestListMedia() {
testCases := []struct {
name string
query string
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功列出媒体",
query: "?limit=10&offset=0",
setupMock: func() {
s.service.EXPECT().
ListMedia(gomock.Any(), 10, 0).
Return([]*ent.Media{{ID: 1}}, nil)
},
expectedStatus: http.StatusOK,
},
{
name: "使用默认限制和偏移",
query: "",
setupMock: func() {
s.service.EXPECT().
ListMedia(gomock.Any(), 10, 0).
Return([]*ent.Media{{ID: 1}}, nil)
},
expectedStatus: http.StatusOK,
},
{
name: "列出媒体失败",
query: "",
setupMock: func() {
s.service.EXPECT().
ListMedia(gomock.Any(), 10, 0).
Return(nil, errors.New("failed to list media"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to list media",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置 mock
tc.setupMock()
// 创建请求
req, _ := http.NewRequest(http.MethodGet, "/media"+tc.query, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// 执行请求
s.handler.ListMedia(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.Equal(tc.expectedError, response["error"])
} else {
var response []*ent.Media
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.NotEmpty(response)
}
})
}
}
func (s *MediaHandlerTestSuite) TestUploadMedia() {
testCases := []struct {
name string
setupRequest func() (*http.Request, error)
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功上传媒体",
setupRequest: func() (*http.Request, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 创建文件部分
fileHeader := make(textproto.MIMEHeader)
fileHeader.Set("Content-Type", "image/jpeg")
fileHeader.Set("Content-Disposition", `form-data; name="file"; filename="test.jpg"`)
part, err := writer.CreatePart(fileHeader)
if err != nil {
return nil, err
}
testContent := "test content"
_, err = io.Copy(part, strings.NewReader(testContent))
if err != nil {
return nil, err
}
writer.Close()
req := httptest.NewRequest(http.MethodPost, "/media", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, nil
},
setupMock: func() {
expectedFile := &multipart.FileHeader{
Filename: "test.jpg",
Size: int64(len("test content")),
Header: textproto.MIMEHeader{
"Content-Type": []string{"image/jpeg"},
},
}
s.service.EXPECT().
Upload(gomock.Any(), gomock.Any(), 1).
DoAndReturn(func(_ context.Context, f *multipart.FileHeader, uid int) (*ent.Media, error) {
s.Equal(expectedFile.Filename, f.Filename)
s.Equal(expectedFile.Size, f.Size)
s.Equal(expectedFile.Header.Get("Content-Type"), f.Header.Get("Content-Type"))
return &ent.Media{ID: 1}, nil
})
},
expectedStatus: http.StatusCreated,
},
{
name: "未授权",
setupRequest: func() (*http.Request, error) {
req := httptest.NewRequest(http.MethodPost, "/media", nil)
return req, nil
},
setupMock: func() {},
expectedStatus: http.StatusUnauthorized,
expectedError: "Unauthorized",
},
{
name: "上传失败",
setupRequest: func() (*http.Request, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 创建文件部分
fileHeader := make(textproto.MIMEHeader)
fileHeader.Set("Content-Type", "image/jpeg")
fileHeader.Set("Content-Disposition", `form-data; name="file"; filename="test.jpg"`)
part, err := writer.CreatePart(fileHeader)
if err != nil {
return nil, err
}
testContent := "test content"
_, err = io.Copy(part, strings.NewReader(testContent))
if err != nil {
return nil, err
}
writer.Close()
req := httptest.NewRequest(http.MethodPost, "/media", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, nil
},
setupMock: func() {
s.service.EXPECT().
Upload(gomock.Any(), gomock.Any(), 1).
Return(nil, errors.New("failed to upload"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to upload media",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置 mock
tc.setupMock()
// 创建请求
req, err := tc.setupRequest()
s.NoError(err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// 设置用户ID除了未授权的测试用例
if tc.expectedError != "Unauthorized" {
c.Set("user_id", 1)
}
// 执行请求
s.handler.UploadMedia(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.Equal(tc.expectedError, response["error"])
} else {
var response *ent.Media
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.NotNil(response)
}
})
}
}
func (s *MediaHandlerTestSuite) TestGetMedia() {
testCases := []struct {
name string
mediaID string
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功获取媒体",
mediaID: "1",
setupMock: func() {
media := &ent.Media{
ID: 1,
MimeType: "image/jpeg",
OriginalName: "test.jpg",
}
s.service.EXPECT().
GetMedia(gomock.Any(), 1).
Return(media, nil)
s.service.EXPECT().
GetFile(gomock.Any(), 1).
Return(io.NopCloser(strings.NewReader("test content")), &storage.FileInfo{
Size: 11,
Name: "test.jpg",
ContentType: "image/jpeg",
}, nil)
},
expectedStatus: http.StatusOK,
},
{
name: "无效的媒体ID",
mediaID: "invalid",
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedError: "Invalid media ID",
},
{
name: "获取媒体元数据失败",
mediaID: "1",
setupMock: func() {
s.service.EXPECT().
GetMedia(gomock.Any(), 1).
Return(nil, errors.New("failed to get media"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to get media",
},
{
name: "获取媒体文件失败",
mediaID: "1",
setupMock: func() {
media := &ent.Media{
ID: 1,
MimeType: "image/jpeg",
OriginalName: "test.jpg",
}
s.service.EXPECT().
GetMedia(gomock.Any(), 1).
Return(media, nil)
s.service.EXPECT().
GetFile(gomock.Any(), 1).
Return(nil, nil, errors.New("failed to get file"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to get media file",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置 mock
tc.setupMock()
// 创建请求
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/media/%s", tc.mediaID), nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Extract ID from URL path
parts := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(parts) >= 2 {
c.Params = []gin.Param{{Key: "id", Value: parts[1]}}
}
// 执行请求
s.handler.GetMedia(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.Equal(tc.expectedError, response["error"])
} else {
s.Equal("image/jpeg", w.Header().Get("Content-Type"))
s.Equal("11", w.Header().Get("Content-Length"))
s.Equal("inline; filename=test.jpg", w.Header().Get("Content-Disposition"))
s.Equal("test content", w.Body.String())
}
})
}
}
func (s *MediaHandlerTestSuite) TestGetMediaFile() {
testCases := []struct {
name string
setupRequest func() (*http.Request, error)
setupMock func()
expectedStatus int
expectedBody []byte
}{
{
name: "成功获取媒体文件",
setupRequest: func() (*http.Request, error) {
return httptest.NewRequest(http.MethodGet, "/media/1/file", nil), nil
},
setupMock: func() {
fileContent := "test file content"
s.service.EXPECT().
GetFile(gomock.Any(), 1).
Return(io.NopCloser(strings.NewReader(fileContent)), &storage.FileInfo{
Name: "test.jpg",
Size: int64(len(fileContent)),
ContentType: "image/jpeg",
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []byte("test file content"),
},
{
name: "无效的媒体ID",
setupRequest: func() (*http.Request, error) {
return httptest.NewRequest(http.MethodGet, "/media/invalid/file", nil), nil
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
},
{
name: "获取媒体文件失败",
setupRequest: func() (*http.Request, error) {
return httptest.NewRequest(http.MethodGet, "/media/1/file", nil), nil
},
setupMock: func() {
s.service.EXPECT().
GetFile(gomock.Any(), 1).
Return(nil, nil, errors.New("failed to get file"))
},
expectedStatus: http.StatusInternalServerError,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// Setup
req, err := tc.setupRequest()
s.Require().NoError(err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Extract ID from URL path
parts := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(parts) >= 2 {
c.Params = []gin.Param{{Key: "id", Value: parts[1]}}
}
// Setup mock
tc.setupMock()
// Test
s.handler.GetMediaFile(c)
// Verify
s.Equal(tc.expectedStatus, w.Code)
if tc.expectedBody != nil {
s.Equal(tc.expectedBody, w.Body.Bytes())
s.Equal("image/jpeg", w.Header().Get("Content-Type"))
s.Equal(fmt.Sprintf("%d", len(tc.expectedBody)), w.Header().Get("Content-Length"))
s.Equal("inline; filename=test.jpg", w.Header().Get("Content-Disposition"))
}
})
}
}
func (s *MediaHandlerTestSuite) TestDeleteMedia() {
testCases := []struct {
name string
mediaID string
setupMock func()
expectedStatus int
expectedError string
}{
{
name: "成功删除媒体",
mediaID: "1",
setupMock: func() {
s.service.EXPECT().
DeleteMedia(gomock.Any(), 1, 1).
Return(nil)
},
expectedStatus: http.StatusNoContent,
},
{
name: "未授权",
mediaID: "1",
setupMock: func() {},
expectedStatus: http.StatusUnauthorized,
expectedError: "Unauthorized",
},
{
name: "无效的媒体ID",
mediaID: "invalid",
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedError: "Invalid media ID",
},
{
name: "删除媒体失败",
mediaID: "1",
setupMock: func() {
s.service.EXPECT().
DeleteMedia(gomock.Any(), 1, 1).
Return(errors.New("failed to delete"))
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to delete media",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// 设置 mock
tc.setupMock()
// 创建请求
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/media/%s", tc.mediaID), nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = req
// Extract ID from URL path
parts := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(parts) >= 2 {
c.Params = []gin.Param{{Key: "id", Value: parts[1]}}
}
// 设置用户ID除了未授权的测试用例
if tc.expectedError != "Unauthorized" {
c.Set("user_id", 1)
}
// 执行请求
s.handler.DeleteMedia(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.Equal(tc.expectedError, response["error"])
}
})
}
}

View file

@ -1,624 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"tss-rocks-be/ent"
"tss-rocks-be/internal/config"
"tss-rocks-be/internal/service"
"tss-rocks-be/internal/service/mock"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"errors"
"strings"
)
type PostHandlerTestSuite struct {
suite.Suite
ctrl *gomock.Controller
service *mock.MockService
handler *Handler
router *gin.Engine
}
func (s *PostHandlerTestSuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.service = mock.NewMockService(s.ctrl)
cfg := &config.Config{
JWT: config.JWTConfig{
Secret: "test-secret",
},
}
s.handler = NewHandler(cfg, s.service)
// Setup Gin router
gin.SetMode(gin.TestMode)
s.router = gin.New()
// Setup mock for GetTokenBlacklist
tokenBlacklist := &service.TokenBlacklist{}
s.service.EXPECT().
GetTokenBlacklist().
Return(tokenBlacklist).
AnyTimes()
s.handler.RegisterRoutes(s.router)
}
func (s *PostHandlerTestSuite) TearDownTest() {
s.ctrl.Finish()
}
func TestPostHandlerSuite(t *testing.T) {
suite.Run(t, new(PostHandlerTestSuite))
}
// Test cases for ListPosts
func (s *PostHandlerTestSuite) TestListPosts() {
categoryID := 1
testCases := []struct {
name string
langCode string
categoryID string
limit string
offset string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success with default language",
langCode: "",
setupMock: func() {
s.service.EXPECT().
ListPosts(gomock.Any(), "en", nil, 10, 0).
Return([]*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
},
},
},
},
},
{
name: "Success with specific language",
langCode: "zh",
setupMock: func() {
s.service.EXPECT().
ListPosts(gomock.Any(), "zh", nil, 10, 0).
Return([]*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "zh",
Title: "测试帖子",
ContentMarkdown: "测试内容",
Summary: "测试摘要",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "zh",
Title: "测试帖子",
ContentMarkdown: "测试内容",
Summary: "测试摘要",
},
},
},
},
},
},
{
name: "Success with category filter",
langCode: "en",
categoryID: "1",
setupMock: func() {
s.service.EXPECT().
ListPosts(gomock.Any(), "en", &categoryID, 10, 0).
Return([]*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Post{
{
ID: 1,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
},
},
},
},
},
{
name: "Success with pagination",
langCode: "en",
limit: "2",
offset: "1",
setupMock: func() {
s.service.EXPECT().
ListPosts(gomock.Any(), "en", nil, 2, 1).
Return([]*ent.Post{
{
ID: 2,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post 2",
ContentMarkdown: "Test Content 2",
Summary: "Test Summary 2",
},
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: []*ent.Post{
{
ID: 2,
Status: "published",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post 2",
ContentMarkdown: "Test Content 2",
Summary: "Test Summary 2",
},
},
},
},
},
},
{
name: "Service Error",
langCode: "en",
setupMock: func() {
s.service.EXPECT().
ListPosts(gomock.Any(), "en", nil, 10, 0).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
// Setup mock
tc.setupMock()
// Create request
url := "/api/v1/posts"
if tc.langCode != "" {
url += "?lang=" + tc.langCode
}
if tc.categoryID != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "category_id=" + tc.categoryID
}
if tc.limit != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "limit=" + tc.limit
}
if tc.offset != "" {
if strings.Contains(url, "?") {
url += "&"
} else {
url += "?"
}
url += "offset=" + tc.offset
}
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
// Perform request
s.router.ServeHTTP(w, req)
// Assert response
s.Equal(tc.expectedStatus, w.Code)
if tc.expectedBody != nil {
var response []*ent.Post
err := json.Unmarshal(w.Body.Bytes(), &response)
s.NoError(err)
s.Equal(tc.expectedBody, response)
}
})
}
}
// Test cases for GetPost
func (s *PostHandlerTestSuite) TestGetPost() {
testCases := []struct {
name string
langCode string
slug string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success with default language",
langCode: "",
slug: "test-post",
setupMock: func() {
s.service.EXPECT().
GetPostBySlug(gomock.Any(), "en", "test-post").
Return(&ent.Post{
ID: 1,
Status: "published",
Slug: "test-post",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: gin.H{
"id": 1,
"status": "published",
"slug": "test-post",
"edges": gin.H{
"contents": []gin.H{
{
"language_code": "en",
"title": "Test Post",
"content_markdown": "Test Content",
"summary": "Test Summary",
},
},
},
},
},
{
name: "Success with specific language",
langCode: "zh",
slug: "test-post",
setupMock: func() {
s.service.EXPECT().
GetPostBySlug(gomock.Any(), "zh", "test-post").
Return(&ent.Post{
ID: 1,
Status: "published",
Slug: "test-post",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{
{
LanguageCode: "zh",
Title: "测试帖子",
ContentMarkdown: "测试内容",
Summary: "测试摘要",
},
},
},
}, nil)
},
expectedStatus: http.StatusOK,
expectedBody: gin.H{
"id": 1,
"status": "published",
"slug": "test-post",
"edges": gin.H{
"contents": []gin.H{
{
"language_code": "zh",
"title": "测试帖子",
"content_markdown": "测试内容",
"summary": "测试摘要",
},
},
},
},
},
{
name: "Service error",
slug: "test-post",
setupMock: func() {
s.service.EXPECT().
GetPostBySlug(gomock.Any(), "en", "test-post").
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to get post"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
url := "/api/v1/posts/" + tc.slug
if tc.langCode != "" {
url += "?lang=" + tc.langCode
}
req := httptest.NewRequest(http.MethodGet, url, nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
// Test cases for CreatePost
func (s *PostHandlerTestSuite) TestCreatePost() {
testCases := []struct {
name string
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
setupMock: func() {
s.service.EXPECT().
CreatePost(gomock.Any(), "draft").
Return(&ent.Post{
ID: 1,
Status: "draft",
Edges: ent.PostEdges{
Contents: []*ent.PostContent{},
},
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: gin.H{
"id": 1,
"status": "draft",
"edges": gin.H{
"contents": []gin.H{},
},
},
},
{
name: "Service error",
setupMock: func() {
s.service.EXPECT().
CreatePost(gomock.Any(), "draft").
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to create post"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
req := httptest.NewRequest(http.MethodPost, "/api/v1/posts", nil)
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}
// Test cases for AddPostContent
func (s *PostHandlerTestSuite) TestAddPostContent() {
testCases := []struct {
name string
postID string
body interface{}
setupMock func()
expectedStatus int
expectedBody interface{}
}{
{
name: "Success",
postID: "1",
body: AddPostContentRequest{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
MetaKeywords: "test,keywords",
MetaDescription: "Test meta description",
},
setupMock: func() {
s.service.EXPECT().
AddPostContent(
gomock.Any(),
1,
"en",
"Test Post",
"Test Content",
"Test Summary",
"test,keywords",
"Test meta description",
).
Return(&ent.PostContent{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
MetaKeywords: "test,keywords",
MetaDescription: "Test meta description",
Edges: ent.PostContentEdges{},
}, nil)
},
expectedStatus: http.StatusCreated,
expectedBody: gin.H{
"language_code": "en",
"title": "Test Post",
"content_markdown": "Test Content",
"summary": "Test Summary",
"meta_keywords": "test,keywords",
"meta_description": "Test meta description",
"edges": gin.H{},
},
},
{
name: "Invalid post ID",
postID: "invalid",
body: AddPostContentRequest{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Invalid post ID"},
},
{
name: "Invalid request body",
postID: "1",
body: map[string]interface{}{
"language_code": "en",
// Missing required fields
},
setupMock: func() {},
expectedStatus: http.StatusBadRequest,
expectedBody: gin.H{"error": "Key: 'AddPostContentRequest.Title' Error:Field validation for 'Title' failed on the 'required' tag\nKey: 'AddPostContentRequest.ContentMarkdown' Error:Field validation for 'ContentMarkdown' failed on the 'required' tag\nKey: 'AddPostContentRequest.Summary' Error:Field validation for 'Summary' failed on the 'required' tag"},
},
{
name: "Service error",
postID: "1",
body: AddPostContentRequest{
LanguageCode: "en",
Title: "Test Post",
ContentMarkdown: "Test Content",
Summary: "Test Summary",
MetaKeywords: "test,keywords",
MetaDescription: "Test meta description",
},
setupMock: func() {
s.service.EXPECT().
AddPostContent(
gomock.Any(),
1,
"en",
"Test Post",
"Test Content",
"Test Summary",
"test,keywords",
"Test meta description",
).
Return(nil, errors.New("service error"))
},
expectedStatus: http.StatusInternalServerError,
expectedBody: gin.H{"error": "Failed to add post content"},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.setupMock()
body, err := json.Marshal(tc.body)
s.NoError(err, "Failed to marshal request body")
req := httptest.NewRequest(http.MethodPost, "/api/v1/posts/"+tc.postID+"/contents", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Equal(tc.expectedStatus, w.Code, "HTTP status code mismatch")
if tc.expectedBody != nil {
expectedJSON, err := json.Marshal(tc.expectedBody)
s.NoError(err, "Failed to marshal expected body")
s.JSONEq(string(expectedJSON), w.Body.String(), "Response body mismatch")
}
})
}
}