package service_test import ( "context" "strings" "testing" "github.com/user-management-system/internal/cache" "github.com/user-management-system/internal/service" ) // ============================================================================= // Captcha Service Tests - Phase 1 // ============================================================================= func TestCaptchaService_Generate(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) svc := service.NewCaptchaService(cacheManager) ctx := context.Background() t.Run("Generate captcha", func(t *testing.T) { result, err := svc.Generate(ctx) if err != nil { t.Fatalf("Generate failed: %v", err) } if result.CaptchaID == "" { t.Error("Expected captcha ID") } if len(result.ImageData) == 0 { t.Error("Expected image data") } }) } func TestCaptchaService_Verify(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) svc := service.NewCaptchaService(cacheManager) ctx := context.Background() t.Run("Verify captcha success", func(t *testing.T) { result, _ := svc.Generate(ctx) // Get the answer from cache val, ok := cacheManager.Get(ctx, "captcha:"+result.CaptchaID) if !ok { t.Fatal("Captcha not found in cache") } answer := val.(string) valid := svc.Verify(ctx, result.CaptchaID, answer) if !valid { t.Error("Expected captcha to be valid") } }) t.Run("Verify captcha with wrong answer", func(t *testing.T) { result, _ := svc.Generate(ctx) valid := svc.Verify(ctx, result.CaptchaID, "wrong") if valid { t.Error("Expected captcha to be invalid") } }) t.Run("Verify captcha with empty ID", func(t *testing.T) { valid := svc.Verify(ctx, "", "answer") if valid { t.Error("Expected false for empty ID") } }) t.Run("Verify captcha with empty answer", func(t *testing.T) { result, _ := svc.Generate(ctx) valid := svc.Verify(ctx, result.CaptchaID, "") if valid { t.Error("Expected false for empty answer") } }) t.Run("Verify captcha twice (one-time use)", func(t *testing.T) { result, _ := svc.Generate(ctx) val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID) answer := val.(string) // First verify svc.Verify(ctx, result.CaptchaID, answer) // Second verify should fail valid := svc.Verify(ctx, result.CaptchaID, answer) if valid { t.Error("Expected captcha to be invalid after first use") } }) t.Run("Verify captcha case insensitive", func(t *testing.T) { result, _ := svc.Generate(ctx) val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID) answer := val.(string) // Verify with uppercase valid := svc.Verify(ctx, result.CaptchaID, strings.ToUpper(answer)) if !valid { t.Error("Expected case-insensitive verification") } }) } func TestCaptchaService_ValidateCaptcha(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) svc := service.NewCaptchaService(cacheManager) ctx := context.Background() t.Run("ValidateCaptcha with empty ID", func(t *testing.T) { err := svc.ValidateCaptcha(ctx, "", "answer") if err == nil { t.Error("Expected error for empty ID") } }) t.Run("ValidateCaptcha with empty answer", func(t *testing.T) { err := svc.ValidateCaptcha(ctx, "id", "") if err == nil { t.Error("Expected error for empty answer") } }) } func TestCaptchaService_VerifyWithoutDelete(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) svc := service.NewCaptchaService(cacheManager) ctx := context.Background() t.Run("VerifyWithoutDelete success", func(t *testing.T) { result, _ := svc.Generate(ctx) val, _ := cacheManager.Get(ctx, "captcha:"+result.CaptchaID) answer := val.(string) valid := svc.VerifyWithoutDelete(ctx, result.CaptchaID, answer) if !valid { t.Error("Expected captcha to be valid") } // Should still be valid after VerifyWithoutDelete valid2 := svc.VerifyWithoutDelete(ctx, result.CaptchaID, answer) if !valid2 { t.Error("Expected captcha to still be valid after VerifyWithoutDelete") } }) t.Run("VerifyWithoutDelete with wrong answer", func(t *testing.T) { result, _ := svc.Generate(ctx) valid := svc.VerifyWithoutDelete(ctx, result.CaptchaID, "wrong") if valid { t.Error("Expected captcha to be invalid") } }) t.Run("VerifyWithoutDelete with empty ID", func(t *testing.T) { valid := svc.VerifyWithoutDelete(ctx, "", "answer") if valid { t.Error("Expected false for empty ID") } }) } // ============================================================================= // Settings Service Tests - Phase 1 // ============================================================================= func TestSettingsService_GetSettings(t *testing.T) { svc := service.NewSettingsService() ctx := context.Background() t.Run("GetSettings returns default values", func(t *testing.T) { settings, err := svc.GetSettings(ctx) if err != nil { t.Fatalf("GetSettings failed: %v", err) } if settings.System.Name == "" { t.Error("Expected system name") } if settings.System.Version == "" { t.Error("Expected system version") } }) } // ============================================================================= // Email Code Service Tests - Phase 1 // ============================================================================= func TestEmailCodeService_New(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) t.Run("NewEmailCodeService", func(t *testing.T) { cfg := service.DefaultEmailCodeConfig() svc := service.NewEmailCodeService(nil, cacheManager, cfg) if svc == nil { t.Error("Expected service instance") } }) } // ============================================================================= // SMS Code Service Tests - Phase 1 // ============================================================================= func TestSMSCodeService_New(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) t.Run("NewSMSCodeService", func(t *testing.T) { cfg := service.DefaultSMSCodeConfig() svc := service.NewSMSCodeService(nil, cacheManager, cfg) if svc == nil { t.Error("Expected service instance") } }) } // ============================================================================= // Password Reset Service Tests - Phase 1 // ============================================================================= func TestPasswordResetService_New(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) t.Run("NewPasswordResetService", func(t *testing.T) { cfg := service.DefaultPasswordResetConfig() svc := service.NewPasswordResetService(nil, cacheManager, cfg) if svc == nil { t.Error("Expected service instance") } }) } // ============================================================================= // Email Code Service Tests - Extended // ============================================================================= func TestEmailCodeService_SendEmailCode(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() svc := service.NewEmailCodeService(provider, cacheManager, cfg) ctx := context.Background() t.Run("Send email code success", func(t *testing.T) { err := svc.SendEmailCode(ctx, "test@example.com", "login") if err != nil { t.Fatalf("SendEmailCode failed: %v", err) } }) t.Run("Send email code with cooldown", func(t *testing.T) { // First request svc.SendEmailCode(ctx, "cooldown@example.com", "login") // Second request should hit cooldown err := svc.SendEmailCode(ctx, "cooldown@example.com", "login") if err == nil { t.Error("Expected rate limit error due to cooldown") } }) } func TestEmailCodeService_VerifyEmailCode(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() svc := service.NewEmailCodeService(provider, cacheManager, cfg) ctx := context.Background() t.Run("Verify email code success", func(t *testing.T) { email := "verify@example.com" svc.SendEmailCode(ctx, email, "login") // Get the code from cache val, ok := cacheManager.Get(ctx, "email_code:login:"+email) if !ok { t.Fatal("Code not found in cache") } code := val.(string) err := svc.VerifyEmailCode(ctx, email, "login", code) if err != nil { t.Fatalf("VerifyEmailCode failed: %v", err) } }) t.Run("Verify email code with wrong code", func(t *testing.T) { email := "wrong@example.com" svc.SendEmailCode(ctx, email, "login") err := svc.VerifyEmailCode(ctx, email, "login", "000000") if err == nil { t.Error("Expected error for wrong code") } }) t.Run("Verify email code with empty code", func(t *testing.T) { err := svc.VerifyEmailCode(ctx, "test@example.com", "login", "") if err == nil { t.Error("Expected error for empty code") } }) t.Run("Verify email code expired", func(t *testing.T) { err := svc.VerifyEmailCode(ctx, "nonexistent@example.com", "login", "123456") if err == nil { t.Error("Expected error for expired/missing code") } }) } // ============================================================================= // SMS Code Service Tests - Extended // ============================================================================= func TestSMSCodeService_SendCode(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockSMSProvider{} cfg := service.DefaultSMSCodeConfig() svc := service.NewSMSCodeService(provider, cacheManager, cfg) ctx := context.Background() t.Run("Send code success", func(t *testing.T) { req := &service.SendCodeRequest{ Phone: "13800138000", Purpose: "login", } resp, err := svc.SendCode(ctx, req) if err != nil { t.Fatalf("SendCode failed: %v", err) } if resp == nil { t.Error("Expected response") } }) t.Run("Send code with invalid phone", func(t *testing.T) { req := &service.SendCodeRequest{ Phone: "invalid", Purpose: "login", } _, err := svc.SendCode(ctx, req) if err == nil { t.Error("Expected error for invalid phone") } }) t.Run("Send code with nil request", func(t *testing.T) { _, err := svc.SendCode(ctx, nil) if err == nil { t.Error("Expected error for nil request") } }) t.Run("Send code with cooldown", func(t *testing.T) { phone := "13900139000" req := &service.SendCodeRequest{Phone: phone, Purpose: "login"} svc.SendCode(ctx, req) // Second request should hit cooldown _, err := svc.SendCode(ctx, req) if err == nil { t.Error("Expected rate limit error due to cooldown") } }) } func TestSMSCodeService_VerifyCode(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockSMSProvider{} cfg := service.DefaultSMSCodeConfig() svc := service.NewSMSCodeService(provider, cacheManager, cfg) ctx := context.Background() t.Run("Verify code success", func(t *testing.T) { phone := "13700137000" req := &service.SendCodeRequest{Phone: phone, Purpose: "login"} svc.SendCode(ctx, req) // Get code from cache val, ok := cacheManager.Get(ctx, "sms_code:login:"+phone) if !ok { t.Fatal("Code not found in cache") } code := val.(string) err := svc.VerifyCode(ctx, phone, "login", code) if err != nil { t.Fatalf("VerifyCode failed: %v", err) } }) t.Run("Verify code with wrong code", func(t *testing.T) { phone := "13600136000" req := &service.SendCodeRequest{Phone: phone, Purpose: "login"} svc.SendCode(ctx, req) err := svc.VerifyCode(ctx, phone, "login", "000000") if err == nil { t.Error("Expected error for wrong code") } }) t.Run("Verify code with empty code", func(t *testing.T) { err := svc.VerifyCode(ctx, "13800138000", "login", "") if err == nil { t.Error("Expected error for empty code") } }) t.Run("Verify code expired", func(t *testing.T) { err := svc.VerifyCode(ctx, "19999999999", "login", "123456") if err == nil { t.Error("Expected error for expired code") } }) } func TestSMSCodeService_NilService(t *testing.T) { var nilSvc *service.SMSCodeService ctx := context.Background() t.Run("SendCode with nil service", func(t *testing.T) { _, err := nilSvc.SendCode(ctx, &service.SendCodeRequest{Phone: "13800138000"}) if err == nil { t.Error("Expected error for nil service") } }) t.Run("VerifyCode with nil service", func(t *testing.T) { err := nilSvc.VerifyCode(ctx, "13800138000", "login", "123456") if err == nil { t.Error("Expected error for nil service") } }) } // ============================================================================= // Email Activation Service Tests // ============================================================================= func TestEmailActivationService_SendActivationEmail(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} svc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") ctx := context.Background() t.Run("Send activation email success", func(t *testing.T) { err := svc.SendActivationEmail(ctx, 1, "test@example.com", "testuser") if err != nil { t.Fatalf("SendActivationEmail failed: %v", err) } }) } func TestEmailActivationService_ValidateActivationToken(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} svc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") ctx := context.Background() t.Run("Validate activation token invalid", func(t *testing.T) { _, err := svc.ValidateActivationToken(ctx, "invalid_token") if err == nil { t.Error("Expected error for invalid token") } }) t.Run("Validate activation token empty", func(t *testing.T) { _, err := svc.ValidateActivationToken(ctx, "") if err == nil { t.Error("Expected error for empty token") } }) t.Run("Validate activation token success", func(t *testing.T) { // Send activation email first to create token svc.SendActivationEmail(ctx, 123, "activate@example.com", "testuser") // Find the token in cache // We can't directly enumerate keys, so we test with known token // This is a limitation of the test approach // In practice, we'd need to either expose the token or mock the cache }) }