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,9 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"carrotskin/pkg/storage"
|
||||
)
|
||||
|
||||
// TestUploadService_FileTypes 测试文件类型常量
|
||||
@@ -135,43 +139,43 @@ func TestGetUploadConfig_TextureConfig(t *testing.T) {
|
||||
// TestValidateFileName 测试文件名验证
|
||||
func TestValidateFileName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fileName string
|
||||
fileType FileType
|
||||
wantErr bool
|
||||
name string
|
||||
fileName string
|
||||
fileType FileType
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "有效的头像文件名",
|
||||
fileName: "avatar.png",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: false,
|
||||
name: "有效的头像文件名",
|
||||
fileName: "avatar.png",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "有效的材质文件名",
|
||||
fileName: "texture.png",
|
||||
fileType: FileTypeTexture,
|
||||
wantErr: false,
|
||||
name: "有效的材质文件名",
|
||||
fileName: "texture.png",
|
||||
fileType: FileTypeTexture,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "文件名为空",
|
||||
fileName: "",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: true,
|
||||
name: "文件名为空",
|
||||
fileName: "",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: true,
|
||||
errContains: "文件名不能为空",
|
||||
},
|
||||
{
|
||||
name: "不支持的文件扩展名",
|
||||
fileName: "file.txt",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: true,
|
||||
name: "不支持的文件扩展名",
|
||||
fileName: "file.txt",
|
||||
fileType: FileTypeAvatar,
|
||||
wantErr: true,
|
||||
errContains: "不支持的文件格式",
|
||||
},
|
||||
{
|
||||
name: "无效的文件类型",
|
||||
fileName: "file.png",
|
||||
fileType: FileType("invalid"),
|
||||
wantErr: true,
|
||||
name: "无效的文件类型",
|
||||
fileName: "file.png",
|
||||
fileType: FileType("invalid"),
|
||||
wantErr: true,
|
||||
errContains: "不支持的文件类型",
|
||||
},
|
||||
}
|
||||
@@ -277,3 +281,130 @@ func TestUploadConfig_Structure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// mockStorageClient 用于单元测试的简单存储客户端假实现
|
||||
// 注意:这里只声明与 upload_service 使用到的方法,避免依赖真实 MinIO 客户端
|
||||
type mockStorageClient struct {
|
||||
getBucketFn func(name string) (string, error)
|
||||
generatePresignedPostURLFn func(ctx context.Context, bucketName, objectName string, minSize, maxSize int64, expires time.Duration) (*storage.PresignedPostPolicyResult, error)
|
||||
}
|
||||
|
||||
func (m *mockStorageClient) GetBucket(name string) (string, error) {
|
||||
if m.getBucketFn != nil {
|
||||
return m.getBucketFn(name)
|
||||
}
|
||||
return "", errors.New("GetBucket not implemented")
|
||||
}
|
||||
|
||||
func (m *mockStorageClient) GeneratePresignedPostURL(ctx context.Context, bucketName, objectName string, minSize, maxSize int64, expires time.Duration) (*storage.PresignedPostPolicyResult, error) {
|
||||
if m.generatePresignedPostURLFn != nil {
|
||||
return m.generatePresignedPostURLFn(ctx, bucketName, objectName, minSize, maxSize, expires)
|
||||
}
|
||||
return nil, errors.New("GeneratePresignedPostURL not implemented")
|
||||
}
|
||||
|
||||
// TestGenerateAvatarUploadURL_Success 测试头像上传URL生成成功
|
||||
func TestGenerateAvatarUploadURL_Success(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mockClient := &mockStorageClient{
|
||||
getBucketFn: func(name string) (string, error) {
|
||||
if name != "avatars" {
|
||||
t.Fatalf("unexpected bucket name: %s", name)
|
||||
}
|
||||
return "avatars-bucket", nil
|
||||
},
|
||||
generatePresignedPostURLFn: func(ctx context.Context, bucketName, objectName string, minSize, maxSize int64, expires time.Duration) (*storage.PresignedPostPolicyResult, error) {
|
||||
if bucketName != "avatars-bucket" {
|
||||
t.Fatalf("unexpected bucketName: %s", bucketName)
|
||||
}
|
||||
if !strings.Contains(objectName, "user_") {
|
||||
t.Fatalf("objectName should contain user_ prefix, got: %s", objectName)
|
||||
}
|
||||
if !strings.Contains(objectName, "avatar.png") {
|
||||
t.Fatalf("objectName should contain original file name, got: %s", objectName)
|
||||
}
|
||||
// 检查大小与过期时间传递
|
||||
if minSize != 1024 {
|
||||
t.Fatalf("minSize = %d, want 1024", minSize)
|
||||
}
|
||||
if maxSize != 5*1024*1024 {
|
||||
t.Fatalf("maxSize = %d, want 5MB", maxSize)
|
||||
}
|
||||
if expires != 15*time.Minute {
|
||||
t.Fatalf("expires = %v, want 15m", expires)
|
||||
}
|
||||
return &storage.PresignedPostPolicyResult{
|
||||
PostURL: "http://example.com/upload",
|
||||
FormData: map[string]string{"key": objectName},
|
||||
FileURL: "http://example.com/file/" + objectName,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// 直接将 mock 实例转换为真实类型使用(依赖其方法集与被测代码一致)
|
||||
storageClient := (*storage.StorageClient)(nil)
|
||||
_ = storageClient // 避免未使用告警,实际调用仍通过 mockClient 完成
|
||||
|
||||
// 直接通过内部使用接口的实现进行测试,避免依赖真实 StorageClient
|
||||
result, err := generateAvatarUploadURLWithClient(ctx, mockClient, 123, "avatar.png")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateAvatarUploadURL() error = %v, want nil", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatalf("GenerateAvatarUploadURL() result is nil")
|
||||
}
|
||||
if result.PostURL == "" || result.FileURL == "" {
|
||||
t.Fatalf("GenerateAvatarUploadURL() result has empty URLs: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateTextureUploadURL_Success 测试材质上传URL生成成功(SKIN/CAPE)
|
||||
func TestGenerateTextureUploadURL_Success(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
textureType string
|
||||
}{
|
||||
{"SKIN 材质", "SKIN"},
|
||||
{"CAPE 材质", "CAPE"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := &mockStorageClient{
|
||||
getBucketFn: func(name string) (string, error) {
|
||||
if name != "textures" {
|
||||
t.Fatalf("unexpected bucket name: %s", name)
|
||||
}
|
||||
return "textures-bucket", nil
|
||||
},
|
||||
generatePresignedPostURLFn: func(ctx context.Context, bucketName, objectName string, minSize, maxSize int64, expires time.Duration) (*storage.PresignedPostPolicyResult, error) {
|
||||
if bucketName != "textures-bucket" {
|
||||
t.Fatalf("unexpected bucketName: %s", bucketName)
|
||||
}
|
||||
if !strings.Contains(objectName, "texture.png") {
|
||||
t.Fatalf("objectName should contain original file name, got: %s", objectName)
|
||||
}
|
||||
if !strings.Contains(objectName, "/"+strings.ToLower(tt.textureType)+"/") {
|
||||
t.Fatalf("objectName should contain texture type folder, got: %s", objectName)
|
||||
}
|
||||
return &storage.PresignedPostPolicyResult{
|
||||
PostURL: "http://example.com/upload",
|
||||
FormData: map[string]string{"key": objectName},
|
||||
FileURL: "http://example.com/file/" + objectName,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
result, err := generateTextureUploadURLWithClient(ctx, mockClient, 123, "texture.png", tt.textureType)
|
||||
if err != nil {
|
||||
t.Fatalf("generateTextureUploadURLWithClient() error = %v, want nil", err)
|
||||
}
|
||||
if result == nil || result.PostURL == "" || result.FileURL == "" {
|
||||
t.Fatalf("generateTextureUploadURLWithClient() result invalid: %+v", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user