Files
backend/internal/service/user_service_test.go

403 lines
12 KiB
Go
Raw Normal View History

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,
// 服务实现现已统一使用 apperrors.ErrUserAlreadyExists错误信息为“用户已存在”
errMsg: "用户已存在",
setupMocks: func() {
_ = userRepo.Create(context.Background(), &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(context.Background(), &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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), 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(context.Background(), user1)
_ = userRepo.Create(context.Background(), 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(context.Background(), &model.SystemConfig{Key: "max_profiles_per_user", Value: "10"})
_ = configRepo.Update(context.Background(), &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)
}
}