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:
lafay
2025-12-02 19:43:39 +08:00
parent 188a05caa7
commit 801f1b1397
33 changed files with 3628 additions and 4129 deletions

View File

@@ -1,199 +1,378 @@
package service
import (
"strings"
"carrotskin/internal/model"
"carrotskin/pkg/auth"
"testing"
"go.uber.org/zap"
)
// TestGetDefaultAvatar 测试获取默认头像的逻辑
// 注意这个测试需要mock repository但由于repository是函数式的
// 我们只测试逻辑部分
func TestGetDefaultAvatar_Logic(t *testing.T) {
func TestUserServiceImpl_Register(t *testing.T) {
// 准备依赖
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
// 初始化Service
// 注意redisClient 传入 nil因为 Register 方法中没有使用 redis
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
// 测试用例
tests := []struct {
name string
configExists bool
configValue string
expectedResult string
name string
username string
password string
email string
avatar string
wantErr bool
errMsg string
setupMocks func()
}{
{
name: "配置存在时返回配置值",
configExists: true,
configValue: "https://example.com/avatar.png",
expectedResult: "https://example.com/avatar.png",
name: "正常注册",
username: "testuser",
password: "password123",
email: "test@example.com",
avatar: "",
wantErr: false,
},
{
name: "配置不存在时返回错误信息",
configExists: false,
configValue: "",
expectedResult: "数据库中不存在默认头像配置",
name: "用户名已存在",
username: "existinguser",
password: "password123",
email: "new@example.com",
avatar: "",
wantErr: true,
errMsg: "用户名已存在",
setupMocks: func() {
userRepo.Create(&model.User{
Username: "existinguser",
Email: "old@example.com",
})
},
},
{
name: "邮箱已存在",
username: "newuser",
password: "password123",
email: "existing@example.com",
avatar: "",
wantErr: true,
errMsg: "邮箱已被注册",
setupMocks: func() {
userRepo.Create(&model.User{
Username: "otheruser",
Email: "existing@example.com",
})
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 这个测试只验证逻辑不实际调用repository
// 实际的repository调用测试需要集成测试或mock
if tt.configExists {
if tt.expectedResult != tt.configValue {
t.Errorf("当配置存在时,应该返回配置值")
// 重置mock状态
if tt.setupMocks != nil {
tt.setupMocks()
}
user, token, err := userService.Register(tt.username, tt.password, tt.email, tt.avatar)
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 !strings.Contains(tt.expectedResult, "数据库中不存在默认头像配置") {
t.Errorf("当配置不存在时,应该返回错误信息")
if err != nil {
t.Errorf("不期望返回错误: %v", err)
return
}
if user == nil {
t.Error("返回的用户不应为nil")
}
if token == "" {
t.Error("返回的Token不应为空")
}
if user.Username != tt.username {
t.Errorf("用户名不匹配: got %v, want %v", user.Username, tt.username)
}
}
})
}
}
// TestLoginUser_EmailDetection 测试登录时邮箱检测逻辑
func TestLoginUser_EmailDetection(t *testing.T) {
func TestUserServiceImpl_Login(t *testing.T) {
// 准备依赖
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
// 预置用户
password := "password123"
hashedPassword, _ := auth.HashPassword(password)
testUser := &model.User{
Username: "testlogin",
Email: "login@example.com",
Password: hashedPassword,
Status: 1,
}
userRepo.Create(testUser)
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
tests := []struct {
name string
usernameOrEmail string
isEmail bool
password string
wantErr bool
errMsg string
}{
{
name: "包含@符号,识别为邮箱",
usernameOrEmail: "user@example.com",
isEmail: true,
name: "用户名登录成功",
usernameOrEmail: "testlogin",
password: "password123",
wantErr: false,
},
{
name: "不包含@符号,识别为用户名",
usernameOrEmail: "username",
isEmail: false,
name: "邮箱登录成功",
usernameOrEmail: "login@example.com",
password: "password123",
wantErr: false,
},
{
name: "空字符串",
usernameOrEmail: "",
isEmail: false,
name: "密码错误",
usernameOrEmail: "testlogin",
password: "wrongpassword",
wantErr: true,
errMsg: "用户名/邮箱或密码错误",
},
{
name: "只有@符号",
usernameOrEmail: "@",
isEmail: true,
name: "用户不存在",
usernameOrEmail: "nonexistent",
password: "password123",
wantErr: true,
errMsg: "用户名/邮箱或密码错误",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isEmail := strings.Contains(tt.usernameOrEmail, "@")
if isEmail != tt.isEmail {
t.Errorf("Email detection failed: got %v, want %v", isEmail, tt.isEmail)
user, token, err := userService.Login(tt.usernameOrEmail, tt.password, "127.0.0.1", "test-agent")
if tt.wantErr {
if err == nil {
t.Error("期望返回错误,但实际没有错误")
} else 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)
}
if user == nil {
t.Error("用户不应为nil")
}
if token == "" {
t.Error("Token不应为空")
}
}
})
}
}
// TestUserService_Constants 测试用户服务相关常量
func TestUserService_Constants(t *testing.T) {
// 测试默认用户角色
defaultRole := "user"
if defaultRole == "" {
t.Error("默认用户角色不能为空")
// TestUserServiceImpl_BasicGetters 测试 GetByID / GetByEmail / UpdateInfo / UpdateAvatar
func TestUserServiceImpl_BasicGettersAndUpdates(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
// 预置用户
user := &model.User{
ID: 1,
Username: "basic",
Email: "basic@example.com",
Avatar: "",
}
userRepo.Create(user)
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
// GetByID
gotByID, err := userService.GetByID(1)
if err != nil || gotByID == nil || gotByID.ID != 1 {
t.Fatalf("GetByID 返回不正确: user=%+v, err=%v", gotByID, err)
}
// 测试默认用户状态
defaultStatus := int16(1)
if defaultStatus != 1 {
t.Errorf("默认用户状态应为1正常实际为%d", defaultStatus)
// GetByEmail
gotByEmail, err := userService.GetByEmail("basic@example.com")
if err != nil || gotByEmail == nil || gotByEmail.Email != "basic@example.com" {
t.Fatalf("GetByEmail 返回不正确: user=%+v, err=%v", gotByEmail, err)
}
// 测试初始积分
initialPoints := 0
if initialPoints < 0 {
t.Errorf("初始积分不应为负数,实际为%d", initialPoints)
// UpdateInfo
user.Username = "updated"
if err := userService.UpdateInfo(user); err != nil {
t.Fatalf("UpdateInfo 失败: %v", err)
}
updated, _ := userRepo.FindByID(1)
if updated.Username != "updated" {
t.Fatalf("UpdateInfo 未更新用户名, got=%s", updated.Username)
}
// UpdateAvatar 只需确认不会返回错误(具体字段更新由仓库层保证)
if err := userService.UpdateAvatar(1, "http://example.com/avatar.png"); err != nil {
t.Fatalf("UpdateAvatar 失败: %v", err)
}
}
// TestUserService_Validation 测试用户数据验证逻辑
func TestUserService_Validation(t *testing.T) {
// TestUserServiceImpl_ChangePassword 测试 ChangePassword
func TestUserServiceImpl_ChangePassword(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
hashed, _ := auth.HashPassword("oldpass")
user := &model.User{
ID: 1,
Username: "changepw",
Password: hashed,
}
userRepo.Create(user)
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
// 原密码正确
if err := userService.ChangePassword(1, "oldpass", "newpass"); err != nil {
t.Fatalf("ChangePassword 正常情况失败: %v", err)
}
// 用户不存在
if err := userService.ChangePassword(999, "oldpass", "newpass"); err == nil {
t.Fatalf("ChangePassword 应在用户不存在时返回错误")
}
// 原密码错误
if err := userService.ChangePassword(1, "wrong", "another"); err == nil {
t.Fatalf("ChangePassword 应在原密码错误时返回错误")
}
}
// TestUserServiceImpl_ResetPassword 测试 ResetPassword
func TestUserServiceImpl_ResetPassword(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
user := &model.User{
ID: 1,
Username: "resetpw",
Email: "reset@example.com",
}
userRepo.Create(user)
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
// 正常重置
if err := userService.ResetPassword("reset@example.com", "newpass"); err != nil {
t.Fatalf("ResetPassword 正常情况失败: %v", err)
}
// 用户不存在
if err := userService.ResetPassword("notfound@example.com", "newpass"); err == nil {
t.Fatalf("ResetPassword 应在用户不存在时返回错误")
}
}
// TestUserServiceImpl_ChangeEmail 测试 ChangeEmail
func TestUserServiceImpl_ChangeEmail(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
user1 := &model.User{ID: 1, Email: "user1@example.com"}
user2 := &model.User{ID: 2, Email: "user2@example.com"}
userRepo.Create(user1)
userRepo.Create(user2)
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
// 正常修改
if err := userService.ChangeEmail(1, "new@example.com"); err != nil {
t.Fatalf("ChangeEmail 正常情况失败: %v", err)
}
// 邮箱被其他用户占用
if err := userService.ChangeEmail(1, "user2@example.com"); err == nil {
t.Fatalf("ChangeEmail 应在邮箱被占用时返回错误")
}
}
// TestUserServiceImpl_ValidateAvatarURL 测试 ValidateAvatarURL
func TestUserServiceImpl_ValidateAvatarURL(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
tests := []struct {
name string
username string
email string
password string
wantValid bool
name string
url string
wantErr bool
}{
{
name: "有效的用户名和邮箱",
username: "testuser",
email: "test@example.com",
password: "password123",
wantValid: true,
},
{
name: "用户名为空",
username: "",
email: "test@example.com",
password: "password123",
wantValid: false,
},
{
name: "邮箱为空",
username: "testuser",
email: "",
password: "password123",
wantValid: false,
},
{
name: "密码为空",
username: "testuser",
email: "test@example.com",
password: "",
wantValid: false,
},
{
name: "邮箱格式无效(缺少@",
username: "testuser",
email: "invalid-email",
password: "password123",
wantValid: false,
},
{"空字符串通过", "", false},
{"相对路径通过", "/images/avatar.png", false},
{"非法URL格式", "://bad-url", true},
{"非法协议", "ftp://example.com/avatar.png", true},
{"缺少主机名", "http:///avatar.png", true},
{"本地域名通过", "http://localhost/avatar.png", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 简单的验证逻辑测试
isValid := tt.username != "" && tt.email != "" && tt.password != "" && strings.Contains(tt.email, "@")
if isValid != tt.wantValid {
t.Errorf("Validation failed: got %v, want %v", isValid, tt.wantValid)
err := userService.ValidateAvatarURL(tt.url)
if (err != nil) != tt.wantErr {
t.Fatalf("ValidateAvatarURL(%q) error = %v, wantErr=%v", tt.url, err, tt.wantErr)
}
})
}
}
// TestUserService_AvatarLogic 测试头像逻辑
func TestUserService_AvatarLogic(t *testing.T) {
tests := []struct {
name string
providedAvatar string
defaultAvatar string
expectedAvatar string
}{
{
name: "提供头像时使用提供的头像",
providedAvatar: "https://example.com/custom.png",
defaultAvatar: "https://example.com/default.png",
expectedAvatar: "https://example.com/custom.png",
},
{
name: "未提供头像时使用默认头像",
providedAvatar: "",
defaultAvatar: "https://example.com/default.png",
expectedAvatar: "https://example.com/default.png",
},
// TestUserServiceImpl_MaxLimits 测试 GetMaxProfilesPerUser / GetMaxTexturesPerUser
func TestUserServiceImpl_MaxLimits(t *testing.T) {
userRepo := NewMockUserRepository()
configRepo := NewMockSystemConfigRepository()
jwtService := auth.NewJWTService("secret", 1)
logger := zap.NewNop()
// 未配置时走默认值
userService := NewUserService(userRepo, configRepo, jwtService, nil, logger)
if got := userService.GetMaxProfilesPerUser(); got != 5 {
t.Fatalf("GetMaxProfilesPerUser 默认值错误, got=%d", got)
}
if got := userService.GetMaxTexturesPerUser(); got != 50 {
t.Fatalf("GetMaxTexturesPerUser 默认值错误, got=%d", got)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
avatarURL := tt.providedAvatar
if avatarURL == "" {
avatarURL = tt.defaultAvatar
}
if avatarURL != tt.expectedAvatar {
t.Errorf("Avatar logic failed: got %s, want %s", avatarURL, tt.expectedAvatar)
}
})
// 配置有效值
configRepo.Update(&model.SystemConfig{Key: "max_profiles_per_user", Value: "10"})
configRepo.Update(&model.SystemConfig{Key: "max_textures_per_user", Value: "100"})
if got := userService.GetMaxProfilesPerUser(); got != 10 {
t.Fatalf("GetMaxProfilesPerUser 配置值错误, got=%d", got)
}
}
if got := userService.GetMaxTexturesPerUser(); got != 100 {
t.Fatalf("GetMaxTexturesPerUser 配置值错误, got=%d", got)
}
}