- Updated main.go to initialize email service and include it in the dependency injection container. - Refactored handlers to utilize context in service method calls, improving consistency and error handling. - Introduced new service options for upload, security, and captcha services, enhancing modularity and testability. - Removed unused repository implementations to streamline the codebase. This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
402 lines
11 KiB
Go
402 lines
11 KiB
Go
package service
|
||
|
||
import (
|
||
"carrotskin/internal/model"
|
||
"carrotskin/pkg/auth"
|
||
"context"
|
||
"testing"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
func TestUserServiceImpl_Register(t *testing.T) {
|
||
// 准备依赖
|
||
userRepo := NewMockUserRepository()
|
||
configRepo := NewMockSystemConfigRepository()
|
||
jwtService := auth.NewJWTService("secret", 1)
|
||
logger := zap.NewNop()
|
||
|
||
// 初始化Service
|
||
// 注意:redisClient 和 cacheManager 传入 nil,因为 Register 方法中没有使用它们
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
// 测试用例
|
||
tests := []struct {
|
||
name string
|
||
username string
|
||
password string
|
||
email string
|
||
avatar string
|
||
wantErr bool
|
||
errMsg string
|
||
setupMocks func()
|
||
}{
|
||
{
|
||
name: "正常注册",
|
||
username: "testuser",
|
||
password: "password123",
|
||
email: "test@example.com",
|
||
avatar: "",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
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) {
|
||
// 重置mock状态
|
||
if tt.setupMocks != nil {
|
||
tt.setupMocks()
|
||
}
|
||
|
||
user, token, err := userService.Register(ctx, 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 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)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
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)
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
tests := []struct {
|
||
name string
|
||
usernameOrEmail string
|
||
password string
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "用户名登录成功",
|
||
usernameOrEmail: "testlogin",
|
||
password: "password123",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "邮箱登录成功",
|
||
usernameOrEmail: "login@example.com",
|
||
password: "password123",
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "密码错误",
|
||
usernameOrEmail: "testlogin",
|
||
password: "wrongpassword",
|
||
wantErr: true,
|
||
errMsg: "用户名/邮箱或密码错误",
|
||
},
|
||
{
|
||
name: "用户不存在",
|
||
usernameOrEmail: "nonexistent",
|
||
password: "password123",
|
||
wantErr: true,
|
||
errMsg: "用户名/邮箱或密码错误",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
user, token, err := userService.Login(ctx, 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不应为空")
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
// GetByID
|
||
gotByID, err := userService.GetByID(ctx, 1)
|
||
if err != nil || gotByID == nil || gotByID.ID != 1 {
|
||
t.Fatalf("GetByID 返回不正确: user=%+v, err=%v", gotByID, err)
|
||
}
|
||
|
||
// GetByEmail
|
||
gotByEmail, err := userService.GetByEmail(ctx, "basic@example.com")
|
||
if err != nil || gotByEmail == nil || gotByEmail.Email != "basic@example.com" {
|
||
t.Fatalf("GetByEmail 返回不正确: user=%+v, err=%v", gotByEmail, err)
|
||
}
|
||
|
||
// UpdateInfo
|
||
user.Username = "updated"
|
||
if err := userService.UpdateInfo(ctx, 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(ctx, 1, "http://example.com/avatar.png"); err != nil {
|
||
t.Fatalf("UpdateAvatar 失败: %v", err)
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
// 原密码正确
|
||
if err := userService.ChangePassword(ctx, 1, "oldpass", "newpass"); err != nil {
|
||
t.Fatalf("ChangePassword 正常情况失败: %v", err)
|
||
}
|
||
|
||
// 用户不存在
|
||
if err := userService.ChangePassword(ctx, 999, "oldpass", "newpass"); err == nil {
|
||
t.Fatalf("ChangePassword 应在用户不存在时返回错误")
|
||
}
|
||
|
||
// 原密码错误
|
||
if err := userService.ChangePassword(ctx, 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)
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
// 正常重置
|
||
if err := userService.ResetPassword(ctx, "reset@example.com", "newpass"); err != nil {
|
||
t.Fatalf("ResetPassword 正常情况失败: %v", err)
|
||
}
|
||
|
||
// 用户不存在
|
||
if err := userService.ResetPassword(ctx, "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)
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
// 正常修改
|
||
if err := userService.ChangeEmail(ctx, 1, "new@example.com"); err != nil {
|
||
t.Fatalf("ChangeEmail 正常情况失败: %v", err)
|
||
}
|
||
|
||
// 邮箱被其他用户占用
|
||
if err := userService.ChangeEmail(ctx, 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()
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, logger)
|
||
|
||
ctx := context.Background()
|
||
|
||
tests := []struct {
|
||
name string
|
||
url string
|
||
wantErr bool
|
||
}{
|
||
{"空字符串通过", "", 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) {
|
||
err := userService.ValidateAvatarURL(ctx, tt.url)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Fatalf("ValidateAvatarURL(%q) error = %v, wantErr=%v", tt.url, err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestUserServiceImpl_MaxLimits 测试 GetMaxProfilesPerUser / GetMaxTexturesPerUser
|
||
func TestUserServiceImpl_MaxLimits(t *testing.T) {
|
||
userRepo := NewMockUserRepository()
|
||
configRepo := NewMockSystemConfigRepository()
|
||
jwtService := auth.NewJWTService("secret", 1)
|
||
logger := zap.NewNop()
|
||
|
||
// 未配置时走默认值
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, configRepo, jwtService, nil, cacheManager, 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)
|
||
}
|
||
|
||
// 配置有效值
|
||
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)
|
||
}
|
||
}
|