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) } }