package service_test import ( "bytes" "context" "encoding/json" "io" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/user-management-system/internal/api/handler" "github.com/user-management-system/internal/api/middleware" "github.com/user-management-system/internal/api/router" "github.com/user-management-system/internal/auth" "github.com/user-management-system/internal/cache" "github.com/user-management-system/internal/config" "github.com/user-management-system/internal/repository" "github.com/user-management-system/internal/service" "github.com/user-management-system/internal/domain" gormsqlite "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" _ "modernc.org/sqlite" ) // doRequest makes an HTTP request with optional body func doRequest(method, url string, token string, body interface{}) (*http.Response, string) { var bodyReader io.Reader if body != nil { jsonBytes, _ := json.Marshal(body) bodyReader = bytes.NewReader(jsonBytes) } req, _ := http.NewRequest(method, url, bodyReader) if token != "" { req.Header.Set("Authorization", "Bearer "+token) } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(req) bodyBytes, _ := io.ReadAll(resp.Body) resp.Body.Close() return resp, string(bodyBytes) } func doPost(url, token string, body interface{}) (*http.Response, string) { return doRequest("POST", url, token, body) } func doGet(url, token string) (*http.Response, string) { return doRequest("GET", url, token, nil) } func setupSettingsTestServer(t *testing.T) (*httptest.Server, *service.SettingsService, string, func()) { gin.SetMode(gin.TestMode) // 使用内存 SQLite db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file::memory:?mode=memory&cache=shared", }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Skipf("skipping test (SQLite unavailable): %v", err) return nil, nil, "", func() {} } // 自动迁移 if err := db.AutoMigrate( &domain.User{}, &domain.Role{}, &domain.Permission{}, &domain.UserRole{}, &domain.RolePermission{}, &domain.Device{}, &domain.LoginLog{}, &domain.OperationLog{}, &domain.SocialAccount{}, &domain.Webhook{}, &domain.WebhookDelivery{}, ); err != nil { t.Fatalf("db migration failed: %v", err) } // 创建 JWT Manager jwtManager, err := auth.NewJWTWithOptions(auth.JWTOptions{ HS256Secret: "test-settings-secret-key", AccessTokenExpire: 15 * time.Minute, RefreshTokenExpire: 7 * 24 * time.Hour, }) if err != nil { t.Fatalf("create jwt manager failed: %v", err) } // 创建缓存 l1Cache := cache.NewL1Cache() l2Cache := cache.NewRedisCache(false) cacheManager := cache.NewCacheManager(l1Cache, l2Cache) // 创建 repositories userRepo := repository.NewUserRepository(db) roleRepo := repository.NewRoleRepository(db) permissionRepo := repository.NewPermissionRepository(db) userRoleRepo := repository.NewUserRoleRepository(db) rolePermissionRepo := repository.NewRolePermissionRepository(db) deviceRepo := repository.NewDeviceRepository(db) loginLogRepo := repository.NewLoginLogRepository(db) opLogRepo := repository.NewOperationLogRepository(db) passwordHistoryRepo := repository.NewPasswordHistoryRepository(db) // 创建 services authSvc := service.NewAuthService(userRepo, nil, jwtManager, cacheManager, 8, 5, 15*time.Minute) authSvc.SetRoleRepositories(userRoleRepo, roleRepo) userSvc := service.NewUserService(userRepo, userRoleRepo, roleRepo, passwordHistoryRepo) roleSvc := service.NewRoleService(roleRepo, rolePermissionRepo) permSvc := service.NewPermissionService(permissionRepo) deviceSvc := service.NewDeviceService(deviceRepo, userRepo) loginLogSvc := service.NewLoginLogService(loginLogRepo) opLogSvc := service.NewOperationLogService(opLogRepo) // 创建 SettingsService settingsService := service.NewSettingsService() // 创建 middleware rateLimitCfg := config.RateLimitConfig{} rateLimitMiddleware := middleware.NewRateLimitMiddleware(rateLimitCfg) authMiddleware := middleware.NewAuthMiddleware( jwtManager, userRepo, userRoleRepo, roleRepo, rolePermissionRepo, permissionRepo, l1Cache, ) authMiddleware.SetCacheManager(cacheManager) opLogMiddleware := middleware.NewOperationLogMiddleware(opLogRepo) // 创建 handlers authHandler := handler.NewAuthHandler(authSvc) userHandler := handler.NewUserHandler(userSvc) roleHandler := handler.NewRoleHandler(roleSvc) permHandler := handler.NewPermissionHandler(permSvc) deviceHandler := handler.NewDeviceHandler(deviceSvc) logHandler := handler.NewLogHandler(loginLogSvc, opLogSvc) settingsHandler := handler.NewSettingsHandler(settingsService) // 创建 router - 22个handler参数(含 metrics)+ variadic avatarHandler r := router.NewRouter( authHandler, userHandler, roleHandler, permHandler, deviceHandler, logHandler, authMiddleware, rateLimitMiddleware, opLogMiddleware, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, settingsHandler, nil, ) engine := r.Setup() server := httptest.NewServer(engine) // 注册用户用于测试 resp, _ := doPost(server.URL+"/api/v1/auth/register", "", map[string]interface{}{ "username": "admintestsu", "email": "admintestsu@test.com", "password": "Password123!", }) resp.Body.Close() // 获取 token loginResp, _ := doPost(server.URL+"/api/v1/auth/login", "", map[string]interface{}{ "account": "admintestsu", "password": "Password123!", }) var result map[string]interface{} json.NewDecoder(loginResp.Body).Decode(&result) loginResp.Body.Close() token := "" if data, ok := result["data"].(map[string]interface{}); ok { token, _ = data["access_token"].(string) } return server, settingsService, token, func() { server.Close() if sqlDB, _ := db.DB(); sqlDB != nil { sqlDB.Close() } } } // ============================================================================= // Settings API Tests // ============================================================================= func TestGetSettings_Success(t *testing.T) { // 仅测试 service 层,不测试 HTTP API svc := service.NewSettingsService() settings, err := svc.GetSettings(context.Background()) if err != nil { t.Fatalf("GetSettings failed: %v", err) } if settings.System.Name != "用户管理系统" { t.Errorf("expected system name '用户管理系统', got '%s'", settings.System.Name) } } func TestGetSettings_Unauthorized(t *testing.T) { server, _, _, cleanup := setupSettingsTestServer(t) defer cleanup() req, _ := http.NewRequest("GET", server.URL+"/api/v1/admin/settings", nil) // 不设置 Authorization header client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() // 无 token 应该返回 401 if resp.StatusCode != http.StatusUnauthorized { t.Errorf("expected status 401, got %d", resp.StatusCode) } } func TestGetSettings_ResponseStructure(t *testing.T) { // 仅测试 service 层数据结构 svc := service.NewSettingsService() settings, err := svc.GetSettings(context.Background()) if err != nil { t.Fatalf("GetSettings failed: %v", err) } // 验证 system 字段 if settings.System.Name == "" { t.Error("System.Name should not be empty") } if settings.System.Version == "" { t.Error("System.Version should not be empty") } if settings.System.Environment == "" { t.Error("System.Environment should not be empty") } // 验证 security 字段 if settings.Security.PasswordMinLength == 0 { t.Error("Security.PasswordMinLength should not be zero") } if !settings.Security.PasswordRequireUppercase { t.Error("Security.PasswordRequireUppercase should be true") } // 验证 features 字段 if !settings.Features.EmailVerification { t.Error("Features.EmailVerification should be true") } if len(settings.Features.OAuthProviders) == 0 { t.Error("Features.OAuthProviders should not be empty") } } // ============================================================================= // SettingsService Unit Tests // ============================================================================= func TestSettingsService_GetSettings(t *testing.T) { svc := service.NewSettingsService() settings, err := svc.GetSettings(context.Background()) if err != nil { t.Fatalf("GetSettings failed: %v", err) } // 验证 system if settings.System.Name == "" { t.Error("System.Name should not be empty") } if settings.System.Version == "" { t.Error("System.Version should not be empty") } // 验证 security defaults if settings.Security.PasswordMinLength != 8 { t.Errorf("PasswordMinLength: got %d, want 8", settings.Security.PasswordMinLength) } if !settings.Security.PasswordRequireUppercase { t.Error("PasswordRequireUppercase should be true") } if !settings.Security.PasswordRequireLowercase { t.Error("PasswordRequireLowercase should be true") } if !settings.Security.PasswordRequireNumbers { t.Error("PasswordRequireNumbers should be true") } if !settings.Security.PasswordRequireSymbols { t.Error("PasswordRequireSymbols should be true") } if settings.Security.PasswordHistory != 5 { t.Errorf("PasswordHistory: got %d, want 5", settings.Security.PasswordHistory) } // 验证 features defaults if !settings.Features.EmailVerification { t.Error("EmailVerification should be true") } if settings.Features.DataExportEnabled != true { t.Error("DataExportEnabled should be true") } }