package service import ( "carrotskin/internal/model" "fmt" "testing" "time" "go.uber.org/zap" ) // TestTokenService_Constants 测试Token服务相关常量 func TestTokenService_Constants(t *testing.T) { // 测试私有常量通过行为验证 if tokenExtendedTimeout != 10*time.Second { t.Errorf("tokenExtendedTimeout = %v, want 10 seconds", tokenExtendedTimeout) } if tokensMaxCount != 10 { t.Errorf("tokensMaxCount = %d, want 10", tokensMaxCount) } } // TestTokenService_Timeout 测试超时常量 func TestTokenService_Timeout(t *testing.T) { if DefaultTimeout != 5*time.Second { t.Errorf("DefaultTimeout = %v, want 5 seconds", DefaultTimeout) } if tokenExtendedTimeout <= DefaultTimeout { t.Errorf("tokenExtendedTimeout (%v) should be greater than DefaultTimeout (%v)", tokenExtendedTimeout, DefaultTimeout) } } // 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", IsActive: true, } profileRepo.Create(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) { _, _, accessToken, clientToken, err := tokenService.Create(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(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) { isValid := tokenService.Validate(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(testToken) tokenService := NewTokenService(tokenRepo, profileRepo, logger) // 验证Token存在 isValid := tokenService.Validate("token-to-invalidate", "") if !isValid { t.Error("Token应该有效") } // 注销Token tokenService.Invalidate("token-to-invalidate") // 验证Token已失效(从repo中删除) _, err := tokenRepo.FindByAccessToken("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(&model.Token{ AccessToken: fmt.Sprintf("user1-token-%d", i), ClientToken: "client-token", UserID: 1, ProfileId: "test-profile-uuid", Usable: true, }) } tokenRepo.Create(&model.Token{ AccessToken: "user2-token-1", ClientToken: "client-token", UserID: 2, ProfileId: "test-profile-uuid-2", Usable: true, }) tokenService := NewTokenService(tokenRepo, profileRepo, logger) // 注销用户1的所有Token tokenService.InvalidateUserTokens(1) // 验证用户1的Token已失效 tokens, _ := tokenRepo.GetByUserID(1) if len(tokens) > 0 { t.Errorf("用户1的Token应该全部被删除,但还剩 %d 个", len(tokens)) } // 验证用户2的Token仍然存在 tokens2, _ := tokenRepo.GetByUserID(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(profile) oldToken := &model.Token{ AccessToken: "old-token", ClientToken: "client-token", UserID: 1, ProfileId: "", Usable: true, } tokenRepo.Create(oldToken) tokenService := NewTokenService(tokenRepo, profileRepo, logger) // 正常刷新,不指定 profile newAccess, client, err := tokenService.Refresh("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("", "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(token) tokenService := NewTokenService(tokenRepo, profileRepo, logger) uuid, err := tokenService.GetUUIDByAccessToken("token-1") if err != nil || uuid != "profile-42" { t.Fatalf("GetUUIDByAccessToken 返回错误: uuid=%s, err=%v", uuid, err) } uid, err := tokenService.GetUserIDByAccessToken("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 := &tokenServiceImpl{ tokenRepo: tokenRepo, profileRepo: profileRepo, logger: logger, } // 预置 Profile profile := &model.Profile{ UUID: "p-1", UserID: 1, } profileRepo.Create(profile) // 参数非法 if ok, err := svc.validateProfileByUserID(0, ""); err == nil || ok { t.Fatalf("validateProfileByUserID 在参数非法时应返回错误") } // Profile 不存在 if ok, err := svc.validateProfileByUserID(1, "not-exists"); err == nil || ok { t.Fatalf("validateProfileByUserID 在 Profile 不存在时应返回错误") } // 用户与 Profile 匹配 if ok, err := svc.validateProfileByUserID(1, "p-1"); err != nil || !ok { t.Fatalf("validateProfileByUserID 匹配时应返回 true, err=%v", err) } // 用户与 Profile 不匹配 if ok, err := svc.validateProfileByUserID(2, "p-1"); err != nil || ok { t.Fatalf("validateProfileByUserID 不匹配时应返回 false, err=%v", err) } }