Files
backend/internal/service/user_service_test.go

385 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}