package service_test import ( "context" "fmt" "testing" "time" "github.com/user-management-system/internal/auth" "github.com/user-management-system/internal/cache" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/repository" "github.com/user-management-system/internal/service" gormsqlite "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) // ============================================================================= // Auth Email Service Tests // ============================================================================= func setupAuthEmailTestEnv(t *testing.T) (*service.AuthService, *gorm.DB) { t.Helper() dsn := fmt.Sprintf("file:auth_email_test_%d?mode=memory&cache=shared", time.Now().UnixNano()) db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: dsn, }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("failed to connect database: %v", err) } if err := db.AutoMigrate(&domain.User{}, &domain.Role{}, &domain.UserRole{}); err != nil { t.Fatalf("failed to migrate: %v", err) } // Create predefined roles for _, role := range domain.PredefinedRoles { db.Create(&role) } jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: fmt.Sprintf("test-secret-%d", time.Now().UnixNano()), AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) userRepo := repository.NewUserRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) roleRepo := repository.NewRoleRepository(db) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) svc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) svc.SetRoleRepositories(userRoleRepo, roleRepo) return svc, db } func TestAuthService_SetEmailActivationService(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) t.Run("Set email activation service", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) // No error means success }) } func TestAuthService_SetEmailCodeService(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) t.Run("Set email code service", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg) svc.SetEmailCodeService(emailCodeSvc) // No error means success }) } func TestAuthService_HasEmailCodeService(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) t.Run("Has email code service false", func(t *testing.T) { if svc.HasEmailCodeService() { t.Error("Expected false for service without email code service") } }) t.Run("Has email code service true", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg) svc.SetEmailCodeService(emailCodeSvc) if !svc.HasEmailCodeService() { t.Error("Expected true after setting email code service") } }) t.Run("Has email code service nil", func(t *testing.T) { var nilSvc *service.AuthService if nilSvc.HasEmailCodeService() { t.Error("Expected false for nil service") } }) } func TestAuthService_SendEmailLoginCode(t *testing.T) { svc, db := setupAuthEmailTestEnv(t) ctx := context.Background() // Create test user with email email := "logincode@test.com" user := &domain.User{ Username: "logincodeuser", Email: &email, Status: domain.UserStatusActive, } db.Create(user) t.Run("Send email login code without service configured", func(t *testing.T) { err := svc.SendEmailLoginCode(ctx, "test@test.com") if err == nil { t.Error("Expected error when email code service not configured") } }) t.Run("Send email login code with service", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg) svc.SetEmailCodeService(emailCodeSvc) err := svc.SendEmailLoginCode(ctx, email) if err != nil { t.Fatalf("SendEmailLoginCode failed: %v", err) } }) t.Run("Send email login code for non-existent email", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg) svc.SetEmailCodeService(emailCodeSvc) // Should return nil to avoid user enumeration err := svc.SendEmailLoginCode(ctx, "nonexistent@test.com") if err != nil { t.Fatalf("Expected nil for non-existent email, got: %v", err) } }) } func TestAuthService_LoginByEmailCode(t *testing.T) { svc, db := setupAuthEmailTestEnv(t) ctx := context.Background() // Create test user with email email := "emailcode@test.com" user := &domain.User{ Username: "emailcodeuser", Email: &email, Status: domain.UserStatusActive, } db.Create(user) t.Run("Login by email code without service", func(t *testing.T) { _, err := svc.LoginByEmailCode(ctx, email, "123456", "127.0.0.1") if err == nil { t.Error("Expected error when email code service not configured") } }) t.Run("Login by email code with invalid code", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} cfg := service.DefaultEmailCodeConfig() emailCodeSvc := service.NewEmailCodeService(provider, cacheManager, cfg) svc.SetEmailCodeService(emailCodeSvc) _, err := svc.LoginByEmailCode(ctx, email, "invalid", "127.0.0.1") if err == nil { t.Error("Expected error for invalid code") } }) } func TestAuthService_ActivateEmail(t *testing.T) { svc, db := setupAuthEmailTestEnv(t) ctx := context.Background() t.Run("Activate email without service", func(t *testing.T) { err := svc.ActivateEmail(ctx, "token") if err == nil { t.Error("Expected error when email activation service not configured") } }) t.Run("Activate email with invalid token", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) err := svc.ActivateEmail(ctx, "invalid_token") if err == nil { t.Error("Expected error for invalid token") } }) t.Run("Activate email for already active user", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) // Create inactive user and send activation email := "activate@test.com" user := &domain.User{ Username: "activateuser", Email: &email, Status: domain.UserStatusActive, } db.Create(user) // Manually store a token in cache cacheManager.Set(ctx, "email_activation:test_token_active", user.ID, 24*60*60*1000000000, 24*60*60*1000000000) err := svc.ActivateEmail(ctx, "test_token_active") if err == nil { t.Error("Expected error for already active user") } }) } func TestAuthService_ResendActivationEmail(t *testing.T) { svc, db := setupAuthEmailTestEnv(t) ctx := context.Background() t.Run("Resend activation without service", func(t *testing.T) { err := svc.ResendActivationEmail(ctx, "test@test.com") if err == nil { t.Error("Expected error when email activation service not configured") } }) t.Run("Resend activation for non-existent email", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) // Should return nil to avoid user enumeration err := svc.ResendActivationEmail(ctx, "nonexistent@test.com") if err != nil { t.Errorf("Expected nil for non-existent email, got: %v", err) } }) t.Run("Resend activation for active user", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) email := "resendactive@test.com" user := &domain.User{ Username: "resendactiveuser", Email: &email, Status: domain.UserStatusActive, } db.Create(user) // Should return nil for active user err := svc.ResendActivationEmail(ctx, email) if err != nil { t.Errorf("Expected nil for active user, got: %v", err) } }) t.Run("Resend activation for inactive user", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) provider := &service.MockEmailProvider{} emailActivationSvc := service.NewEmailActivationService(provider, cacheManager, "http://localhost:8080", "TestSite") svc.SetEmailActivationService(emailActivationSvc) email := "resendinactive@test.com" user := &domain.User{ Username: "resendinactiveuser", Email: &email, Status: domain.UserStatusInactive, } db.Create(user) err := svc.ResendActivationEmail(ctx, email) if err != nil { t.Fatalf("ResendActivationEmail failed: %v", err) } }) } func TestAuthService_RegisterWithActivation(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) ctx := context.Background() t.Run("Register with activation success", func(t *testing.T) { req := &service.RegisterRequest{ Username: "regactuser", Password: "Password123!", Email: "regact@test.com", } userInfo, err := svc.RegisterWithActivation(ctx, req) if err != nil { t.Fatalf("RegisterWithActivation failed: %v", err) } if userInfo == nil { t.Error("Expected user info") } }) t.Run("Register with weak password", func(t *testing.T) { req := &service.RegisterRequest{ Username: "weakpwduser", Password: "123", } _, err := svc.RegisterWithActivation(ctx, req) if err == nil { t.Error("Expected error for weak password") } }) t.Run("Register with duplicate username", func(t *testing.T) { req := &service.RegisterRequest{ Username: "regactuser", // Already exists Password: "Password123!", } _, err := svc.RegisterWithActivation(ctx, req) if err == nil { t.Error("Expected error for duplicate username") } }) } // ============================================================================= // Login By Email Code Extended Tests // ============================================================================= func TestAuthService_LoginByEmailCode_Extended(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) ctx := context.Background() t.Run("LoginByEmailCode without email code service", func(t *testing.T) { _, err := svc.LoginByEmailCode(ctx, "test@example.com", "code123", "127.0.0.1") if err == nil { t.Error("Expected error when email code service not configured") } }) t.Run("LoginByEmailCode with empty email", func(t *testing.T) { // Create a service with email code service l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) emailProvider := &service.MockEmailProvider{} emailCodeSvc := service.NewEmailCodeService(emailProvider, cacheManager, service.DefaultEmailCodeConfig()) svc.SetEmailCodeService(emailCodeSvc) _, err := svc.LoginByEmailCode(ctx, "", "code123", "127.0.0.1") if err == nil { t.Error("Expected error for empty email") } }) t.Run("LoginByEmailCode for non-existent user", func(t *testing.T) { l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) emailProvider := &service.MockEmailProvider{} emailCodeSvc := service.NewEmailCodeService(emailProvider, cacheManager, service.DefaultEmailCodeConfig()) svc.SetEmailCodeService(emailCodeSvc) // Store a valid code cacheManager.Set(ctx, fmt.Sprintf("email_code:login:%s", "nonexistent@test.com"), "123456", time.Minute*5, time.Minute*5) _, err := svc.LoginByEmailCode(ctx, "nonexistent@test.com", "123456", "127.0.0.1") if err == nil { t.Error("Expected error for non-existent user") } }) } // ============================================================================= // Register With Activation Extended Tests // ============================================================================= func TestAuthService_RegisterWithActivation_Extended(t *testing.T) { svc, _ := setupAuthEmailTestEnv(t) ctx := context.Background() t.Run("Register with duplicate email", func(t *testing.T) { // Create first user req1 := &service.RegisterRequest{ Username: "dupemailuser1", Password: "Password123!", Email: "dup@test.com", } svc.RegisterWithActivation(ctx, req1) // Try to register with same email req2 := &service.RegisterRequest{ Username: "dupemailuser2", Password: "Password123!", Email: "dup@test.com", } _, err := svc.RegisterWithActivation(ctx, req2) if err == nil { t.Error("Expected error for duplicate email") } }) t.Run("Register with phone", func(t *testing.T) { phone := "13800138000" req := &service.RegisterRequest{ Username: "phoneuser", Password: "Password123!", Phone: phone, } _, err := svc.RegisterWithActivation(ctx, req) // Phone registration requires SMS verification which is not configured if err == nil { t.Error("Expected error for phone registration without SMS service") } }) }