- Add new test files for auth, service, and handler modules - Improve test organization and coverage - Refactor code for better maintainability - Add captcha, settings, stats, and theme handler tests - Add auth module tests (CAS, OAuth, password, SSO, state) - Add service layer tests for auth, export, permissions, roles - All Go tests pass (exit code 0) - All frontend tests pass (325 tests in 59 files)
469 lines
15 KiB
Go
469 lines
15 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|