refactor: Implement dependency injection for handlers and services
- Refactored AuthHandler, UserHandler, TextureHandler, ProfileHandler, CaptchaHandler, and YggdrasilHandler to use dependency injection. - Removed direct instantiation of services and repositories within handlers, replacing them with constructor injection. - Updated the container to initialize service instances and provide them to handlers. - Enhanced code structure for better testability and adherence to Go best practices.
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"carrotskin/internal/model"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TestTextureService_TypeValidation 测试材质类型验证
|
||||
@@ -469,3 +472,357 @@ func TestCheckTextureUploadLimit_Logic(t *testing.T) {
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 使用 Mock 的集成测试
|
||||
// ============================================================================
|
||||
|
||||
// TestTextureServiceImpl_Create 测试创建Texture
|
||||
func TestTextureServiceImpl_Create(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置用户
|
||||
testUser := &model.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
Status: 1,
|
||||
}
|
||||
userRepo.Create(testUser)
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
uploaderID int64
|
||||
textureName string
|
||||
textureType string
|
||||
hash string
|
||||
wantErr bool
|
||||
errContains string
|
||||
setupMocks func()
|
||||
}{
|
||||
{
|
||||
name: "正常创建SKIN材质",
|
||||
uploaderID: 1,
|
||||
textureName: "TestSkin",
|
||||
textureType: "SKIN",
|
||||
hash: "unique-hash-1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "正常创建CAPE材质",
|
||||
uploaderID: 1,
|
||||
textureName: "TestCape",
|
||||
textureType: "CAPE",
|
||||
hash: "unique-hash-2",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "用户不存在",
|
||||
uploaderID: 999,
|
||||
textureName: "TestTexture",
|
||||
textureType: "SKIN",
|
||||
hash: "unique-hash-3",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "材质Hash已存在",
|
||||
uploaderID: 1,
|
||||
textureName: "DuplicateTexture",
|
||||
textureType: "SKIN",
|
||||
hash: "existing-hash",
|
||||
wantErr: true,
|
||||
errContains: "已存在",
|
||||
setupMocks: func() {
|
||||
textureRepo.Create(&model.Texture{
|
||||
ID: 100,
|
||||
UploaderID: 1,
|
||||
Name: "ExistingTexture",
|
||||
Hash: "existing-hash",
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "无效的材质类型",
|
||||
uploaderID: 1,
|
||||
textureName: "InvalidTypeTexture",
|
||||
textureType: "INVALID",
|
||||
hash: "unique-hash-4",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.setupMocks != nil {
|
||||
tt.setupMocks()
|
||||
}
|
||||
|
||||
texture, err := textureService.Create(
|
||||
tt.uploaderID,
|
||||
tt.textureName,
|
||||
"Test description",
|
||||
tt.textureType,
|
||||
"http://example.com/texture.png",
|
||||
tt.hash,
|
||||
1024,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
return
|
||||
}
|
||||
if tt.errContains != "" && !containsString(err.Error(), tt.errContains) {
|
||||
t.Errorf("错误信息应包含 %q, 实际为: %v", tt.errContains, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
if texture == nil {
|
||||
t.Error("返回的Texture不应为nil")
|
||||
}
|
||||
if texture.Name != tt.textureName {
|
||||
t.Errorf("Texture名称不匹配: got %v, want %v", texture.Name, tt.textureName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextureServiceImpl_GetByID 测试获取Texture
|
||||
func TestTextureServiceImpl_GetByID(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Texture
|
||||
testTexture := &model.Texture{
|
||||
ID: 1,
|
||||
UploaderID: 1,
|
||||
Name: "TestTexture",
|
||||
Hash: "test-hash",
|
||||
}
|
||||
textureRepo.Create(testTexture)
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "获取存在的Texture",
|
||||
id: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "获取不存在的Texture",
|
||||
id: 999,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
texture, err := textureService.GetByID(tt.id)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
if texture == nil {
|
||||
t.Error("返回的Texture不应为nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextureServiceImpl_GetByUserID_And_Search 测试 GetByUserID 与 Search 分页封装
|
||||
func TestTextureServiceImpl_GetByUserID_And_Search(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置多条 Texture
|
||||
for i := int64(1); i <= 5; i++ {
|
||||
textureRepo.Create(&model.Texture{
|
||||
ID: i,
|
||||
UploaderID: 1,
|
||||
Name: "T",
|
||||
IsPublic: i%2 == 0,
|
||||
})
|
||||
}
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
// GetByUserID 应按上传者过滤并调用 NormalizePagination
|
||||
textures, total, err := textureService.GetByUserID(1, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByUserID 失败: %v", err)
|
||||
}
|
||||
if total != int64(len(textures)) {
|
||||
t.Fatalf("GetByUserID 返回数量与总数不一致, total=%d, len=%d", total, len(textures))
|
||||
}
|
||||
|
||||
// Search 仅验证能够正常调用并返回结果
|
||||
searchResult, searchTotal, err := textureService.Search("", "", true, -1, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("Search 失败: %v", err)
|
||||
}
|
||||
if searchTotal != int64(len(searchResult)) {
|
||||
t.Fatalf("Search 返回数量与总数不一致, total=%d, len=%d", searchTotal, len(searchResult))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextureServiceImpl_Update_And_Delete 测试 Update / Delete 权限与字段更新
|
||||
func TestTextureServiceImpl_Update_And_Delete(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
texture := &model.Texture{
|
||||
ID: 1,
|
||||
UploaderID: 1,
|
||||
Name: "Old",
|
||||
Description:"OldDesc",
|
||||
IsPublic: false,
|
||||
}
|
||||
textureRepo.Create(texture)
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
// 更新成功
|
||||
newName := "NewName"
|
||||
newDesc := "NewDesc"
|
||||
public := boolPtr(true)
|
||||
updated, err := textureService.Update(1, 1, newName, newDesc, public)
|
||||
if err != nil {
|
||||
t.Fatalf("Update 正常情况失败: %v", err)
|
||||
}
|
||||
// 由于 MockTextureRepository.UpdateFields 不会真正修改结构体字段,这里只验证不会返回 nil 即可
|
||||
if updated == nil {
|
||||
t.Fatalf("Update 返回结果不应为 nil")
|
||||
}
|
||||
|
||||
// 无权限更新
|
||||
if _, err := textureService.Update(1, 2, "X", "Y", nil); err == nil {
|
||||
t.Fatalf("Update 在无权限时应返回错误")
|
||||
}
|
||||
|
||||
// 删除成功
|
||||
if err := textureService.Delete(1, 1); err != nil {
|
||||
t.Fatalf("Delete 正常情况失败: %v", err)
|
||||
}
|
||||
|
||||
// 无权限删除
|
||||
if err := textureService.Delete(1, 2); err == nil {
|
||||
t.Fatalf("Delete 在无权限时应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextureServiceImpl_FavoritesAndLimit 测试 GetUserFavorites 与 CheckUploadLimit
|
||||
func TestTextureServiceImpl_FavoritesAndLimit(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置若干 Texture 与收藏关系
|
||||
for i := int64(1); i <= 3; i++ {
|
||||
textureRepo.Create(&model.Texture{
|
||||
ID: i,
|
||||
UploaderID: 1,
|
||||
Name: "T",
|
||||
})
|
||||
_ = textureRepo.AddFavorite(1, i)
|
||||
}
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
// GetUserFavorites
|
||||
favs, total, err := textureService.GetUserFavorites(1, -1, -1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetUserFavorites 失败: %v", err)
|
||||
}
|
||||
if int64(len(favs)) != total || total != 3 {
|
||||
t.Fatalf("GetUserFavorites 数量不正确, total=%d, len=%d", total, len(favs))
|
||||
}
|
||||
|
||||
// CheckUploadLimit 未超过上限
|
||||
if err := textureService.CheckUploadLimit(1, 10); err != nil {
|
||||
t.Fatalf("CheckUploadLimit 在未达到上限时不应报错: %v", err)
|
||||
}
|
||||
|
||||
// CheckUploadLimit 超过上限
|
||||
if err := textureService.CheckUploadLimit(1, 2); err == nil {
|
||||
t.Fatalf("CheckUploadLimit 在超过上限时应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextureServiceImpl_ToggleFavorite 测试收藏功能
|
||||
func TestTextureServiceImpl_ToggleFavorite(t *testing.T) {
|
||||
textureRepo := NewMockTextureRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置用户和Texture
|
||||
testUser := &model.User{ID: 1, Username: "testuser", Status: 1}
|
||||
userRepo.Create(testUser)
|
||||
|
||||
testTexture := &model.Texture{
|
||||
ID: 1,
|
||||
UploaderID: 1,
|
||||
Name: "TestTexture",
|
||||
Hash: "test-hash",
|
||||
}
|
||||
textureRepo.Create(testTexture)
|
||||
|
||||
textureService := NewTextureService(textureRepo, userRepo, logger)
|
||||
|
||||
// 第一次收藏
|
||||
isFavorited, err := textureService.ToggleFavorite(1, 1)
|
||||
if err != nil {
|
||||
t.Errorf("第一次收藏失败: %v", err)
|
||||
}
|
||||
if !isFavorited {
|
||||
t.Error("第一次操作应该是添加收藏")
|
||||
}
|
||||
|
||||
// 第二次取消收藏
|
||||
isFavorited, err = textureService.ToggleFavorite(1, 1)
|
||||
if err != nil {
|
||||
t.Errorf("取消收藏失败: %v", err)
|
||||
}
|
||||
if isFavorited {
|
||||
t.Error("第二次操作应该是取消收藏")
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func containsString(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr ||
|
||||
(len(s) > len(substr) && (findSubstring(s, substr) != -1)))
|
||||
}
|
||||
|
||||
func findSubstring(s, substr string) int {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user