Files
backend/internal/service/profile_service_test.go

741 lines
17 KiB
Go
Raw Normal View History

package service
import (
"carrotskin/internal/model"
"context"
"testing"
"go.uber.org/zap"
)
// TestProfileService_Validation 测试Profile服务验证逻辑
func TestProfileService_Validation(t *testing.T) {
tests := []struct {
name string
userID int64
profileName string
wantValid bool
}{
{
name: "有效的用户ID和角色名",
userID: 1,
profileName: "TestProfile",
wantValid: true,
},
{
name: "用户ID为0时无效",
userID: 0,
profileName: "TestProfile",
wantValid: false,
},
{
name: "角色名为空时无效",
userID: 1,
profileName: "",
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.userID > 0 && tt.profileName != ""
if isValid != tt.wantValid {
t.Errorf("Validation failed: got %v, want %v", isValid, tt.wantValid)
}
})
}
}
// TestProfileService_StatusValidation 测试用户状态验证
func TestProfileService_StatusValidation(t *testing.T) {
tests := []struct {
name string
status int16
wantValid bool
}{
{
name: "状态为1正常时有效",
status: 1,
wantValid: true,
},
{
name: "状态为0禁用时无效",
status: 0,
wantValid: false,
},
{
name: "状态为-1删除时无效",
status: -1,
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.status == 1
if isValid != tt.wantValid {
t.Errorf("Status validation failed: got %v, want %v", isValid, tt.wantValid)
}
})
}
}
// TestProfileService_IsActiveDefault 测试Profile默认活跃状态
func TestProfileService_IsActiveDefault(t *testing.T) {
// 新创建的档案默认为活跃状态
isActive := true
if !isActive {
t.Error("新创建的Profile应该默认为活跃状态")
}
}
// TestUpdateProfile_PermissionCheck 测试更新Profile的权限检查逻辑
func TestUpdateProfile_PermissionCheck(t *testing.T) {
tests := []struct {
name string
profileUserID int64
requestUserID int64
wantErr bool
}{
{
name: "用户ID匹配允许操作",
profileUserID: 1,
requestUserID: 1,
wantErr: false,
},
{
name: "用户ID不匹配拒绝操作",
profileUserID: 1,
requestUserID: 2,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasError := tt.profileUserID != tt.requestUserID
if hasError != tt.wantErr {
t.Errorf("Permission check failed: got %v, want %v", hasError, tt.wantErr)
}
})
}
}
// TestUpdateProfile_NameValidation 测试更新Profile时名称验证逻辑
func TestUpdateProfile_NameValidation(t *testing.T) {
tests := []struct {
name string
currentName string
newName *string
shouldCheck bool
}{
{
name: "名称未改变,不检查",
currentName: "TestProfile",
newName: stringPtr("TestProfile"),
shouldCheck: false,
},
{
name: "名称改变,需要检查",
currentName: "TestProfile",
newName: stringPtr("NewProfile"),
shouldCheck: true,
},
{
name: "名称为nil不检查",
currentName: "TestProfile",
newName: nil,
shouldCheck: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
shouldCheck := tt.newName != nil && *tt.newName != tt.currentName
if shouldCheck != tt.shouldCheck {
t.Errorf("Name validation check failed: got %v, want %v", shouldCheck, tt.shouldCheck)
}
})
}
}
// TestDeleteProfile_PermissionCheck 测试删除Profile的权限检查
func TestDeleteProfile_PermissionCheck(t *testing.T) {
tests := []struct {
name string
profileUserID int64
requestUserID int64
wantErr bool
}{
{
name: "用户ID匹配允许删除",
profileUserID: 1,
requestUserID: 1,
wantErr: false,
},
{
name: "用户ID不匹配拒绝删除",
profileUserID: 1,
requestUserID: 2,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasError := tt.profileUserID != tt.requestUserID
if hasError != tt.wantErr {
t.Errorf("Permission check failed: got %v, want %v", hasError, tt.wantErr)
}
})
}
}
// TestSetActiveProfile_PermissionCheck 测试设置活跃Profile的权限检查
func TestSetActiveProfile_PermissionCheck(t *testing.T) {
tests := []struct {
name string
profileUserID int64
requestUserID int64
wantErr bool
}{
{
name: "用户ID匹配允许设置",
profileUserID: 1,
requestUserID: 1,
wantErr: false,
},
{
name: "用户ID不匹配拒绝设置",
profileUserID: 1,
requestUserID: 2,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasError := tt.profileUserID != tt.requestUserID
if hasError != tt.wantErr {
t.Errorf("Permission check failed: got %v, want %v", hasError, tt.wantErr)
}
})
}
}
// TestCheckProfileLimit_Logic 测试Profile数量限制检查逻辑
func TestCheckProfileLimit_Logic(t *testing.T) {
tests := []struct {
name string
count int
maxProfiles int
wantErr bool
}{
{
name: "未达到上限",
count: 5,
maxProfiles: 10,
wantErr: false,
},
{
name: "达到上限",
count: 10,
maxProfiles: 10,
wantErr: true,
},
{
name: "超过上限",
count: 15,
maxProfiles: 10,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasError := tt.count >= tt.maxProfiles
if hasError != tt.wantErr {
t.Errorf("Limit check failed: got %v, want %v", hasError, tt.wantErr)
}
})
}
}
// TestValidateProfileByUserID_InputValidation 测试ValidateProfileByUserID输入验证
func TestValidateProfileByUserID_InputValidation(t *testing.T) {
tests := []struct {
name string
userID int64
uuid string
wantErr bool
}{
{
name: "有效输入",
userID: 1,
uuid: "test-uuid",
wantErr: false,
},
{
name: "userID为0",
userID: 0,
uuid: "test-uuid",
wantErr: true,
},
{
name: "uuid为空",
userID: 1,
uuid: "",
wantErr: true,
},
{
name: "两者都无效",
userID: 0,
uuid: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasError := tt.userID == 0 || tt.uuid == ""
if hasError != tt.wantErr {
t.Errorf("Input validation failed: got %v, want %v", hasError, tt.wantErr)
}
})
}
}
// TestValidateProfileByUserID_UserIDMatching 测试用户ID匹配逻辑
func TestValidateProfileByUserID_UserIDMatching(t *testing.T) {
tests := []struct {
name string
profileUserID int64
requestUserID int64
wantValid bool
}{
{
name: "用户ID匹配",
profileUserID: 1,
requestUserID: 1,
wantValid: true,
},
{
name: "用户ID不匹配",
profileUserID: 1,
requestUserID: 2,
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.profileUserID == tt.requestUserID
if isValid != tt.wantValid {
t.Errorf("UserID matching failed: got %v, want %v", isValid, tt.wantValid)
}
})
}
}
// TestGenerateRSAPrivateKey 测试RSA私钥生成
func TestGenerateRSAPrivateKey(t *testing.T) {
tests := []struct {
name string
wantError bool
}{
{
name: "生成RSA私钥",
wantError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
privateKey, err := generateRSAPrivateKeyInternal()
if (err != nil) != tt.wantError {
t.Errorf("generateRSAPrivateKeyInternal() error = %v, wantError %v", err, tt.wantError)
return
}
if !tt.wantError {
if privateKey == "" {
t.Error("generateRSAPrivateKeyInternal() 返回的私钥不应为空")
}
// 验证PEM格式
if len(privateKey) < 100 {
t.Errorf("generateRSAPrivateKeyInternal() 返回的私钥长度异常: %d", len(privateKey))
}
// 验证包含PEM头部
if !contains(privateKey, "BEGIN RSA PRIVATE KEY") {
t.Error("generateRSAPrivateKeyInternal() 返回的私钥应包含PEM头部")
}
}
})
}
}
// TestGenerateRSAPrivateKey_Uniqueness 测试RSA私钥唯一性
func TestGenerateRSAPrivateKey_Uniqueness(t *testing.T) {
keys := make(map[string]bool)
for i := 0; i < 10; i++ {
key, err := generateRSAPrivateKeyInternal()
if err != nil {
t.Fatalf("generateRSAPrivateKeyInternal() 失败: %v", err)
}
if keys[key] {
t.Errorf("第%d次生成的密钥与之前重复", i+1)
}
keys[key] = true
}
}
// 辅助函数
func stringPtr(s string) *string {
return &s
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr ||
(len(s) > len(substr) && (s[:len(substr)] == substr ||
s[len(s)-len(substr):] == substr ||
containsMiddle(s, substr))))
}
func containsMiddle(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
// ============================================================================
// 使用 Mock 的集成测试
// ============================================================================
// TestProfileServiceImpl_Create 测试创建Profile
func TestProfileServiceImpl_Create(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
// 预置用户
testUser := &model.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
Status: 1,
}
userRepo.Create(testUser)
cacheManager := NewMockCacheManager()
profileService := NewProfileService(profileRepo, userRepo, cacheManager, logger)
tests := []struct {
name string
userID int64
profileName string
wantErr bool
errMsg string
setupMocks func()
}{
{
name: "正常创建Profile",
userID: 1,
profileName: "TestProfile",
wantErr: false,
},
{
name: "用户不存在",
userID: 999,
profileName: "TestProfile2",
wantErr: true,
errMsg: "用户不存在",
},
{
name: "角色名已存在",
userID: 1,
profileName: "ExistingProfile",
wantErr: true,
errMsg: "角色名已被使用",
setupMocks: func() {
profileRepo.Create(&model.Profile{
UUID: "existing-uuid",
UserID: 2,
Name: "ExistingProfile",
})
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setupMocks != nil {
tt.setupMocks()
}
ctx := context.Background()
profile, err := profileService.Create(ctx, tt.userID, tt.profileName)
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 profile == nil {
t.Error("返回的Profile不应为nil")
}
if profile.Name != tt.profileName {
t.Errorf("Profile名称不匹配: got %v, want %v", profile.Name, tt.profileName)
}
if profile.UUID == "" {
t.Error("Profile UUID不应为空")
}
}
})
}
}
// TestProfileServiceImpl_GetByUUID 测试获取Profile
func TestProfileServiceImpl_GetByUUID(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
// 预置Profile
testProfile := &model.Profile{
UUID: "test-uuid-123",
UserID: 1,
Name: "TestProfile",
}
profileRepo.Create(testProfile)
cacheManager := NewMockCacheManager()
profileService := NewProfileService(profileRepo, userRepo, cacheManager, logger)
tests := []struct {
name string
uuid string
wantErr bool
}{
{
name: "获取存在的Profile",
uuid: "test-uuid-123",
wantErr: false,
},
{
name: "获取不存在的Profile",
uuid: "non-existent-uuid",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
profile, err := profileService.GetByUUID(ctx, tt.uuid)
if tt.wantErr {
if err == nil {
t.Error("期望返回错误,但实际没有错误")
}
} else {
if err != nil {
t.Errorf("不期望返回错误: %v", err)
return
}
if profile == nil {
t.Error("返回的Profile不应为nil")
}
if profile.UUID != tt.uuid {
t.Errorf("Profile UUID不匹配: got %v, want %v", profile.UUID, tt.uuid)
}
}
})
}
}
// TestProfileServiceImpl_Delete 测试删除Profile
func TestProfileServiceImpl_Delete(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
// 预置Profile
testProfile := &model.Profile{
UUID: "delete-test-uuid",
UserID: 1,
Name: "DeleteTestProfile",
}
profileRepo.Create(testProfile)
cacheManager := NewMockCacheManager()
profileService := NewProfileService(profileRepo, userRepo, cacheManager, logger)
tests := []struct {
name string
uuid string
userID int64
wantErr bool
}{
{
name: "正常删除",
uuid: "delete-test-uuid",
userID: 1,
wantErr: false,
},
{
name: "用户ID不匹配",
uuid: "delete-test-uuid",
userID: 2,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
err := profileService.Delete(ctx, tt.uuid, tt.userID)
if tt.wantErr {
if err == nil {
t.Error("期望返回错误,但实际没有错误")
}
} else {
if err != nil {
t.Errorf("不期望返回错误: %v", err)
}
}
})
}
}
// TestProfileServiceImpl_GetByUserID 测试按用户获取档案列表
func TestProfileServiceImpl_GetByUserID(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
// 为用户 1 和 2 预置不同档案
profileRepo.Create(&model.Profile{UUID: "p1", UserID: 1, Name: "P1"})
profileRepo.Create(&model.Profile{UUID: "p2", UserID: 1, Name: "P2"})
profileRepo.Create(&model.Profile{UUID: "p3", UserID: 2, Name: "P3"})
cacheManager := NewMockCacheManager()
svc := NewProfileService(profileRepo, userRepo, cacheManager, logger)
ctx := context.Background()
list, err := svc.GetByUserID(ctx, 1)
if err != nil {
t.Fatalf("GetByUserID 失败: %v", err)
}
if len(list) != 2 {
t.Fatalf("GetByUserID 返回数量错误, got=%d, want=2", len(list))
}
}
// TestProfileServiceImpl_Update_And_SetActive 测试 Update 与 SetActive
func TestProfileServiceImpl_Update_And_SetActive(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
profile := &model.Profile{
UUID: "u1",
UserID: 1,
Name: "OldName",
}
profileRepo.Create(profile)
cacheManager := NewMockCacheManager()
svc := NewProfileService(profileRepo, userRepo, cacheManager, logger)
ctx := context.Background()
// 正常更新名称与皮肤/披风
newName := "NewName"
var skinID int64 = 10
var capeID int64 = 20
updated, err := svc.Update(ctx, "u1", 1, &newName, &skinID, &capeID)
if err != nil {
t.Fatalf("Update 正常情况失败: %v", err)
}
if updated == nil || updated.Name != newName {
t.Fatalf("Update 未更新名称, got=%+v", updated)
}
// 用户无权限
if _, err := svc.Update(ctx, "u1", 2, &newName, nil, nil); err == nil {
t.Fatalf("Update 在无权限时应返回错误")
}
// 名称重复
profileRepo.Create(&model.Profile{
UUID: "u2",
UserID: 2,
Name: "Duplicate",
})
if _, err := svc.Update(ctx, "u1", 1, stringPtr("Duplicate"), nil, nil); err == nil {
t.Fatalf("Update 在名称重复时应返回错误")
}
// SetActive 正常
if err := svc.SetActive(ctx, "u1", 1); err != nil {
t.Fatalf("SetActive 正常情况失败: %v", err)
}
// SetActive 无权限
if err := svc.SetActive(ctx, "u1", 2); err == nil {
t.Fatalf("SetActive 在无权限时应返回错误")
}
}
// TestProfileServiceImpl_CheckLimit_And_GetByNames 测试 CheckLimit / GetByNames / GetByProfileName
func TestProfileServiceImpl_CheckLimit_And_GetByNames(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
// 为用户 1 预置 2 个档案
profileRepo.Create(&model.Profile{UUID: "a", UserID: 1, Name: "A"})
profileRepo.Create(&model.Profile{UUID: "b", UserID: 1, Name: "B"})
cacheManager := NewMockCacheManager()
svc := NewProfileService(profileRepo, userRepo, cacheManager, logger)
ctx := context.Background()
// CheckLimit 未达上限
if err := svc.CheckLimit(ctx, 1, 3); err != nil {
t.Fatalf("CheckLimit 未达到上限时不应报错: %v", err)
}
// CheckLimit 达到上限
if err := svc.CheckLimit(ctx, 1, 2); err == nil {
t.Fatalf("CheckLimit 达到上限时应报错")
}
// GetByNames
list, err := svc.GetByNames(ctx, []string{"A", "B"})
if err != nil {
t.Fatalf("GetByNames 失败: %v", err)
}
if len(list) != 2 {
t.Fatalf("GetByNames 返回数量错误, got=%d, want=2", len(list))
}
// GetByProfileName 存在
p, err := svc.GetByProfileName(ctx, "A")
if err != nil || p == nil || p.Name != "A" {
t.Fatalf("GetByProfileName 返回错误, profile=%+v, err=%v", p, err)
}
}