385 lines
11 KiB
Go
385 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()
|
||
jwtService := auth.NewJWTService("secret", 1)
|
||
logger := zap.NewNop()
|
||
|
||
// 初始化Service
|
||
// 注意:redisClient 和 storageClient 传入 nil,因为 Register 方法中没有使用它们
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, 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()
|
||
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, jwtService, nil, cacheManager, nil, 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()
|
||
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, jwtService, nil, cacheManager, nil, 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()
|
||
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, jwtService, nil, cacheManager, nil, 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()
|
||
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, jwtService, nil, cacheManager, nil, 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()
|
||
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, jwtService, nil, cacheManager, nil, 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()
|
||
jwtService := auth.NewJWTService("secret", 1)
|
||
logger := zap.NewNop()
|
||
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, 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()
|
||
jwtService := auth.NewJWTService("secret", 1)
|
||
logger := zap.NewNop()
|
||
|
||
// 未配置时走默认值
|
||
cacheManager := NewMockCacheManager()
|
||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, 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)
|
||
}
|
||
}
|