- 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)
303 lines
7.8 KiB
Go
303 lines
7.8 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 Core Methods Tests - Phase 1: Coverage to 35%
|
|
// =============================================================================
|
|
|
|
type authTestEnv struct {
|
|
db *gorm.DB
|
|
authSvc *service.AuthService
|
|
userSvc *service.UserService
|
|
}
|
|
|
|
func setupAuthTestEnv(t *testing.T) *authTestEnv {
|
|
t.Helper()
|
|
|
|
dsn := fmt.Sprintf("file:authtest_%s_%d?mode=memory&cache=shared", sanitizeTestName(t.Name()), 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.Skipf("skipping test (SQLite unavailable): %v", err)
|
|
return nil
|
|
}
|
|
|
|
db.Exec("PRAGMA journal_mode=WAL")
|
|
|
|
if err := db.AutoMigrate(
|
|
&domain.User{},
|
|
&domain.Role{},
|
|
&domain.UserRole{},
|
|
&domain.LoginLog{},
|
|
&domain.PasswordHistory{},
|
|
); err != nil {
|
|
t.Fatalf("db migration failed: %v", err)
|
|
}
|
|
|
|
// Seed roles
|
|
for _, role := range domain.PredefinedRoles {
|
|
if err := db.Create(&role).Error; err != nil {
|
|
t.Fatalf("seed role %s failed: %v", role.Code, err)
|
|
}
|
|
}
|
|
|
|
jwtManager, _ := auth.NewJWTWithOptions(auth.JWTOptions{
|
|
HS256Secret: fmt.Sprintf("test-secret-%d", time.Now().UnixNano()),
|
|
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)
|
|
passwordHistoryRepo := repository.NewPasswordHistoryRepository(db)
|
|
|
|
authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute)
|
|
authSvc.SetRoleRepositories(userRoleRepo, roleRepo)
|
|
userSvc := service.NewUserService(userRepo, userRoleRepo, roleRepo, passwordHistoryRepo)
|
|
|
|
t.Cleanup(func() {
|
|
if sqlDB, err := db.DB(); err == nil {
|
|
sqlDB.Close()
|
|
}
|
|
})
|
|
|
|
return &authTestEnv{
|
|
db: db,
|
|
authSvc: authSvc,
|
|
userSvc: userSvc,
|
|
}
|
|
}
|
|
|
|
// Test RefreshToken method
|
|
func TestAuthService_RefreshToken(t *testing.T) {
|
|
env := setupAuthTestEnv(t)
|
|
if env == nil {
|
|
return
|
|
}
|
|
ctx := context.Background()
|
|
|
|
// First register a user
|
|
req := &service.RegisterRequest{
|
|
Username: "refreshuser",
|
|
Password: "Test123!",
|
|
Email: "refresh@test.com",
|
|
}
|
|
authResp, err := env.authSvc.Register(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("Register failed: %v", err)
|
|
}
|
|
userID := authResp.ID
|
|
|
|
// Login to get refresh token
|
|
loginResp, err := env.authSvc.Login(ctx, &service.LoginRequest{
|
|
Username: "refreshuser",
|
|
Password: "Test123!",
|
|
}, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Login failed: %v", err)
|
|
}
|
|
refreshToken := loginResp.RefreshToken
|
|
|
|
t.Run("Refresh token success", func(t *testing.T) {
|
|
resp, err := env.authSvc.RefreshToken(ctx, refreshToken)
|
|
if err != nil {
|
|
t.Fatalf("RefreshToken failed: %v", err)
|
|
}
|
|
if resp.AccessToken == "" {
|
|
t.Error("Expected access token to be returned")
|
|
}
|
|
if resp.RefreshToken == "" {
|
|
t.Error("Expected refresh token to be returned")
|
|
}
|
|
})
|
|
|
|
t.Run("Refresh token with invalid token", func(t *testing.T) {
|
|
_, err := env.authSvc.RefreshToken(ctx, "invalid-token")
|
|
if err == nil {
|
|
t.Error("Expected error for invalid token")
|
|
}
|
|
})
|
|
|
|
t.Run("Refresh token with empty token", func(t *testing.T) {
|
|
_, err := env.authSvc.RefreshToken(ctx, "")
|
|
if err == nil {
|
|
t.Error("Expected error for empty token")
|
|
}
|
|
})
|
|
|
|
t.Run("Refresh token for locked user", func(t *testing.T) {
|
|
// Lock the user
|
|
env.userSvc.UpdateStatus(ctx, userID, domain.UserStatusLocked)
|
|
|
|
// Try to refresh token - should fail
|
|
_, err := env.authSvc.RefreshToken(ctx, refreshToken)
|
|
if err == nil {
|
|
t.Error("Expected error for locked user")
|
|
}
|
|
|
|
// Unlock user for cleanup
|
|
env.userSvc.UpdateStatus(ctx, userID, domain.UserStatusActive)
|
|
})
|
|
|
|
t.Run("Refresh token with nil service", func(t *testing.T) {
|
|
var nilSvc *service.AuthService
|
|
_, err := nilSvc.RefreshToken(ctx, refreshToken)
|
|
if err == nil {
|
|
t.Error("Expected error for nil service")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test GetUserInfo method
|
|
func TestAuthService_GetUserInfo(t *testing.T) {
|
|
env := setupAuthTestEnv(t)
|
|
if env == nil {
|
|
return
|
|
}
|
|
ctx := context.Background()
|
|
|
|
// Register a user
|
|
req := &service.RegisterRequest{
|
|
Username: "infouser",
|
|
Password: "Test123!",
|
|
Email: "info@test.com",
|
|
Nickname: "Info User",
|
|
}
|
|
authResp, err := env.authSvc.Register(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("Register failed: %v", err)
|
|
}
|
|
userID := authResp.ID
|
|
|
|
t.Run("Get user info success", func(t *testing.T) {
|
|
info, err := env.authSvc.GetUserInfo(ctx, userID)
|
|
if err != nil {
|
|
t.Fatalf("GetUserInfo failed: %v", err)
|
|
}
|
|
if info.ID != userID {
|
|
t.Errorf("Expected user ID %d, got %d", userID, info.ID)
|
|
}
|
|
if info.Username != "infouser" {
|
|
t.Errorf("Expected username 'infouser', got %s", info.Username)
|
|
}
|
|
if info.Nickname != "Info User" {
|
|
t.Errorf("Expected nickname 'Info User', got %s", info.Nickname)
|
|
}
|
|
if info.Email != "info@test.com" {
|
|
t.Errorf("Expected email 'info@test.com', got %s", info.Email)
|
|
}
|
|
})
|
|
|
|
t.Run("Get user info from cache", func(t *testing.T) {
|
|
// Second call should hit cache
|
|
info, err := env.authSvc.GetUserInfo(ctx, userID)
|
|
if err != nil {
|
|
t.Fatalf("GetUserInfo from cache failed: %v", err)
|
|
}
|
|
if info.ID != userID {
|
|
t.Errorf("Expected user ID %d, got %d", userID, info.ID)
|
|
}
|
|
})
|
|
|
|
t.Run("Get user info for non-existent user", func(t *testing.T) {
|
|
_, err := env.authSvc.GetUserInfo(ctx, 99999)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent user")
|
|
}
|
|
})
|
|
|
|
t.Run("Get user info with nil service", func(t *testing.T) {
|
|
var nilSvc *service.AuthService
|
|
_, err := nilSvc.GetUserInfo(ctx, userID)
|
|
if err == nil {
|
|
t.Error("Expected error for nil service")
|
|
}
|
|
})
|
|
|
|
t.Run("Get user info with zero ID", func(t *testing.T) {
|
|
_, err := env.authSvc.GetUserInfo(ctx, 0)
|
|
if err == nil {
|
|
t.Error("Expected error for zero user ID")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test Logout method
|
|
func TestAuthService_Logout(t *testing.T) {
|
|
env := setupAuthTestEnv(t)
|
|
if env == nil {
|
|
return
|
|
}
|
|
ctx := context.Background()
|
|
|
|
// Register and login a user
|
|
req := &service.RegisterRequest{
|
|
Username: "logoutuser",
|
|
Password: "Test123!",
|
|
}
|
|
_, err := env.authSvc.Register(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("Register failed: %v", err)
|
|
}
|
|
|
|
loginResp, err := env.authSvc.Login(ctx, &service.LoginRequest{
|
|
Username: "logoutuser",
|
|
Password: "Test123!",
|
|
}, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Login failed: %v", err)
|
|
}
|
|
|
|
t.Run("Logout success", func(t *testing.T) {
|
|
err := env.authSvc.Logout(ctx, "logoutuser", &service.LogoutRequest{
|
|
AccessToken: loginResp.AccessToken,
|
|
RefreshToken: loginResp.RefreshToken,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Logout failed: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Logout with nil request", func(t *testing.T) {
|
|
err := env.authSvc.Logout(ctx, "logoutuser", nil)
|
|
if err != nil {
|
|
t.Errorf("Logout with nil request should not error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Logout with nil service", func(t *testing.T) {
|
|
var nilSvc *service.AuthService
|
|
err := nilSvc.Logout(ctx, "logoutuser", &service.LogoutRequest{
|
|
AccessToken: loginResp.AccessToken,
|
|
RefreshToken: loginResp.RefreshToken,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Logout with nil service should not error: %v", err)
|
|
}
|
|
})
|
|
}
|