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"
|
||||
)
|
||||
|
||||
// TestProfileService_Validation 测试Profile服务验证逻辑
|
||||
@@ -347,22 +350,22 @@ func TestGenerateRSAPrivateKey(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
privateKey, err := generateRSAPrivateKey()
|
||||
privateKey, err := generateRSAPrivateKeyInternal()
|
||||
if (err != nil) != tt.wantError {
|
||||
t.Errorf("generateRSAPrivateKey() error = %v, wantError %v", err, tt.wantError)
|
||||
t.Errorf("generateRSAPrivateKeyInternal() error = %v, wantError %v", err, tt.wantError)
|
||||
return
|
||||
}
|
||||
if !tt.wantError {
|
||||
if privateKey == "" {
|
||||
t.Error("generateRSAPrivateKey() 返回的私钥不应为空")
|
||||
t.Error("generateRSAPrivateKeyInternal() 返回的私钥不应为空")
|
||||
}
|
||||
// 验证PEM格式
|
||||
if len(privateKey) < 100 {
|
||||
t.Errorf("generateRSAPrivateKey() 返回的私钥长度异常: %d", len(privateKey))
|
||||
t.Errorf("generateRSAPrivateKeyInternal() 返回的私钥长度异常: %d", len(privateKey))
|
||||
}
|
||||
// 验证包含PEM头部
|
||||
if !contains(privateKey, "BEGIN RSA PRIVATE KEY") {
|
||||
t.Error("generateRSAPrivateKey() 返回的私钥应包含PEM头部")
|
||||
t.Error("generateRSAPrivateKeyInternal() 返回的私钥应包含PEM头部")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -373,9 +376,9 @@ func TestGenerateRSAPrivateKey(t *testing.T) {
|
||||
func TestGenerateRSAPrivateKey_Uniqueness(t *testing.T) {
|
||||
keys := make(map[string]bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
key, err := generateRSAPrivateKey()
|
||||
key, err := generateRSAPrivateKeyInternal()
|
||||
if err != nil {
|
||||
t.Fatalf("generateRSAPrivateKey() 失败: %v", err)
|
||||
t.Fatalf("generateRSAPrivateKeyInternal() 失败: %v", err)
|
||||
}
|
||||
if keys[key] {
|
||||
t.Errorf("第%d次生成的密钥与之前重复", i+1)
|
||||
@@ -404,3 +407,319 @@ func containsMiddle(s, substr string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 使用 Mock 的集成测试
|
||||
// ============================================================================
|
||||
|
||||
// TestProfileServiceImpl_Create 测试创建Profile
|
||||
func TestProfileServiceImpl_Create(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置用户
|
||||
testUser := &model.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
Status: 1,
|
||||
}
|
||||
userRepo.Create(testUser)
|
||||
|
||||
profileService := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
userID int64
|
||||
profileName string
|
||||
wantErr bool
|
||||
errMsg string
|
||||
setupMocks func()
|
||||
}{
|
||||
{
|
||||
name: "正常创建Profile",
|
||||
userID: 1,
|
||||
profileName: "TestProfile",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "用户不存在",
|
||||
userID: 999,
|
||||
profileName: "TestProfile2",
|
||||
wantErr: true,
|
||||
errMsg: "用户不存在",
|
||||
},
|
||||
{
|
||||
name: "角色名已存在",
|
||||
userID: 1,
|
||||
profileName: "ExistingProfile",
|
||||
wantErr: true,
|
||||
errMsg: "角色名已被使用",
|
||||
setupMocks: func() {
|
||||
profileRepo.Create(&model.Profile{
|
||||
UUID: "existing-uuid",
|
||||
UserID: 2,
|
||||
Name: "ExistingProfile",
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.setupMocks != nil {
|
||||
tt.setupMocks()
|
||||
}
|
||||
|
||||
profile, err := profileService.Create(tt.userID, tt.profileName)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
return
|
||||
}
|
||||
if tt.errMsg != "" && err.Error() != tt.errMsg {
|
||||
t.Errorf("错误信息不匹配: got %v, want %v", err.Error(), tt.errMsg)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
if profile == nil {
|
||||
t.Error("返回的Profile不应为nil")
|
||||
}
|
||||
if profile.Name != tt.profileName {
|
||||
t.Errorf("Profile名称不匹配: got %v, want %v", profile.Name, tt.profileName)
|
||||
}
|
||||
if profile.UUID == "" {
|
||||
t.Error("Profile UUID不应为空")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProfileServiceImpl_GetByUUID 测试获取Profile
|
||||
func TestProfileServiceImpl_GetByUUID(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Profile
|
||||
testProfile := &model.Profile{
|
||||
UUID: "test-uuid-123",
|
||||
UserID: 1,
|
||||
Name: "TestProfile",
|
||||
}
|
||||
profileRepo.Create(testProfile)
|
||||
|
||||
profileService := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
uuid string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "获取存在的Profile",
|
||||
uuid: "test-uuid-123",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "获取不存在的Profile",
|
||||
uuid: "non-existent-uuid",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
profile, err := profileService.GetByUUID(tt.uuid)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
if profile == nil {
|
||||
t.Error("返回的Profile不应为nil")
|
||||
}
|
||||
if profile.UUID != tt.uuid {
|
||||
t.Errorf("Profile UUID不匹配: got %v, want %v", profile.UUID, tt.uuid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProfileServiceImpl_Delete 测试删除Profile
|
||||
func TestProfileServiceImpl_Delete(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Profile
|
||||
testProfile := &model.Profile{
|
||||
UUID: "delete-test-uuid",
|
||||
UserID: 1,
|
||||
Name: "DeleteTestProfile",
|
||||
}
|
||||
profileRepo.Create(testProfile)
|
||||
|
||||
profileService := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
uuid string
|
||||
userID int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "正常删除",
|
||||
uuid: "delete-test-uuid",
|
||||
userID: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "用户ID不匹配",
|
||||
uuid: "delete-test-uuid",
|
||||
userID: 2,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := profileService.Delete(tt.uuid, tt.userID)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProfileServiceImpl_GetByUserID 测试按用户获取档案列表
|
||||
func TestProfileServiceImpl_GetByUserID(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 为用户 1 和 2 预置不同档案
|
||||
profileRepo.Create(&model.Profile{UUID: "p1", UserID: 1, Name: "P1"})
|
||||
profileRepo.Create(&model.Profile{UUID: "p2", UserID: 1, Name: "P2"})
|
||||
profileRepo.Create(&model.Profile{UUID: "p3", UserID: 2, Name: "P3"})
|
||||
|
||||
svc := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
list, err := svc.GetByUserID(1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByUserID 失败: %v", err)
|
||||
}
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("GetByUserID 返回数量错误, got=%d, want=2", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
// TestProfileServiceImpl_Update_And_SetActive 测试 Update 与 SetActive
|
||||
func TestProfileServiceImpl_Update_And_SetActive(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
profile := &model.Profile{
|
||||
UUID: "u1",
|
||||
UserID: 1,
|
||||
Name: "OldName",
|
||||
}
|
||||
profileRepo.Create(profile)
|
||||
|
||||
svc := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
// 正常更新名称与皮肤/披风
|
||||
newName := "NewName"
|
||||
var skinID int64 = 10
|
||||
var capeID int64 = 20
|
||||
updated, err := svc.Update("u1", 1, &newName, &skinID, &capeID)
|
||||
if err != nil {
|
||||
t.Fatalf("Update 正常情况失败: %v", err)
|
||||
}
|
||||
if updated == nil || updated.Name != newName {
|
||||
t.Fatalf("Update 未更新名称, got=%+v", updated)
|
||||
}
|
||||
|
||||
// 用户无权限
|
||||
if _, err := svc.Update("u1", 2, &newName, nil, nil); err == nil {
|
||||
t.Fatalf("Update 在无权限时应返回错误")
|
||||
}
|
||||
|
||||
// 名称重复
|
||||
profileRepo.Create(&model.Profile{
|
||||
UUID: "u2",
|
||||
UserID: 2,
|
||||
Name: "Duplicate",
|
||||
})
|
||||
if _, err := svc.Update("u1", 1, stringPtr("Duplicate"), nil, nil); err == nil {
|
||||
t.Fatalf("Update 在名称重复时应返回错误")
|
||||
}
|
||||
|
||||
// SetActive 正常
|
||||
if err := svc.SetActive("u1", 1); err != nil {
|
||||
t.Fatalf("SetActive 正常情况失败: %v", err)
|
||||
}
|
||||
|
||||
// SetActive 无权限
|
||||
if err := svc.SetActive("u1", 2); err == nil {
|
||||
t.Fatalf("SetActive 在无权限时应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProfileServiceImpl_CheckLimit_And_GetByNames 测试 CheckLimit / GetByNames / GetByProfileName
|
||||
func TestProfileServiceImpl_CheckLimit_And_GetByNames(t *testing.T) {
|
||||
profileRepo := NewMockProfileRepository()
|
||||
userRepo := NewMockUserRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 为用户 1 预置 2 个档案
|
||||
profileRepo.Create(&model.Profile{UUID: "a", UserID: 1, Name: "A"})
|
||||
profileRepo.Create(&model.Profile{UUID: "b", UserID: 1, Name: "B"})
|
||||
|
||||
svc := NewProfileService(profileRepo, userRepo, logger)
|
||||
|
||||
// CheckLimit 未达上限
|
||||
if err := svc.CheckLimit(1, 3); err != nil {
|
||||
t.Fatalf("CheckLimit 未达到上限时不应报错: %v", err)
|
||||
}
|
||||
|
||||
// CheckLimit 达到上限
|
||||
if err := svc.CheckLimit(1, 2); err == nil {
|
||||
t.Fatalf("CheckLimit 达到上限时应报错")
|
||||
}
|
||||
|
||||
// GetByNames
|
||||
list, err := svc.GetByNames([]string{"A", "B"})
|
||||
if err != nil {
|
||||
t.Fatalf("GetByNames 失败: %v", err)
|
||||
}
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("GetByNames 返回数量错误, got=%d, want=2", len(list))
|
||||
}
|
||||
|
||||
// GetByProfileName 存在
|
||||
p, err := svc.GetByProfileName("A")
|
||||
if err != nil || p == nil || p.Name != "A" {
|
||||
t.Fatalf("GetByProfileName 返回错误, profile=%+v, err=%v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user