test: add comprehensive test coverage and improve code quality
- 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)
This commit is contained in:
302
internal/service/auth_core_test.go
Normal file
302
internal/service/auth_core_test.go
Normal file
@@ -0,0 +1,302 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user