解决合并后出现的问题,为swagger提供禁用选项,暂时移除wiki
This commit is contained in:
@@ -474,52 +474,6 @@ func (m *MockTextureRepository) BatchDelete(ctx context.Context, ids []int64) (i
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
// MockSystemConfigRepository 模拟SystemConfigRepository
|
||||
type MockSystemConfigRepository struct {
|
||||
configs map[string]*model.SystemConfig
|
||||
}
|
||||
|
||||
func NewMockSystemConfigRepository() *MockSystemConfigRepository {
|
||||
return &MockSystemConfigRepository{
|
||||
configs: make(map[string]*model.SystemConfig),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockSystemConfigRepository) GetByKey(ctx context.Context, key string) (*model.SystemConfig, error) {
|
||||
if config, ok := m.configs[key]; ok {
|
||||
return config, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockSystemConfigRepository) GetPublic(ctx context.Context) ([]model.SystemConfig, error) {
|
||||
var result []model.SystemConfig
|
||||
for _, v := range m.configs {
|
||||
result = append(result, *v)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *MockSystemConfigRepository) GetAll(ctx context.Context) ([]model.SystemConfig, error) {
|
||||
var result []model.SystemConfig
|
||||
for _, v := range m.configs {
|
||||
result = append(result, *v)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *MockSystemConfigRepository) Update(ctx context.Context, config *model.SystemConfig) error {
|
||||
m.configs[config.Key] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockSystemConfigRepository) UpdateValue(ctx context.Context, key, value string) error {
|
||||
if config, ok := m.configs[key]; ok {
|
||||
config.Value = value
|
||||
return nil
|
||||
}
|
||||
return errors.New("config not found")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Mocks
|
||||
|
||||
@@ -563,15 +563,16 @@ func TestTextureServiceImpl_Create(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
texture, err := textureService.Create(
|
||||
// UploadTexture需要文件数据,这里创建一个简单的测试数据
|
||||
fileData := []byte("fake png data for testing")
|
||||
texture, err := textureService.UploadTexture(
|
||||
ctx,
|
||||
tt.uploaderID,
|
||||
tt.textureName,
|
||||
"Test description",
|
||||
tt.textureType,
|
||||
"http://example.com/texture.png",
|
||||
tt.hash,
|
||||
512,
|
||||
fileData,
|
||||
"test.png",
|
||||
true,
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -1,512 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"carrotskin/internal/model"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TestTokenService_Constants 测试Token服务相关常量
|
||||
func TestTokenService_Constants(t *testing.T) {
|
||||
// 内部常量已私有化,通过服务行为间接测试
|
||||
t.Skip("Token constants are now private - test through service behavior instead")
|
||||
}
|
||||
|
||||
// TestTokenService_Validation 测试Token验证逻辑
|
||||
func TestTokenService_Validation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
accessToken string
|
||||
wantValid bool
|
||||
}{
|
||||
{
|
||||
name: "空token无效",
|
||||
accessToken: "",
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "非空token可能有效",
|
||||
accessToken: "valid-token-string",
|
||||
wantValid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 测试空token检查逻辑
|
||||
isValid := tt.accessToken != ""
|
||||
if isValid != tt.wantValid {
|
||||
t.Errorf("Token validation failed: got %v, want %v", isValid, tt.wantValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenService_ClientTokenLogic 测试ClientToken逻辑
|
||||
func TestTokenService_ClientTokenLogic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clientToken string
|
||||
shouldGenerate bool
|
||||
}{
|
||||
{
|
||||
name: "空的clientToken应该生成新的",
|
||||
clientToken: "",
|
||||
shouldGenerate: true,
|
||||
},
|
||||
{
|
||||
name: "非空的clientToken应该使用提供的",
|
||||
clientToken: "existing-client-token",
|
||||
shouldGenerate: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
shouldGenerate := tt.clientToken == ""
|
||||
if shouldGenerate != tt.shouldGenerate {
|
||||
t.Errorf("ClientToken logic failed: got %v, want %v", shouldGenerate, tt.shouldGenerate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenService_ProfileSelection 测试Profile选择逻辑
|
||||
func TestTokenService_ProfileSelection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
profileCount int
|
||||
shouldAutoSelect bool
|
||||
}{
|
||||
{
|
||||
name: "只有一个profile时自动选择",
|
||||
profileCount: 1,
|
||||
shouldAutoSelect: true,
|
||||
},
|
||||
{
|
||||
name: "多个profile时不自动选择",
|
||||
profileCount: 2,
|
||||
shouldAutoSelect: false,
|
||||
},
|
||||
{
|
||||
name: "没有profile时不自动选择",
|
||||
profileCount: 0,
|
||||
shouldAutoSelect: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
shouldAutoSelect := tt.profileCount == 1
|
||||
if shouldAutoSelect != tt.shouldAutoSelect {
|
||||
t.Errorf("Profile selection logic failed: got %v, want %v", shouldAutoSelect, tt.shouldAutoSelect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenService_CleanupLogic 测试清理逻辑
|
||||
func TestTokenService_CleanupLogic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenCount int
|
||||
maxCount int
|
||||
shouldCleanup bool
|
||||
cleanupCount int
|
||||
}{
|
||||
{
|
||||
name: "token数量未超过上限,不需要清理",
|
||||
tokenCount: 5,
|
||||
maxCount: 10,
|
||||
shouldCleanup: false,
|
||||
cleanupCount: 0,
|
||||
},
|
||||
{
|
||||
name: "token数量超过上限,需要清理",
|
||||
tokenCount: 15,
|
||||
maxCount: 10,
|
||||
shouldCleanup: true,
|
||||
cleanupCount: 5,
|
||||
},
|
||||
{
|
||||
name: "token数量等于上限,不需要清理",
|
||||
tokenCount: 10,
|
||||
maxCount: 10,
|
||||
shouldCleanup: false,
|
||||
cleanupCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
shouldCleanup := tt.tokenCount > tt.maxCount
|
||||
if shouldCleanup != tt.shouldCleanup {
|
||||
t.Errorf("Cleanup decision failed: got %v, want %v", shouldCleanup, tt.shouldCleanup)
|
||||
}
|
||||
|
||||
if shouldCleanup {
|
||||
expectedCleanupCount := tt.tokenCount - tt.maxCount
|
||||
if expectedCleanupCount != tt.cleanupCount {
|
||||
t.Errorf("Cleanup count failed: got %d, want %d", expectedCleanupCount, tt.cleanupCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenService_UserIDValidation 测试UserID验证
|
||||
func TestTokenService_UserIDValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID int64
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
name: "有效的UserID",
|
||||
userID: 1,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
name: "UserID为0时无效",
|
||||
userID: 0,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
name: "负数UserID无效",
|
||||
userID: -1,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := tt.userID > 0
|
||||
if isValid != tt.isValid {
|
||||
t.Errorf("UserID validation failed: got %v, want %v", isValid, tt.isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 使用 Mock 的集成测试
|
||||
// ============================================================================
|
||||
|
||||
// TestTokenServiceImpl_Create 测试创建Token
|
||||
func TestTokenServiceImpl_Create(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Profile
|
||||
testProfile := &model.Profile{
|
||||
UUID: "test-profile-uuid",
|
||||
UserID: 1,
|
||||
Name: "TestProfile",
|
||||
}
|
||||
_ = profileRepo.Create(context.Background(), testProfile)
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
userID int64
|
||||
uuid string
|
||||
clientToken string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "正常创建Token(指定UUID)",
|
||||
userID: 1,
|
||||
uuid: "test-profile-uuid",
|
||||
clientToken: "client-token-1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "正常创建Token(空clientToken)",
|
||||
userID: 1,
|
||||
uuid: "test-profile-uuid",
|
||||
clientToken: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, _, accessToken, clientToken, err := tokenService.Create(ctx, tt.userID, tt.uuid, tt.clientToken)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("期望返回错误,但实际没有错误")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
if accessToken == "" {
|
||||
t.Error("accessToken不应为空")
|
||||
}
|
||||
if clientToken == "" {
|
||||
t.Error("clientToken不应为空")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_Validate 测试验证Token
|
||||
func TestTokenServiceImpl_Validate(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Token
|
||||
testToken := &model.Token{
|
||||
AccessToken: "valid-access-token",
|
||||
ClientToken: "valid-client-token",
|
||||
UserID: 1,
|
||||
ProfileId: "test-profile-uuid",
|
||||
Usable: true,
|
||||
}
|
||||
_ = tokenRepo.Create(context.Background(), testToken)
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
accessToken string
|
||||
clientToken string
|
||||
wantValid bool
|
||||
}{
|
||||
{
|
||||
name: "有效Token(完全匹配)",
|
||||
accessToken: "valid-access-token",
|
||||
clientToken: "valid-client-token",
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "有效Token(只检查accessToken)",
|
||||
accessToken: "valid-access-token",
|
||||
clientToken: "",
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "无效Token(accessToken不存在)",
|
||||
accessToken: "invalid-access-token",
|
||||
clientToken: "",
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "无效Token(clientToken不匹配)",
|
||||
accessToken: "valid-access-token",
|
||||
clientToken: "wrong-client-token",
|
||||
wantValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
isValid := tokenService.Validate(ctx, tt.accessToken, tt.clientToken)
|
||||
|
||||
if isValid != tt.wantValid {
|
||||
t.Errorf("Token验证结果不匹配: got %v, want %v", isValid, tt.wantValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_Invalidate 测试注销Token
|
||||
func TestTokenServiceImpl_Invalidate(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置Token
|
||||
testToken := &model.Token{
|
||||
AccessToken: "token-to-invalidate",
|
||||
ClientToken: "client-token",
|
||||
UserID: 1,
|
||||
ProfileId: "test-profile-uuid",
|
||||
Usable: true,
|
||||
}
|
||||
_ = tokenRepo.Create(context.Background(), testToken)
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 验证Token存在
|
||||
isValid := tokenService.Validate(ctx, "token-to-invalidate", "")
|
||||
if !isValid {
|
||||
t.Error("Token应该有效")
|
||||
}
|
||||
|
||||
// 注销Token
|
||||
tokenService.Invalidate(ctx, "token-to-invalidate")
|
||||
|
||||
// 验证Token已失效(从repo中删除)
|
||||
_, err := tokenRepo.FindByAccessToken(context.Background(), "token-to-invalidate")
|
||||
if err == nil {
|
||||
t.Error("Token应该已被删除")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_InvalidateUserTokens 测试注销用户所有Token
|
||||
func TestTokenServiceImpl_InvalidateUserTokens(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置多个Token
|
||||
for i := 1; i <= 3; i++ {
|
||||
_ = tokenRepo.Create(context.Background(), &model.Token{
|
||||
AccessToken: fmt.Sprintf("user1-token-%d", i),
|
||||
ClientToken: "client-token",
|
||||
UserID: 1,
|
||||
ProfileId: "test-profile-uuid",
|
||||
Usable: true,
|
||||
})
|
||||
}
|
||||
_ = tokenRepo.Create(context.Background(), &model.Token{
|
||||
AccessToken: "user2-token-1",
|
||||
ClientToken: "client-token",
|
||||
UserID: 2,
|
||||
ProfileId: "test-profile-uuid-2",
|
||||
Usable: true,
|
||||
})
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 注销用户1的所有Token
|
||||
tokenService.InvalidateUserTokens(ctx, 1)
|
||||
|
||||
// 验证用户1的Token已失效
|
||||
tokens, _ := tokenRepo.GetByUserID(context.Background(), 1)
|
||||
if len(tokens) > 0 {
|
||||
t.Errorf("用户1的Token应该全部被删除,但还剩 %d 个", len(tokens))
|
||||
}
|
||||
|
||||
// 验证用户2的Token仍然存在
|
||||
tokens2, _ := tokenRepo.GetByUserID(context.Background(), 2)
|
||||
if len(tokens2) != 1 {
|
||||
t.Errorf("用户2的Token应该仍然存在,期望1个,实际 %d 个", len(tokens2))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_Refresh 覆盖 Refresh 的主要分支
|
||||
func TestTokenServiceImpl_Refresh(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
// 预置 Profile 与 Token
|
||||
profile := &model.Profile{
|
||||
UUID: "profile-uuid",
|
||||
UserID: 1,
|
||||
}
|
||||
_ = profileRepo.Create(context.Background(), profile)
|
||||
|
||||
oldToken := &model.Token{
|
||||
AccessToken: "old-token",
|
||||
ClientToken: "client-token",
|
||||
UserID: 1,
|
||||
ProfileId: "",
|
||||
Usable: true,
|
||||
}
|
||||
_ = tokenRepo.Create(context.Background(), oldToken)
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 正常刷新,不指定 profile
|
||||
newAccess, client, err := tokenService.Refresh(ctx, "old-token", "client-token", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Refresh 正常情况失败: %v", err)
|
||||
}
|
||||
if newAccess == "" || client != "client-token" {
|
||||
t.Fatalf("Refresh 返回值异常: access=%s, client=%s", newAccess, client)
|
||||
}
|
||||
|
||||
// accessToken 为空
|
||||
if _, _, err := tokenService.Refresh(ctx, "", "client-token", ""); err == nil {
|
||||
t.Fatalf("Refresh 在 accessToken 为空时应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_GetByAccessToken 封装 GetUUIDByAccessToken / GetUserIDByAccessToken
|
||||
func TestTokenServiceImpl_GetByAccessToken(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
token := &model.Token{
|
||||
AccessToken: "token-1",
|
||||
UserID: 42,
|
||||
ProfileId: "profile-42",
|
||||
Usable: true,
|
||||
}
|
||||
_ = tokenRepo.Create(context.Background(), token)
|
||||
|
||||
tokenService := NewTokenService(tokenRepo, profileRepo, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
uuid, err := tokenService.GetUUIDByAccessToken(ctx, "token-1")
|
||||
if err != nil || uuid != "profile-42" {
|
||||
t.Fatalf("GetUUIDByAccessToken 返回错误: uuid=%s, err=%v", uuid, err)
|
||||
}
|
||||
|
||||
uid, err := tokenService.GetUserIDByAccessToken(ctx, "token-1")
|
||||
if err != nil || uid != 42 {
|
||||
t.Fatalf("GetUserIDByAccessToken 返回错误: uid=%d, err=%v", uid, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenServiceImpl_validateProfileByUserID 直接测试内部校验逻辑
|
||||
func TestTokenServiceImpl_validateProfileByUserID(t *testing.T) {
|
||||
tokenRepo := NewMockTokenRepository()
|
||||
profileRepo := NewMockProfileRepository()
|
||||
logger := zap.NewNop()
|
||||
|
||||
svc := &tokenService{
|
||||
tokenRepo: tokenRepo,
|
||||
profileRepo: profileRepo,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// 预置 Profile
|
||||
profile := &model.Profile{
|
||||
UUID: "p-1",
|
||||
UserID: 1,
|
||||
}
|
||||
_ = profileRepo.Create(context.Background(), profile)
|
||||
|
||||
// 参数非法
|
||||
if ok, err := svc.validateProfileByUserID(context.Background(), 0, ""); err == nil || ok {
|
||||
t.Fatalf("validateProfileByUserID 在参数非法时应返回错误")
|
||||
}
|
||||
|
||||
// Profile 不存在
|
||||
if ok, err := svc.validateProfileByUserID(context.Background(), 1, "not-exists"); err == nil || ok {
|
||||
t.Fatalf("validateProfileByUserID 在 Profile 不存在时应返回错误")
|
||||
}
|
||||
|
||||
// 用户与 Profile 匹配
|
||||
if ok, err := svc.validateProfileByUserID(context.Background(), 1, "p-1"); err != nil || !ok {
|
||||
t.Fatalf("validateProfileByUserID 匹配时应返回 true, err=%v", err)
|
||||
}
|
||||
|
||||
// 用户与 Profile 不匹配
|
||||
if ok, err := svc.validateProfileByUserID(context.Background(), 2, "p-1"); err != nil || ok {
|
||||
t.Fatalf("validateProfileByUserID 不匹配时应返回 false, err=%v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user