- 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)
335 lines
9.4 KiB
Go
335 lines
9.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestGetEnv(t *testing.T) {
|
|
// Test with default value when env not set
|
|
result := getEnv("NON_EXISTENT_ENV_VAR", "default")
|
|
if result != "default" {
|
|
t.Errorf("getEnv() = %s, want default", result)
|
|
}
|
|
|
|
// Test with env set
|
|
os.Setenv("TEST_ENV_VAR", "test_value")
|
|
defer os.Unsetenv("TEST_ENV_VAR")
|
|
|
|
result = getEnv("TEST_ENV_VAR", "default")
|
|
if result != "test_value" {
|
|
t.Errorf("getEnv() = %s, want test_value", result)
|
|
}
|
|
}
|
|
|
|
func TestGetEnvBool(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
envValue string
|
|
defaultValue bool
|
|
want bool
|
|
}{
|
|
{"default true, no env", "", true, true},
|
|
{"default false, no env", "", false, false},
|
|
{"env true", "true", false, true},
|
|
{"env TRUE", "TRUE", false, true},
|
|
{"env True", "True", false, true},
|
|
{"env 1", "1", false, true},
|
|
{"env false", "false", true, false},
|
|
{"env 0", "0", true, false},
|
|
{"env other", "random", true, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.envValue != "" {
|
|
os.Setenv("TEST_BOOL_ENV", tt.envValue)
|
|
defer os.Unsetenv("TEST_BOOL_ENV")
|
|
} else {
|
|
os.Unsetenv("TEST_BOOL_ENV")
|
|
}
|
|
|
|
result := getEnvBool("TEST_BOOL_ENV", tt.defaultValue)
|
|
if result != tt.want {
|
|
t.Errorf("getEnvBool() = %v, want %v", result, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadFromEnv(t *testing.T) {
|
|
// Set some env vars
|
|
os.Setenv("OAUTH_REDIRECT_BASE_URL", "https://example.com")
|
|
os.Setenv("OAUTH_CALLBACK_PATH", "/auth/callback")
|
|
os.Setenv("WECHAT_OAUTH_ENABLED", "true")
|
|
os.Setenv("WECHAT_APP_ID", "wechat-app-id")
|
|
os.Setenv("GOOGLE_OAUTH_ENABLED", "true")
|
|
os.Setenv("GOOGLE_CLIENT_ID", "google-client-id")
|
|
defer func() {
|
|
os.Unsetenv("OAUTH_REDIRECT_BASE_URL")
|
|
os.Unsetenv("OAUTH_CALLBACK_PATH")
|
|
os.Unsetenv("WECHAT_OAUTH_ENABLED")
|
|
os.Unsetenv("WECHAT_APP_ID")
|
|
os.Unsetenv("GOOGLE_OAUTH_ENABLED")
|
|
os.Unsetenv("GOOGLE_CLIENT_ID")
|
|
}()
|
|
|
|
config := loadFromEnv()
|
|
|
|
if config.Common.RedirectBaseURL != "https://example.com" {
|
|
t.Errorf("RedirectBaseURL = %s, want https://example.com", config.Common.RedirectBaseURL)
|
|
}
|
|
if config.Common.CallbackPath != "/auth/callback" {
|
|
t.Errorf("CallbackPath = %s, want /auth/callback", config.Common.CallbackPath)
|
|
}
|
|
if !config.WeChat.Enabled {
|
|
t.Error("WeChat.Enabled should be true")
|
|
}
|
|
if config.WeChat.AppID != "wechat-app-id" {
|
|
t.Errorf("WeChat.AppID = %s, want wechat-app-id", config.WeChat.AppID)
|
|
}
|
|
if !config.Google.Enabled {
|
|
t.Error("Google.Enabled should be true")
|
|
}
|
|
if config.Google.ClientID != "google-client-id" {
|
|
t.Errorf("Google.ClientID = %s, want google-client-id", config.Google.ClientID)
|
|
}
|
|
|
|
// Check default URLs
|
|
if config.WeChat.AuthURL != "https://open.weixin.qq.com/connect/qrconnect" {
|
|
t.Errorf("WeChat.AuthURL = %s", config.WeChat.AuthURL)
|
|
}
|
|
if config.Google.UserInfoURL != "https://www.googleapis.com/oauth2/v2/userinfo" {
|
|
t.Errorf("Google.UserInfoURL = %s", config.Google.UserInfoURL)
|
|
}
|
|
}
|
|
|
|
// resetOAuthConfig resets the oauth config singleton for testing
|
|
func resetOAuthConfig() {
|
|
oauthConfig = nil
|
|
oauthConfigOnce = sync.Once{}
|
|
}
|
|
|
|
func TestLoadOAuthConfig_FileNotExists(t *testing.T) {
|
|
// Reset the singleton for testing
|
|
resetOAuthConfig()
|
|
|
|
// Load from non-existent file - should fall back to env
|
|
config, _ := LoadOAuthConfig("/non/existent/path/config.yaml")
|
|
if config == nil {
|
|
t.Error("LoadOAuthConfig() should return config even when file doesn't exist")
|
|
}
|
|
}
|
|
|
|
func TestLoadOAuthConfig_InvalidYAML(t *testing.T) {
|
|
// Create temp file with invalid YAML
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "invalid_config.yaml")
|
|
if err := os.WriteFile(configPath, []byte("invalid: yaml: content: ["), 0644); err != nil {
|
|
t.Fatalf("Failed to write temp file: %v", err)
|
|
}
|
|
|
|
// Reset the singleton for testing
|
|
resetOAuthConfig()
|
|
|
|
config, err := LoadOAuthConfig(configPath)
|
|
if err == nil {
|
|
t.Error("LoadOAuthConfig() should return error for invalid YAML")
|
|
}
|
|
if config == nil {
|
|
t.Error("LoadOAuthConfig() should still return fallback config on error")
|
|
}
|
|
}
|
|
|
|
func TestLoadOAuthConfig_ValidYAML(t *testing.T) {
|
|
yamlContent := `
|
|
common:
|
|
redirect_base_url: "https://myapp.com"
|
|
callback_path: "/oauth/callback"
|
|
wechat:
|
|
enabled: true
|
|
app_id: "test-wechat-id"
|
|
app_secret: "test-secret"
|
|
scopes:
|
|
- snsapi_login
|
|
google:
|
|
enabled: true
|
|
client_id: "test-google-id"
|
|
client_secret: "test-secret"
|
|
scopes:
|
|
- openid
|
|
- email
|
|
facebook:
|
|
enabled: false
|
|
app_id: ""
|
|
app_secret: ""
|
|
qq:
|
|
enabled: true
|
|
app_id: "test-qq-id"
|
|
app_key: "test-qq-key"
|
|
weibo:
|
|
enabled: false
|
|
twitter:
|
|
enabled: false
|
|
`
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "oauth_config.yaml")
|
|
if err := os.WriteFile(configPath, []byte(yamlContent), 0644); err != nil {
|
|
t.Fatalf("Failed to write temp file: %v", err)
|
|
}
|
|
|
|
// Reset the singleton for testing
|
|
resetOAuthConfig()
|
|
|
|
config, err := LoadOAuthConfig(configPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadOAuthConfig() error = %v", err)
|
|
}
|
|
|
|
if config.Common.RedirectBaseURL != "https://myapp.com" {
|
|
t.Errorf("RedirectBaseURL = %s, want https://myapp.com", config.Common.RedirectBaseURL)
|
|
}
|
|
if !config.WeChat.Enabled {
|
|
t.Error("WeChat.Enabled should be true")
|
|
}
|
|
if config.WeChat.AppID != "test-wechat-id" {
|
|
t.Errorf("WeChat.AppID = %s, want test-wechat-id", config.WeChat.AppID)
|
|
}
|
|
if len(config.WeChat.Scopes) != 1 || config.WeChat.Scopes[0] != "snsapi_login" {
|
|
t.Errorf("WeChat.Scopes = %v, want [snsapi_login]", config.WeChat.Scopes)
|
|
}
|
|
if !config.Google.Enabled {
|
|
t.Error("Google.Enabled should be true")
|
|
}
|
|
if len(config.Google.Scopes) != 2 {
|
|
t.Errorf("Google.Scopes length = %d, want 2", len(config.Google.Scopes))
|
|
}
|
|
if config.Facebook.Enabled {
|
|
t.Error("Facebook.Enabled should be false")
|
|
}
|
|
if !config.QQ.Enabled {
|
|
t.Error("QQ.Enabled should be true")
|
|
}
|
|
}
|
|
|
|
func TestGetOAuthConfig(t *testing.T) {
|
|
// Reset the singleton
|
|
resetOAuthConfig()
|
|
|
|
// Set an env var to verify it's loaded
|
|
os.Setenv("OAUTH_REDIRECT_BASE_URL", "https://test-get-config.com")
|
|
defer os.Unsetenv("OAUTH_REDIRECT_BASE_URL")
|
|
|
|
config := GetOAuthConfig()
|
|
if config == nil {
|
|
t.Fatal("GetOAuthConfig() returned nil")
|
|
}
|
|
|
|
if config.Common.RedirectBaseURL != "https://test-get-config.com" {
|
|
t.Errorf("RedirectBaseURL = %s, want https://test-get-config.com", config.Common.RedirectBaseURL)
|
|
}
|
|
|
|
// Call again to test singleton behavior
|
|
config2 := GetOAuthConfig()
|
|
if config != config2 {
|
|
t.Error("GetOAuthConfig() should return same instance")
|
|
}
|
|
}
|
|
|
|
func TestLoadOAuthConfig_DefaultPath(t *testing.T) {
|
|
// Reset the singleton
|
|
resetOAuthConfig()
|
|
|
|
// Set env to verify fallback to env
|
|
os.Setenv("OAUTH_REDIRECT_BASE_URL", "https://default-path-test.com")
|
|
defer os.Unsetenv("OAUTH_REDIRECT_BASE_URL")
|
|
|
|
// Load with empty path - should use default path and fall back to env
|
|
config, _ := LoadOAuthConfig("")
|
|
|
|
if config.Common.RedirectBaseURL != "https://default-path-test.com" {
|
|
t.Errorf("RedirectBaseURL = %s, want https://default-path-test.com", config.Common.RedirectBaseURL)
|
|
}
|
|
}
|
|
|
|
func TestMiniProgramConfig(t *testing.T) {
|
|
yamlContent := `
|
|
wechat:
|
|
enabled: true
|
|
app_id: "test-app-id"
|
|
mini_program:
|
|
enabled: true
|
|
app_id: "mini-app-id"
|
|
app_secret: "mini-secret"
|
|
`
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "oauth_config.yaml")
|
|
if err := os.WriteFile(configPath, []byte(yamlContent), 0644); err != nil {
|
|
t.Fatalf("Failed to write temp file: %v", err)
|
|
}
|
|
|
|
// Reset the singleton for testing
|
|
resetOAuthConfig()
|
|
|
|
config, err := LoadOAuthConfig(configPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadOAuthConfig() error = %v", err)
|
|
}
|
|
|
|
if !config.WeChat.MiniProgram.Enabled {
|
|
t.Error("MiniProgram.Enabled should be true")
|
|
}
|
|
if config.WeChat.MiniProgram.AppID != "mini-app-id" {
|
|
t.Errorf("MiniProgram.AppID = %s, want mini-app-id", config.WeChat.MiniProgram.AppID)
|
|
}
|
|
}
|
|
|
|
func TestAllOAuthConfigs_HaveDefaultURLs(t *testing.T) {
|
|
// Clear all relevant env vars
|
|
envVars := []string{
|
|
"WECHAT_AUTH_URL", "WECHAT_TOKEN_URL", "WECHAT_USER_INFO_URL",
|
|
"GOOGLE_AUTH_URL", "GOOGLE_TOKEN_URL", "GOOGLE_USER_INFO_URL",
|
|
"FACEBOOK_AUTH_URL", "FACEBOOK_TOKEN_URL", "FACEBOOK_USER_INFO_URL",
|
|
"QQ_AUTH_URL", "QQ_TOKEN_URL", "QQ_OPENID_URL", "QQ_USER_INFO_URL",
|
|
"WEIBO_AUTH_URL", "WEIBO_TOKEN_URL", "WEIBO_USER_INFO_URL",
|
|
"TWITTER_AUTH_URL", "TWITTER_TOKEN_URL", "TWITTER_USER_INFO_URL",
|
|
}
|
|
for _, v := range envVars {
|
|
os.Unsetenv(v)
|
|
}
|
|
|
|
config := loadFromEnv()
|
|
|
|
// Verify WeChat defaults
|
|
if config.WeChat.AuthURL != "https://open.weixin.qq.com/connect/qrconnect" {
|
|
t.Errorf("WeChat.AuthURL default incorrect: %s", config.WeChat.AuthURL)
|
|
}
|
|
|
|
// Verify Google defaults
|
|
if config.Google.AuthURL != "https://accounts.google.com/o/oauth2/v2/auth" {
|
|
t.Errorf("Google.AuthURL default incorrect: %s", config.Google.AuthURL)
|
|
}
|
|
|
|
// Verify Facebook defaults
|
|
if config.Facebook.AuthURL != "https://www.facebook.com/v18.0/dialog/oauth" {
|
|
t.Errorf("Facebook.AuthURL default incorrect: %s", config.Facebook.AuthURL)
|
|
}
|
|
|
|
// Verify QQ defaults
|
|
if config.QQ.AuthURL != "https://graph.qq.com/oauth2.0/authorize" {
|
|
t.Errorf("QQ.AuthURL default incorrect: %s", config.QQ.AuthURL)
|
|
}
|
|
|
|
// Verify Weibo defaults
|
|
if config.Weibo.AuthURL != "https://api.weibo.com/oauth2/authorize" {
|
|
t.Errorf("Weibo.AuthURL default incorrect: %s", config.Weibo.AuthURL)
|
|
}
|
|
|
|
// Verify Twitter defaults
|
|
if config.Twitter.AuthURL != "https://twitter.com/i/oauth2/authorize" {
|
|
t.Errorf("Twitter.AuthURL default incorrect: %s", config.Twitter.AuthURL)
|
|
}
|
|
}
|