Files
backend/internal/service/texture_service_test.go

847 lines
19 KiB
Go
Raw Normal View History

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
}