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