Files
backend/internal/service/profile_service_test.go

690 lines
16 KiB
Go
Raw Permalink 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"
"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)
}
})
}
}
// 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)
}
})
}
}
// 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(context.Background(), 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(context.Background(), &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(context.Background(), 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(context.Background(), 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(context.Background(), &model.Profile{UUID: "p1", UserID: 1, Name: "P1"})
_ = profileRepo.Create(context.Background(), &model.Profile{UUID: "p2", UserID: 1, Name: "P2"})
_ = profileRepo.Create(context.Background(), &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 测试 Update
func TestProfileServiceImpl_Update(t *testing.T) {
profileRepo := NewMockProfileRepository()
userRepo := NewMockUserRepository()
logger := zap.NewNop()
profile := &model.Profile{
UUID: "u1",
UserID: 1,
Name: "OldName",
}
_ = profileRepo.Create(context.Background(), 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(context.Background(), &model.Profile{
UUID: "u2",
UserID: 2,
Name: "Duplicate",
})
if _, err := svc.Update(ctx, "u1", 1, stringPtr("Duplicate"), nil, nil); err == nil {
t.Fatalf("Update 在名称重复时应返回错误")
}
}
// 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(context.Background(), &model.Profile{UUID: "a", UserID: 1, Name: "A"})
_ = profileRepo.Create(context.Background(), &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)
}
}