package service import ( "carrotskin/internal/model" "context" "testing" "go.uber.org/zap" ) // TestTextureService_TypeValidation 测试材质类型验证 func TestTextureService_TypeValidation(t *testing.T) { tests := []struct { name string textureType string wantValid bool }{ { name: "SKIN类型有效", textureType: "SKIN", wantValid: true, }, { name: "CAPE类型有效", textureType: "CAPE", wantValid: true, }, { name: "无效类型", textureType: "INVALID", wantValid: false, }, { name: "空类型无效", textureType: "", wantValid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { isValid := tt.textureType == "SKIN" || tt.textureType == "CAPE" if isValid != tt.wantValid { t.Errorf("Texture type validation failed: got %v, want %v", isValid, tt.wantValid) } }) } } // TestTextureService_DefaultValues 测试材质默认值 func TestTextureService_DefaultValues(t *testing.T) { // 测试默认状态 defaultStatus := 1 if defaultStatus != 1 { t.Errorf("默认状态应为1,实际为%d", defaultStatus) } // 测试默认下载数 defaultDownloadCount := 0 if defaultDownloadCount != 0 { t.Errorf("默认下载数应为0,实际为%d", defaultDownloadCount) } // 测试默认收藏数 defaultFavoriteCount := 0 if defaultFavoriteCount != 0 { t.Errorf("默认收藏数应为0,实际为%d", defaultFavoriteCount) } } // TestTextureService_StatusValidation 测试材质状态验证 func TestTextureService_StatusValidation(t *testing.T) { tests := []struct { name string status int16 wantValid bool }{ { name: "状态为1(正常)时有效", status: 1, wantValid: true, }, { name: "状态为-1(删除)时无效", status: -1, wantValid: false, }, { name: "状态为0时可能有效(取决于业务逻辑)", status: 0, wantValid: true, // 状态为0(禁用)时,材质仍然存在,只是不可用,但查询时不会返回错误 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 材质状态为-1时表示已删除,无效 isValid := tt.status != -1 if isValid != tt.wantValid { t.Errorf("Status validation failed: got %v, want %v", isValid, tt.wantValid) } }) } } // TestGetUserTextures_Pagination 测试分页逻辑 func TestGetUserTextures_Pagination(t *testing.T) { tests := []struct { name string page int pageSize int wantPage int wantSize int }{ { name: "有效的分页参数", page: 2, pageSize: 20, wantPage: 2, wantSize: 20, }, { name: "page小于1,应该设为1", page: 0, pageSize: 20, wantPage: 1, wantSize: 20, }, { name: "pageSize小于1,应该设为20", page: 1, pageSize: 0, wantPage: 1, wantSize: 20, }, { name: "pageSize超过100,应该设为20", page: 1, pageSize: 200, wantPage: 1, wantSize: 20, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { page := tt.page pageSize := tt.pageSize if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } if page != tt.wantPage { t.Errorf("Page = %d, want %d", page, tt.wantPage) } if pageSize != tt.wantSize { t.Errorf("PageSize = %d, want %d", pageSize, tt.wantSize) } }) } } // TestSearchTextures_Pagination 测试搜索分页逻辑 func TestSearchTextures_Pagination(t *testing.T) { tests := []struct { name string page int pageSize int wantPage int wantSize int }{ { name: "有效的分页参数", page: 1, pageSize: 10, wantPage: 1, wantSize: 10, }, { name: "page小于1,应该设为1", page: -1, pageSize: 20, wantPage: 1, wantSize: 20, }, { name: "pageSize超过100,应该设为20", page: 1, pageSize: 150, wantPage: 1, wantSize: 20, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { page := tt.page pageSize := tt.pageSize if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } if page != tt.wantPage { t.Errorf("Page = %d, want %d", page, tt.wantPage) } if pageSize != tt.wantSize { t.Errorf("PageSize = %d, want %d", pageSize, tt.wantSize) } }) } } // TestUpdateTexture_PermissionCheck 测试更新材质的权限检查 func TestUpdateTexture_PermissionCheck(t *testing.T) { tests := []struct { name string uploaderID int64 requestID int64 wantErr bool }{ { name: "上传者ID匹配,允许更新", uploaderID: 1, requestID: 1, wantErr: false, }, { name: "上传者ID不匹配,拒绝更新", uploaderID: 1, requestID: 2, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hasError := tt.uploaderID != tt.requestID if hasError != tt.wantErr { t.Errorf("Permission check failed: got %v, want %v", hasError, tt.wantErr) } }) } } // TestUpdateTexture_FieldUpdates 测试更新字段逻辑 func TestUpdateTexture_FieldUpdates(t *testing.T) { tests := []struct { name string nameValue string descValue string isPublic *bool wantUpdates int }{ { name: "更新所有字段", nameValue: "NewName", descValue: "NewDesc", isPublic: boolPtr(true), wantUpdates: 3, }, { name: "只更新名称", nameValue: "NewName", descValue: "", isPublic: nil, wantUpdates: 1, }, { name: "只更新描述", nameValue: "", descValue: "NewDesc", isPublic: nil, wantUpdates: 1, }, { name: "只更新公开状态", nameValue: "", descValue: "", isPublic: boolPtr(false), wantUpdates: 1, }, { name: "没有更新", nameValue: "", descValue: "", isPublic: nil, wantUpdates: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { updates := 0 if tt.nameValue != "" { updates++ } if tt.descValue != "" { updates++ } if tt.isPublic != nil { updates++ } if updates != tt.wantUpdates { t.Errorf("Updates count = %d, want %d", updates, tt.wantUpdates) } }) } } // TestDeleteTexture_PermissionCheck 测试删除材质的权限检查 func TestDeleteTexture_PermissionCheck(t *testing.T) { tests := []struct { name string uploaderID int64 requestID int64 wantErr bool }{ { name: "上传者ID匹配,允许删除", uploaderID: 1, requestID: 1, wantErr: false, }, { name: "上传者ID不匹配,拒绝删除", uploaderID: 1, requestID: 2, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hasError := tt.uploaderID != tt.requestID if hasError != tt.wantErr { t.Errorf("Permission check failed: got %v, want %v", hasError, tt.wantErr) } }) } } // TestToggleTextureFavorite_Logic 测试切换收藏状态的逻辑 func TestToggleTextureFavorite_Logic(t *testing.T) { tests := []struct { name string isFavorited bool wantResult bool }{ { name: "已收藏,取消收藏", isFavorited: true, wantResult: false, }, { name: "未收藏,添加收藏", isFavorited: false, wantResult: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := !tt.isFavorited if result != tt.wantResult { t.Errorf("Toggle favorite failed: got %v, want %v", result, tt.wantResult) } }) } } // TestGetUserTextureFavorites_Pagination 测试收藏列表分页 func TestGetUserTextureFavorites_Pagination(t *testing.T) { tests := []struct { name string page int pageSize int wantPage int wantSize int }{ { name: "有效的分页参数", page: 1, pageSize: 20, wantPage: 1, wantSize: 20, }, { name: "page小于1,应该设为1", page: 0, pageSize: 20, wantPage: 1, wantSize: 20, }, { name: "pageSize超过100,应该设为20", page: 1, pageSize: 200, wantPage: 1, wantSize: 20, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { page := tt.page pageSize := tt.pageSize if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } if page != tt.wantPage { t.Errorf("Page = %d, want %d", page, tt.wantPage) } if pageSize != tt.wantSize { t.Errorf("PageSize = %d, want %d", pageSize, tt.wantSize) } }) } } // TestCheckTextureUploadLimit_Logic 测试上传限制检查逻辑 func TestCheckTextureUploadLimit_Logic(t *testing.T) { tests := []struct { name string count int64 maxTextures int wantErr bool }{ { name: "未达到上限", count: 5, maxTextures: 10, wantErr: false, }, { name: "达到上限", count: 10, maxTextures: 10, wantErr: true, }, { name: "超过上限", count: 15, maxTextures: 10, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hasError := tt.count >= int64(tt.maxTextures) if hasError != tt.wantErr { t.Errorf("Limit check failed: got %v, want %v", hasError, tt.wantErr) } }) } } // 辅助函数 func boolPtr(b bool) *bool { return &b } // ============================================================================ // 使用 Mock 的集成测试 // ============================================================================ // TestTextureServiceImpl_Create 测试创建Texture func TestTextureServiceImpl_Create(t *testing.T) { textureRepo := NewMockTextureRepository() 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() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) tests := []struct { name string uploaderID int64 textureName string textureType string hash string wantErr bool errContains string setupMocks func() }{ { name: "正常创建SKIN材质", uploaderID: 1, textureName: "TestSkin", textureType: "SKIN", hash: "unique-hash-1", wantErr: false, }, { name: "正常创建CAPE材质", uploaderID: 1, textureName: "TestCape", textureType: "CAPE", hash: "unique-hash-2", wantErr: false, }, { name: "用户不存在", uploaderID: 999, textureName: "TestTexture", textureType: "SKIN", hash: "unique-hash-3", wantErr: true, }, { name: "材质Hash已存在", uploaderID: 1, textureName: "DuplicateTexture", textureType: "SKIN", hash: "existing-hash", wantErr: false, setupMocks: func() { _ = textureRepo.Create(context.Background(), &model.Texture{ ID: 100, UploaderID: 1, Name: "ExistingTexture", Hash: "existing-hash", }) }, }, { name: "无效的材质类型", uploaderID: 1, textureName: "InvalidTypeTexture", textureType: "INVALID", hash: "unique-hash-4", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.setupMocks != nil { tt.setupMocks() } ctx := context.Background() // UploadTexture需要文件数据,这里创建一个简单的测试数据 fileData := []byte("fake png data for testing") texture, err := textureService.UploadTexture( ctx, tt.uploaderID, tt.textureName, "Test description", tt.textureType, fileData, "test.png", true, false, ) if tt.wantErr { if err == nil { t.Error("期望返回错误,但实际没有错误") return } if tt.errContains != "" && !containsString(err.Error(), tt.errContains) { t.Errorf("错误信息应包含 %q, 实际为: %v", tt.errContains, err.Error()) } } else { if err != nil { t.Errorf("不期望返回错误: %v", err) return } if texture == nil { t.Error("返回的Texture不应为nil") } if texture.Name != tt.textureName { t.Errorf("Texture名称不匹配: got %v, want %v", texture.Name, tt.textureName) } } }) } } // TestTextureServiceImpl_GetByID 测试获取Texture func TestTextureServiceImpl_GetByID(t *testing.T) { textureRepo := NewMockTextureRepository() userRepo := NewMockUserRepository() logger := zap.NewNop() // 预置Texture testTexture := &model.Texture{ ID: 1, UploaderID: 1, Name: "TestTexture", Hash: "test-hash", } _ = textureRepo.Create(context.Background(), testTexture) cacheManager := NewMockCacheManager() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) tests := []struct { name string id int64 wantErr bool }{ { name: "获取存在的Texture", id: 1, wantErr: false, }, { name: "获取不存在的Texture", id: 999, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() texture, err := textureService.GetByID(ctx, tt.id) if tt.wantErr { if err == nil { t.Error("期望返回错误,但实际没有错误") } } else { if err != nil { t.Errorf("不期望返回错误: %v", err) return } if texture == nil { t.Error("返回的Texture不应为nil") } } }) } } // TestTextureServiceImpl_GetByUserID_And_Search 测试 GetByUserID 与 Search 分页封装 func TestTextureServiceImpl_GetByUserID_And_Search(t *testing.T) { textureRepo := NewMockTextureRepository() userRepo := NewMockUserRepository() logger := zap.NewNop() // 预置多条 Texture for i := int64(1); i <= 5; i++ { _ = textureRepo.Create(context.Background(), &model.Texture{ ID: i, UploaderID: 1, Name: "T", IsPublic: i%2 == 0, }) } cacheManager := NewMockCacheManager() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) ctx := context.Background() // GetByUserID 应按上传者过滤并调用 NormalizePagination textures, total, err := textureService.GetByUserID(ctx, 1, 0, 0) if err != nil { t.Fatalf("GetByUserID 失败: %v", err) } if total != int64(len(textures)) { t.Fatalf("GetByUserID 返回数量与总数不一致, total=%d, len=%d", total, len(textures)) } // Search 仅验证能够正常调用并返回结果 searchResult, searchTotal, err := textureService.Search(ctx, "", model.TextureTypeSkin, true, -1, 200) if err != nil { t.Fatalf("Search 失败: %v", err) } if searchTotal != int64(len(searchResult)) { t.Fatalf("Search 返回数量与总数不一致, total=%d, len=%d", searchTotal, len(searchResult)) } } // TestTextureServiceImpl_Update_And_Delete 测试 Update / Delete 权限与字段更新 func TestTextureServiceImpl_Update_And_Delete(t *testing.T) { textureRepo := NewMockTextureRepository() userRepo := NewMockUserRepository() logger := zap.NewNop() texture := &model.Texture{ ID: 1, UploaderID: 1, Name: "Old", Description: "OldDesc", IsPublic: false, } _ = textureRepo.Create(context.Background(), texture) cacheManager := NewMockCacheManager() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) ctx := context.Background() // 更新成功 newName := "NewName" newDesc := "NewDesc" public := boolPtr(true) updated, err := textureService.Update(ctx, 1, 1, newName, newDesc, public) if err != nil { t.Fatalf("Update 正常情况失败: %v", err) } // 由于 MockTextureRepository.UpdateFields 不会真正修改结构体字段,这里只验证不会返回 nil 即可 if updated == nil { t.Fatalf("Update 返回结果不应为 nil") } // 无权限更新 if _, err := textureService.Update(ctx, 1, 2, "X", "Y", nil); err == nil { t.Fatalf("Update 在无权限时应返回错误") } // 删除成功 if err := textureService.Delete(ctx, 1, 1); err != nil { t.Fatalf("Delete 正常情况失败: %v", err) } // 无权限删除 if err := textureService.Delete(ctx, 1, 2); err == nil { t.Fatalf("Delete 在无权限时应返回错误") } } // TestTextureServiceImpl_FavoritesAndLimit 测试 GetUserFavorites 与 CheckUploadLimit func TestTextureServiceImpl_FavoritesAndLimit(t *testing.T) { textureRepo := NewMockTextureRepository() userRepo := NewMockUserRepository() logger := zap.NewNop() // 预置若干 Texture 与收藏关系 for i := int64(1); i <= 3; i++ { _ = textureRepo.Create(context.Background(), &model.Texture{ ID: i, UploaderID: 1, Name: "T", }) _ = textureRepo.AddFavorite(context.Background(), 1, i) } cacheManager := NewMockCacheManager() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) ctx := context.Background() // GetUserFavorites favs, total, err := textureService.GetUserFavorites(ctx, 1, -1, -1) if err != nil { t.Fatalf("GetUserFavorites 失败: %v", err) } if int64(len(favs)) != total || total != 3 { t.Fatalf("GetUserFavorites 数量不正确, total=%d, len=%d", total, len(favs)) } // CheckUploadLimit 未超过上限 if err := textureService.CheckUploadLimit(ctx, 1, 10); err != nil { t.Fatalf("CheckUploadLimit 在未达到上限时不应报错: %v", err) } // CheckUploadLimit 超过上限 if err := textureService.CheckUploadLimit(ctx, 1, 2); err == nil { t.Fatalf("CheckUploadLimit 在超过上限时应返回错误") } } // TestTextureServiceImpl_ToggleFavorite 测试收藏功能 func TestTextureServiceImpl_ToggleFavorite(t *testing.T) { textureRepo := NewMockTextureRepository() userRepo := NewMockUserRepository() logger := zap.NewNop() // 预置用户和Texture testUser := &model.User{ID: 1, Username: "testuser", Status: 1} _ = userRepo.Create(context.Background(), testUser) testTexture := &model.Texture{ ID: 1, UploaderID: 1, Name: "TestTexture", Hash: "test-hash", } _ = textureRepo.Create(context.Background(), testTexture) cacheManager := NewMockCacheManager() textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger) ctx := context.Background() // 第一次收藏 isFavorited, err := textureService.ToggleFavorite(ctx, 1, 1) if err != nil { t.Errorf("第一次收藏失败: %v", err) } if !isFavorited { t.Error("第一次操作应该是添加收藏") } // 第二次取消收藏 isFavorited, err = textureService.ToggleFavorite(ctx, 1, 1) if err != nil { t.Errorf("取消收藏失败: %v", err) } if isFavorited { t.Error("第二次操作应该是取消收藏") } } // 辅助函数 func containsString(s, substr string) bool { return len(s) >= len(substr) && (s == substr || (len(s) > len(substr) && (findSubstring(s, substr) != -1))) } func findSubstring(s, substr string) int { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return i } } return -1 }