package service_test import ( "context" "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 Capabilities Tests - Phase 1 // ============================================================================= func setupCapabilitiesTestEnv(t *testing.T) (*service.AuthService, *gorm.DB) { t.Helper() dsn := "file:cap_test?mode=memory&cache=shared" 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) } // Seed roles db.Create(&domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled}) db.Create(&domain.Role{Code: "user", Name: "用户", Status: domain.RoleStatusEnabled}) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) return authSvc, db } func TestAuthCapabilities_SimpleMethods(t *testing.T) { svc, _ := setupCapabilitiesTestEnv(t) ctx := context.Background() t.Run("SupportsEmailActivation", func(t *testing.T) { if svc.SupportsEmailActivation() { t.Error("Should not support email activation without config") } }) t.Run("SupportsEmailCodeLogin", func(t *testing.T) { if svc.SupportsEmailCodeLogin() { t.Error("Should not support email code login without config") } }) t.Run("SupportsSMSCodeLogin", func(t *testing.T) { if svc.SupportsSMSCodeLogin() { t.Error("Should not support SMS code login without config") } }) t.Run("GetAuthCapabilities", func(t *testing.T) { caps := svc.GetAuthCapabilities(ctx) if !caps.Password { t.Error("Password should always be true") } }) t.Run("GetAuthCapabilities with nil ctx", func(t *testing.T) { caps := svc.GetAuthCapabilities(nil) if !caps.Password { t.Error("Password should always be true") } }) t.Run("IsAdminBootstrapRequired with nil ctx", func(t *testing.T) { // 测试nil ctx不会panic _ = svc.IsAdminBootstrapRequired(nil) }) t.Run("nil service methods", func(t *testing.T) { var nilSvc *service.AuthService if nilSvc.SupportsEmailActivation() { t.Error("nil service should return false") } if nilSvc.SupportsEmailCodeLogin() { t.Error("nil service should return false") } if nilSvc.SupportsSMSCodeLogin() { t.Error("nil service should return false") } if nilSvc.IsAdminBootstrapRequired(ctx) { t.Error("nil service should return false") } }) } func TestAuthCapabilities_IsAdminBootstrapRequired(t *testing.T) { svc, _ := setupCapabilitiesTestEnv(t) ctx := context.Background() t.Run("Admin bootstrap required when no admin", func(t *testing.T) { required := svc.IsAdminBootstrapRequired(ctx) // Should be true since no admin user exists if !required { t.Log("Admin bootstrap should be required when no admin exists") } }) } // Test nil service behavior func TestAuthService_NilBehavior(t *testing.T) { ctx := context.Background() var nilSvc *service.AuthService t.Run("nil service RefreshToken", func(t *testing.T) { _, err := nilSvc.RefreshToken(ctx, "token") if err == nil { t.Error("nil service should return error") } }) t.Run("nil service GetUserInfo", func(t *testing.T) { _, err := nilSvc.GetUserInfo(ctx, 1) if err == nil { t.Error("nil service should return error") } }) t.Run("nil service Logout", func(t *testing.T) { err := nilSvc.Logout(ctx, "user", nil) if err != nil { t.Errorf("nil service Logout should not error: %v", err) } }) t.Run("nil service IsTokenBlacklisted", func(t *testing.T) { blacklisted := nilSvc.IsTokenBlacklisted(ctx, "jti") if blacklisted { t.Error("nil service should return false") } }) t.Run("nil service GetAuthCapabilities", func(t *testing.T) { caps := nilSvc.GetAuthCapabilities(ctx) // nil service returns empty capabilities, Password is false _ = caps t.Logf("nil service GetAuthCapabilities: %+v", caps) }) t.Run("nil service RefreshTokenTTLSeconds", func(t *testing.T) { ttl := nilSvc.RefreshTokenTTLSeconds() if ttl != 0 { t.Errorf("nil service should return 0, got %d", ttl) } }) } // ============================================================================= // IsAdminBootstrapRequired Tests // ============================================================================= func TestAuthService_IsAdminBootstrapRequired(t *testing.T) { t.Run("nil service returns false", func(t *testing.T) { var nilSvc *service.AuthService result := nilSvc.IsAdminBootstrapRequired(context.Background()) if result { t.Error("nil service should return false") } }) t.Run("service without role repo returns false", func(t *testing.T) { svc := &service.AuthService{} result := svc.IsAdminBootstrapRequired(context.Background()) if result { t.Error("service without role repo should return false") } }) } // ============================================================================= // IsAdminBootstrapRequired Extended Tests // ============================================================================= func TestAuthService_IsAdminBootstrapRequired_Extended(t *testing.T) { t.Run("returns true when admin role not found", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_no_role?mode=memory&cache=shared", }), &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) } // Do NOT create admin role jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if !result { t.Error("Should return true when admin role not found") } }) t.Run("returns true when admin role exists but no users assigned", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_no_users?mode=memory&cache=shared", }), &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 admin role but no users db.Create(&domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled}) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if !result { t.Error("Should return true when no admin users assigned") } }) t.Run("returns false when active admin user exists", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_active_admin?mode=memory&cache=shared", }), &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 admin role adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled} db.Create(adminRole) // Create active admin user adminUser := &domain.User{ Username: "admin", Password: "hashed", Status: domain.UserStatusActive, } db.Create(adminUser) // Assign admin role db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID}) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if result { t.Error("Should return false when active admin user exists") } }) t.Run("returns true when admin user is not active", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_inactive_admin?mode=memory&cache=shared", }), &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 admin role adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled} db.Create(adminRole) // Create inactive admin user adminUser := &domain.User{ Username: "admin", Password: "hashed", Status: domain.UserStatusInactive, } db.Create(adminUser) // Assign admin role db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID}) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if !result { t.Error("Should return true when admin user is not active") } }) t.Run("returns true when admin user is locked", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_locked_admin?mode=memory&cache=shared", }), &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 admin role adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusEnabled} db.Create(adminRole) // Create locked admin user adminUser := &domain.User{ Username: "admin", Password: "hashed", Status: domain.UserStatusLocked, } db.Create(adminUser) // Assign admin role db.Create(&domain.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID}) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if !result { t.Error("Should return true when admin user is locked") } }) t.Run("returns true when admin role is disabled", func(t *testing.T) { db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:cap_test_disabled_role?mode=memory&cache=shared", }), &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 disabled admin role adminRole := &domain.Role{Code: "admin", Name: "管理员", Status: domain.RoleStatusDisabled} db.Create(adminRole) jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-secret", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) result := authSvc.IsAdminBootstrapRequired(context.Background()) if !result { t.Error("Should return true when admin role is disabled") } }) }