package service import ( "carrotskin/internal/model" "carrotskin/pkg/database" "context" "errors" "time" ) // ============================================================================ // Repository Mocks // ============================================================================ // MockUserRepository 模拟UserRepository type MockUserRepository struct { users map[int64]*model.User // 用于模拟错误的标志 FailCreate bool FailFindByID bool FailFindByUsername bool FailFindByEmail bool FailUpdate bool } func NewMockUserRepository() *MockUserRepository { return &MockUserRepository{ users: make(map[int64]*model.User), } } func (m *MockUserRepository) Create(ctx context.Context, user *model.User) error { if m.FailCreate { return errors.New("mock create error") } if user.ID == 0 { user.ID = int64(len(m.users) + 1) } m.users[user.ID] = user return nil } func (m *MockUserRepository) FindByID(ctx context.Context, id int64) (*model.User, error) { if m.FailFindByID { return nil, errors.New("mock find error") } if user, ok := m.users[id]; ok { return user, nil } return nil, nil } func (m *MockUserRepository) FindByUsername(ctx context.Context, username string) (*model.User, error) { if m.FailFindByUsername { return nil, errors.New("mock find by username error") } for _, user := range m.users { if user.Username == username { return user, nil } } return nil, nil } func (m *MockUserRepository) FindByEmail(ctx context.Context, email string) (*model.User, error) { if m.FailFindByEmail { return nil, errors.New("mock find by email error") } for _, user := range m.users { if user.Email == email { return user, nil } } return nil, nil } func (m *MockUserRepository) Update(ctx context.Context, user *model.User) error { if m.FailUpdate { return errors.New("mock update error") } m.users[user.ID] = user return nil } func (m *MockUserRepository) UpdateFields(ctx context.Context, id int64, fields map[string]interface{}) error { if m.FailUpdate { return errors.New("mock update fields error") } _, ok := m.users[id] if !ok { return errors.New("user not found") } return nil } func (m *MockUserRepository) Delete(ctx context.Context, id int64) error { delete(m.users, id) return nil } func (m *MockUserRepository) CreateLoginLog(ctx context.Context, log *model.UserLoginLog) error { return nil } func (m *MockUserRepository) CreatePointLog(ctx context.Context, log *model.UserPointLog) error { return nil } func (m *MockUserRepository) UpdatePoints(ctx context.Context, userID int64, amount int, changeType, reason string) error { return nil } // BatchUpdate 和 BatchDelete 仅用于满足接口,在测试中不做具体操作 func (m *MockUserRepository) BatchUpdate(ctx context.Context, ids []int64, fields map[string]interface{}) (int64, error) { return 0, nil } func (m *MockUserRepository) BatchDelete(ctx context.Context, ids []int64) (int64, error) { return 0, nil } // FindByIDs 批量查询用户 func (m *MockUserRepository) FindByIDs(ctx context.Context, ids []int64) ([]*model.User, error) { var result []*model.User for _, id := range ids { if u, ok := m.users[id]; ok { result = append(result, u) } } return result, nil } // MockProfileRepository 模拟ProfileRepository type MockProfileRepository struct { profiles map[string]*model.Profile userProfiles map[int64][]*model.Profile nextID int64 FailCreate bool FailFind bool FailUpdate bool FailDelete bool } func NewMockProfileRepository() *MockProfileRepository { return &MockProfileRepository{ profiles: make(map[string]*model.Profile), userProfiles: make(map[int64][]*model.Profile), nextID: 1, } } func (m *MockProfileRepository) Create(ctx context.Context, profile *model.Profile) error { if m.FailCreate { return errors.New("mock create error") } m.profiles[profile.UUID] = profile m.userProfiles[profile.UserID] = append(m.userProfiles[profile.UserID], profile) return nil } func (m *MockProfileRepository) FindByUUID(ctx context.Context, uuid string) (*model.Profile, error) { if m.FailFind { return nil, errors.New("mock find error") } if profile, ok := m.profiles[uuid]; ok { return profile, nil } return nil, errors.New("profile not found") } func (m *MockProfileRepository) FindByName(ctx context.Context, name string) (*model.Profile, error) { if m.FailFind { return nil, errors.New("mock find error") } for _, profile := range m.profiles { if profile.Name == name { return profile, nil } } return nil, nil } func (m *MockProfileRepository) FindByUserID(ctx context.Context, userID int64) ([]*model.Profile, error) { if m.FailFind { return nil, errors.New("mock find error") } return m.userProfiles[userID], nil } func (m *MockProfileRepository) Update(ctx context.Context, profile *model.Profile) error { if m.FailUpdate { return errors.New("mock update error") } m.profiles[profile.UUID] = profile return nil } func (m *MockProfileRepository) UpdateFields(ctx context.Context, uuid string, updates map[string]interface{}) error { if m.FailUpdate { return errors.New("mock update error") } return nil } func (m *MockProfileRepository) Delete(ctx context.Context, uuid string) error { if m.FailDelete { return errors.New("mock delete error") } delete(m.profiles, uuid) return nil } func (m *MockProfileRepository) CountByUserID(ctx context.Context, userID int64) (int64, error) { return int64(len(m.userProfiles[userID])), nil } func (m *MockProfileRepository) SetActive(ctx context.Context, uuid string, userID int64) error { return nil } func (m *MockProfileRepository) UpdateLastUsedAt(ctx context.Context, uuid string) error { return nil } func (m *MockProfileRepository) GetByNames(ctx context.Context, names []string) ([]*model.Profile, error) { var result []*model.Profile for _, name := range names { for _, profile := range m.profiles { if profile.Name == name { result = append(result, profile) } } } return result, nil } func (m *MockProfileRepository) GetKeyPair(ctx context.Context, profileId string) (*model.KeyPair, error) { return nil, nil } func (m *MockProfileRepository) UpdateKeyPair(ctx context.Context, profileId string, keyPair *model.KeyPair) error { return nil } // BatchUpdate / BatchDelete 仅用于满足接口 func (m *MockProfileRepository) BatchUpdate(ctx context.Context, uuids []string, updates map[string]interface{}) (int64, error) { return 0, nil } func (m *MockProfileRepository) BatchDelete(ctx context.Context, uuids []string) (int64, error) { return 0, nil } // FindByUUIDs 批量查询 Profile func (m *MockProfileRepository) FindByUUIDs(ctx context.Context, uuids []string) ([]*model.Profile, error) { var result []*model.Profile for _, id := range uuids { if p, ok := m.profiles[id]; ok { result = append(result, p) } } return result, nil } // MockTextureRepository 模拟TextureRepository type MockTextureRepository struct { textures map[int64]*model.Texture favorites map[int64]map[int64]bool // userID -> textureID -> favorited nextID int64 FailCreate bool FailFind bool FailUpdate bool FailDelete bool } func NewMockTextureRepository() *MockTextureRepository { return &MockTextureRepository{ textures: make(map[int64]*model.Texture), favorites: make(map[int64]map[int64]bool), nextID: 1, } } func (m *MockTextureRepository) Create(ctx context.Context, texture *model.Texture) error { if m.FailCreate { return errors.New("mock create error") } if texture.ID == 0 { texture.ID = m.nextID m.nextID++ } m.textures[texture.ID] = texture return nil } func (m *MockTextureRepository) FindByID(ctx context.Context, id int64) (*model.Texture, error) { if m.FailFind { return nil, errors.New("mock find error") } if texture, ok := m.textures[id]; ok { return texture, nil } return nil, errors.New("texture not found") } func (m *MockTextureRepository) FindByHash(ctx context.Context, hash string) (*model.Texture, error) { if m.FailFind { return nil, errors.New("mock find error") } for _, texture := range m.textures { if texture.Hash == hash { return texture, nil } } return nil, nil } func (m *MockTextureRepository) FindByHashAndUploaderID(ctx context.Context, hash string, uploaderID int64) (*model.Texture, error) { if m.FailFind { return nil, errors.New("mock find error") } for _, texture := range m.textures { if texture.Hash == hash && texture.UploaderID == uploaderID { return texture, nil } } return nil, nil } func (m *MockTextureRepository) FindByUploaderID(ctx context.Context, uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error) { if m.FailFind { return nil, 0, errors.New("mock find error") } var result []*model.Texture for _, texture := range m.textures { if texture.UploaderID == uploaderID { result = append(result, texture) } } return result, int64(len(result)), nil } func (m *MockTextureRepository) Search(ctx context.Context, keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error) { if m.FailFind { return nil, 0, errors.New("mock find error") } var result []*model.Texture for _, texture := range m.textures { if publicOnly && !texture.IsPublic { continue } result = append(result, texture) } return result, int64(len(result)), nil } func (m *MockTextureRepository) Update(ctx context.Context, texture *model.Texture) error { if m.FailUpdate { return errors.New("mock update error") } m.textures[texture.ID] = texture return nil } func (m *MockTextureRepository) UpdateFields(ctx context.Context, id int64, fields map[string]interface{}) error { if m.FailUpdate { return errors.New("mock update error") } return nil } func (m *MockTextureRepository) Delete(ctx context.Context, id int64) error { if m.FailDelete { return errors.New("mock delete error") } delete(m.textures, id) return nil } func (m *MockTextureRepository) IncrementDownloadCount(ctx context.Context, id int64) error { if texture, ok := m.textures[id]; ok { texture.DownloadCount++ } return nil } func (m *MockTextureRepository) IncrementFavoriteCount(ctx context.Context, id int64) error { if texture, ok := m.textures[id]; ok { texture.FavoriteCount++ } return nil } func (m *MockTextureRepository) DecrementFavoriteCount(ctx context.Context, id int64) error { if texture, ok := m.textures[id]; ok && texture.FavoriteCount > 0 { texture.FavoriteCount-- } return nil } func (m *MockTextureRepository) CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error { return nil } func (m *MockTextureRepository) IsFavorited(ctx context.Context, userID, textureID int64) (bool, error) { if userFavs, ok := m.favorites[userID]; ok { return userFavs[textureID], nil } return false, nil } func (m *MockTextureRepository) AddFavorite(ctx context.Context, userID, textureID int64) error { if m.favorites[userID] == nil { m.favorites[userID] = make(map[int64]bool) } m.favorites[userID][textureID] = true return nil } func (m *MockTextureRepository) RemoveFavorite(ctx context.Context, userID, textureID int64) error { if userFavs, ok := m.favorites[userID]; ok { delete(userFavs, textureID) } return nil } func (m *MockTextureRepository) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) { var result []*model.Texture if userFavs, ok := m.favorites[userID]; ok { for textureID := range userFavs { if texture, exists := m.textures[textureID]; exists { result = append(result, texture) } } } return result, int64(len(result)), nil } func (m *MockTextureRepository) CountByUploaderID(ctx context.Context, uploaderID int64) (int64, error) { var count int64 for _, texture := range m.textures { if texture.UploaderID == uploaderID { count++ } } return count, nil } // FindByIDs 批量查询 Texture func (m *MockTextureRepository) FindByIDs(ctx context.Context, ids []int64) ([]*model.Texture, error) { var result []*model.Texture for _, id := range ids { if tex, ok := m.textures[id]; ok { result = append(result, tex) } } return result, nil } // BatchUpdate 仅用于满足接口 func (m *MockTextureRepository) BatchUpdate(ctx context.Context, ids []int64, fields map[string]interface{}) (int64, error) { return 0, nil } // BatchDelete 仅用于满足接口 func (m *MockTextureRepository) BatchDelete(ctx context.Context, ids []int64) (int64, error) { var deleted int64 for _, id := range ids { if _, ok := m.textures[id]; ok { delete(m.textures, id) deleted++ } } return deleted, nil } // ============================================================================ // Service Mocks // ============================================================================ // MockUserService 模拟UserService type MockUserService struct { users map[int64]*model.User maxProfilesPerUser int maxTexturesPerUser int FailRegister bool FailLogin bool FailGetByID bool FailUpdate bool } func NewMockUserService() *MockUserService { return &MockUserService{ users: make(map[int64]*model.User), maxProfilesPerUser: 5, maxTexturesPerUser: 50, } } func (m *MockUserService) Register(username, password, email, avatar string) (*model.User, string, error) { if m.FailRegister { return nil, "", errors.New("mock register error") } user := &model.User{ ID: int64(len(m.users) + 1), Username: username, Email: email, Avatar: avatar, Status: 1, } m.users[user.ID] = user return user, "mock-token", nil } func (m *MockUserService) Login(usernameOrEmail, password, ipAddress, userAgent string) (*model.User, string, error) { if m.FailLogin { return nil, "", errors.New("mock login error") } for _, user := range m.users { if user.Username == usernameOrEmail || user.Email == usernameOrEmail { return user, "mock-token", nil } } return nil, "", errors.New("user not found") } func (m *MockUserService) GetByID(id int64) (*model.User, error) { if m.FailGetByID { return nil, errors.New("mock get by id error") } if user, ok := m.users[id]; ok { return user, nil } return nil, nil } func (m *MockUserService) GetByEmail(email string) (*model.User, error) { for _, user := range m.users { if user.Email == email { return user, nil } } return nil, nil } func (m *MockUserService) UpdateInfo(user *model.User) error { if m.FailUpdate { return errors.New("mock update error") } m.users[user.ID] = user return nil } func (m *MockUserService) UpdateAvatar(userID int64, avatarURL string) error { if m.FailUpdate { return errors.New("mock update error") } if user, ok := m.users[userID]; ok { user.Avatar = avatarURL } return nil } func (m *MockUserService) ChangePassword(userID int64, oldPassword, newPassword string) error { return nil } func (m *MockUserService) ResetPassword(email, newPassword string) error { return nil } func (m *MockUserService) ChangeEmail(userID int64, newEmail string) error { if user, ok := m.users[userID]; ok { user.Email = newEmail } return nil } func (m *MockUserService) ValidateAvatarURL(avatarURL string) error { return nil } func (m *MockUserService) GetMaxProfilesPerUser() int { return m.maxProfilesPerUser } func (m *MockUserService) GetMaxTexturesPerUser() int { return m.maxTexturesPerUser } // MockProfileService 模拟ProfileService type MockProfileService struct { profiles map[string]*model.Profile FailCreate bool FailGet bool FailUpdate bool FailDelete bool } func NewMockProfileService() *MockProfileService { return &MockProfileService{ profiles: make(map[string]*model.Profile), } } func (m *MockProfileService) Create(userID int64, name string) (*model.Profile, error) { if m.FailCreate { return nil, errors.New("mock create error") } profile := &model.Profile{ UUID: "mock-uuid-" + name, UserID: userID, Name: name, } m.profiles[profile.UUID] = profile return profile, nil } func (m *MockProfileService) GetByUUID(uuid string) (*model.Profile, error) { if m.FailGet { return nil, errors.New("mock get error") } if profile, ok := m.profiles[uuid]; ok { return profile, nil } return nil, errors.New("profile not found") } func (m *MockProfileService) GetByUserID(userID int64) ([]*model.Profile, error) { if m.FailGet { return nil, errors.New("mock get error") } var result []*model.Profile for _, profile := range m.profiles { if profile.UserID == userID { result = append(result, profile) } } return result, nil } func (m *MockProfileService) Update(uuid string, userID int64, name *string, skinID, capeID *int64) (*model.Profile, error) { if m.FailUpdate { return nil, errors.New("mock update error") } if profile, ok := m.profiles[uuid]; ok { if name != nil { profile.Name = *name } if skinID != nil { profile.SkinID = skinID } if capeID != nil { profile.CapeID = capeID } return profile, nil } return nil, errors.New("profile not found") } func (m *MockProfileService) Delete(uuid string, userID int64) error { if m.FailDelete { return errors.New("mock delete error") } delete(m.profiles, uuid) return nil } func (m *MockProfileService) SetActive(uuid string, userID int64) error { return nil } func (m *MockProfileService) CheckLimit(userID int64, maxProfiles int) error { count := 0 for _, profile := range m.profiles { if profile.UserID == userID { count++ } } if count >= maxProfiles { return errors.New("达到档案数量上限") } return nil } func (m *MockProfileService) GetByNames(names []string) ([]*model.Profile, error) { var result []*model.Profile for _, name := range names { for _, profile := range m.profiles { if profile.Name == name { result = append(result, profile) } } } return result, nil } func (m *MockProfileService) GetByProfileName(name string) (*model.Profile, error) { for _, profile := range m.profiles { if profile.Name == name { return profile, nil } } return nil, errors.New("profile not found") } // MockTextureService 模拟TextureService type MockTextureService struct { textures map[int64]*model.Texture nextID int64 FailCreate bool FailGet bool FailUpdate bool FailDelete bool } func NewMockTextureService() *MockTextureService { return &MockTextureService{ textures: make(map[int64]*model.Texture), nextID: 1, } } func (m *MockTextureService) Create(uploaderID int64, name, description, textureType, url, hash string, size int, isPublic, isSlim bool) (*model.Texture, error) { if m.FailCreate { return nil, errors.New("mock create error") } texture := &model.Texture{ ID: m.nextID, UploaderID: uploaderID, Name: name, Description: description, URL: url, Hash: hash, Size: size, IsPublic: isPublic, IsSlim: isSlim, } m.textures[texture.ID] = texture m.nextID++ return texture, nil } func (m *MockTextureService) GetByID(id int64) (*model.Texture, error) { if m.FailGet { return nil, errors.New("mock get error") } if texture, ok := m.textures[id]; ok { return texture, nil } return nil, errors.New("texture not found") } func (m *MockTextureService) GetByUserID(uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error) { if m.FailGet { return nil, 0, errors.New("mock get error") } var result []*model.Texture for _, texture := range m.textures { if texture.UploaderID == uploaderID { result = append(result, texture) } } return result, int64(len(result)), nil } func (m *MockTextureService) Search(keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error) { if m.FailGet { return nil, 0, errors.New("mock get error") } var result []*model.Texture for _, texture := range m.textures { if publicOnly && !texture.IsPublic { continue } result = append(result, texture) } return result, int64(len(result)), nil } func (m *MockTextureService) Update(textureID, uploaderID int64, name, description string, isPublic *bool) (*model.Texture, error) { if m.FailUpdate { return nil, errors.New("mock update error") } if texture, ok := m.textures[textureID]; ok { if name != "" { texture.Name = name } if description != "" { texture.Description = description } if isPublic != nil { texture.IsPublic = *isPublic } return texture, nil } return nil, errors.New("texture not found") } func (m *MockTextureService) Delete(textureID, uploaderID int64) error { if m.FailDelete { return errors.New("mock delete error") } delete(m.textures, textureID) return nil } func (m *MockTextureService) ToggleFavorite(userID, textureID int64) (bool, error) { return true, nil } func (m *MockTextureService) GetUserFavorites(userID int64, page, pageSize int) ([]*model.Texture, int64, error) { return nil, 0, nil } func (m *MockTextureService) CheckUploadLimit(uploaderID int64, maxTextures int) error { count := 0 for _, texture := range m.textures { if texture.UploaderID == uploaderID { count++ } } if count >= maxTextures { return errors.New("达到材质数量上限") } return nil } // ============================================================================ // CacheManager Mock - 使用 database.CacheManager 的内存版本 // ============================================================================ // NewMockCacheManager 创建一个内存 CacheManager 用于测试 func NewMockCacheManager() *database.CacheManager { return database.NewCacheManager(nil, database.CacheConfig{ Prefix: "test:", Expiration: 5 * time.Minute, Enabled: false, // 禁用缓存,测试不依赖 Redis }) }