mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
e64fc7f730
* Add redis support for distributed caching * Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * ... and another all nighter. * fixup! ... and another all nighter. * fixup! fixup! ... and another all nighter. * fixup! fixup! fixup! ... and another all nighter. * Resolve issue #85 by adding ability to set custom claims in JWT tokens * Remove redundant validation in auth middleware ( issue #89 ) * Add ability to set cookie prefix for session cookies ( #87 ) * fixup! Add ability to set cookie prefix for session cookies ( #87 ) * Add ability to set cookie max age - issue #91 * Potential fix for code scanning alert no. 10: Size computation for allocation may overflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fixup! Merge main into 0.8.0-redis: resolve conflicts --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1391 lines
35 KiB
Go
1391 lines
35 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Version Detection Tests
|
|
// =============================================================================
|
|
|
|
func TestConfigMigrator_DetectVersion_UnifiedJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
unifiedConfig := map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"issuerURL": "https://provider.example.com",
|
|
},
|
|
"session": map[string]interface{}{
|
|
"encryptionKey": "test-key",
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(unifiedConfig)
|
|
require.NoError(t, err)
|
|
|
|
version := migrator.DetectVersion(data)
|
|
assert.Equal(t, VersionUnified, version, "Should detect unified format with provider+session")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_UnifiedYAML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
yamlData := `
|
|
provider:
|
|
issuerURL: https://provider.example.com
|
|
session:
|
|
encryptionKey: test-key
|
|
`
|
|
|
|
version := migrator.DetectVersion([]byte(yamlData))
|
|
assert.Equal(t, VersionUnified, version, "Should detect unified format from YAML")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_LegacyLowercaseProviderUrl(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyConfig := map[string]interface{}{
|
|
"providerUrl": "https://provider.example.com",
|
|
"clientId": "test-client",
|
|
}
|
|
|
|
data, err := json.Marshal(legacyConfig)
|
|
require.NoError(t, err)
|
|
|
|
version := migrator.DetectVersion(data)
|
|
assert.Equal(t, VersionLegacy, version, "Should detect legacy format with providerUrl")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_LegacyCapitalizedProviderURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyConfig := map[string]interface{}{
|
|
"ProviderURL": "https://provider.example.com",
|
|
"ClientID": "test-client",
|
|
}
|
|
|
|
data, err := json.Marshal(legacyConfig)
|
|
require.NoError(t, err)
|
|
|
|
version := migrator.DetectVersion(data)
|
|
assert.Equal(t, VersionLegacy, version, "Should detect legacy format with ProviderURL")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_InvalidJSONDefaultsToLegacy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
invalidData := []byte("this is not valid JSON or YAML")
|
|
|
|
version := migrator.DetectVersion(invalidData)
|
|
assert.Equal(t, VersionLegacy, version, "Should default to legacy for invalid data")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_EmptyDataDefaultsToLegacy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
version := migrator.DetectVersion([]byte("{}"))
|
|
assert.Equal(t, VersionLegacy, version, "Should default to legacy for empty config")
|
|
}
|
|
|
|
func TestConfigMigrator_DetectVersion_ProviderWithoutSession(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
config := map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"issuerURL": "https://provider.example.com",
|
|
},
|
|
// Missing session field
|
|
}
|
|
|
|
data, err := json.Marshal(config)
|
|
require.NoError(t, err)
|
|
|
|
version := migrator.DetectVersion(data)
|
|
assert.Equal(t, VersionLegacy, version, "Should require both provider AND session for unified detection")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Migration Pipeline Tests
|
|
// =============================================================================
|
|
|
|
func TestConfigMigrator_Migrate_AlreadyUnifiedJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
unifiedConfig := map[string]interface{}{
|
|
"provider": map[string]interface{}{
|
|
"issuerURL": "https://provider.example.com",
|
|
"clientID": "test-client",
|
|
"redirectURL": "https://app.example.com/callback",
|
|
},
|
|
"session": map[string]interface{}{
|
|
"encryptionKey": "test-encryption-key",
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(unifiedConfig)
|
|
require.NoError(t, err)
|
|
|
|
config, warnings, err := migrator.Migrate(data)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.NotNil(t, warnings)
|
|
assert.Equal(t, "https://provider.example.com", config.Provider.IssuerURL)
|
|
assert.Equal(t, "test-client", config.Provider.ClientID)
|
|
}
|
|
|
|
func TestConfigMigrator_Migrate_AlreadyUnifiedYAML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
yamlData := `
|
|
provider:
|
|
issuerURL: https://provider.example.com
|
|
clientID: test-client
|
|
session:
|
|
encryptionKey: test-key
|
|
`
|
|
|
|
config, warnings, err := migrator.Migrate([]byte(yamlData))
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.NotNil(t, warnings)
|
|
assert.Equal(t, "https://provider.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestConfigMigrator_Migrate_LegacyToUnified(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyConfig := map[string]interface{}{
|
|
"providerUrl": "https://legacy-provider.com",
|
|
"clientId": "legacy-client",
|
|
"clientSecret": "legacy-secret",
|
|
"callbackUrl": "https://app.com/callback",
|
|
"sessionEncryptionKey": "legacy-encryption-key",
|
|
"forceHttps": true,
|
|
"enablePkce": true,
|
|
}
|
|
|
|
data, err := json.Marshal(legacyConfig)
|
|
require.NoError(t, err)
|
|
|
|
config, warnings, err := migrator.Migrate(data)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.NotNil(t, warnings)
|
|
|
|
// Verify migration worked
|
|
assert.Equal(t, "https://legacy-provider.com", config.Provider.IssuerURL)
|
|
assert.Equal(t, "legacy-client", config.Provider.ClientID)
|
|
assert.Equal(t, "legacy-secret", config.Provider.ClientSecret)
|
|
assert.Equal(t, "https://app.com/callback", config.Provider.RedirectURL)
|
|
assert.Equal(t, "legacy-encryption-key", config.Session.EncryptionKey)
|
|
assert.True(t, config.Security.ForceHTTPS)
|
|
assert.True(t, config.Security.EnablePKCE)
|
|
}
|
|
|
|
func TestConfigMigrator_Migrate_InvalidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
invalidData := []byte("{invalid json}")
|
|
|
|
config, warnings, err := migrator.Migrate(invalidData)
|
|
// Invalid JSON will be detected as legacy and migrated with default values
|
|
// This is expected behavior - migration is lenient
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.NotNil(t, warnings)
|
|
}
|
|
|
|
func TestConfigMigrator_Migrate_CollectsDeprecationWarnings(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
// Use a deprecated field that the compat layer would warn about
|
|
legacyConfig := map[string]interface{}{
|
|
"providerUrl": "https://provider.com",
|
|
"clientId": "test-client",
|
|
}
|
|
|
|
data, err := json.Marshal(legacyConfig)
|
|
require.NoError(t, err)
|
|
|
|
config, warnings, err := migrator.Migrate(data)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
// Warnings may or may not be present depending on compat layer config
|
|
assert.NotNil(t, warnings)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Provider Configuration
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_ProviderConfigFlat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"clientId": "test-client-123",
|
|
"clientSecret": "test-secret-456",
|
|
"callbackUrl": "https://app.example.com/callback",
|
|
"logoutUrl": "https://auth.example.com/logout",
|
|
"postLogoutRedirectUri": "https://app.example.com/logged-out",
|
|
"scopes": []interface{}{"openid", "profile", "email"},
|
|
"overrideScopes": true,
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
assert.Equal(t, "test-client-123", config.Provider.ClientID)
|
|
assert.Equal(t, "test-secret-456", config.Provider.ClientSecret)
|
|
assert.Equal(t, "https://app.example.com/callback", config.Provider.RedirectURL)
|
|
assert.Equal(t, "https://auth.example.com/logout", config.Provider.LogoutURL)
|
|
assert.Equal(t, "https://app.example.com/logged-out", config.Provider.PostLogoutRedirectURI)
|
|
assert.Equal(t, []string{"openid", "profile", "email"}, config.Provider.Scopes)
|
|
assert.True(t, config.Provider.OverrideScopes)
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_ProviderConfigCapitalized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"ProviderURL": "https://auth.example.com",
|
|
"ClientID": "test-client",
|
|
"ClientSecret": "test-secret",
|
|
"CallbackURL": "https://app.example.com/callback",
|
|
"LogoutURL": "https://auth.example.com/logout",
|
|
"PostLogoutRedirectURI": "https://app.example.com/logged-out",
|
|
"Scopes": []string{"openid", "profile"},
|
|
"OverrideScopes": false,
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
// Should handle capitalized field names
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
assert.Equal(t, "test-client", config.Provider.ClientID)
|
|
assert.Equal(t, "test-secret", config.Provider.ClientSecret)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Session Configuration
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_SessionConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"sessionEncryptionKey": "my-encryption-key-32-bytes-long",
|
|
"cookieDomain": ".example.com",
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "my-encryption-key-32-bytes-long", config.Session.EncryptionKey)
|
|
assert.Equal(t, ".example.com", config.Session.Domain)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Security Configuration
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_SecurityConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"forceHttps": true,
|
|
"enablePkce": true,
|
|
"allowedUsers": []interface{}{"user1@example.com", "user2@example.com"},
|
|
"allowedUserDomains": []interface{}{"example.com", "partner.com"},
|
|
"allowedRolesAndGroups": []interface{}{"admin", "developers"},
|
|
"excludedUrls": []interface{}{"/health", "/metrics"},
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, config.Security.ForceHTTPS)
|
|
assert.True(t, config.Security.EnablePKCE)
|
|
assert.Equal(t, []string{"user1@example.com", "user2@example.com"}, config.Security.AllowedUsers)
|
|
assert.Equal(t, []string{"example.com", "partner.com"}, config.Security.AllowedUserDomains)
|
|
assert.Equal(t, []string{"admin", "developers"}, config.Security.AllowedRolesAndGroups)
|
|
assert.Equal(t, []string{"/health", "/metrics"}, config.Security.ExcludedURLs)
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_SecurityConfigCapitalized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"ProviderURL": "https://auth.example.com",
|
|
"ForceHTTPS": false,
|
|
"EnablePKCE": false,
|
|
"AllowedUsers": []string{"admin@example.com"},
|
|
"AllowedUserDomains": []string{"example.com"},
|
|
"AllowedRolesAndGroups": []string{"admins"},
|
|
"ExcludedURLs": []string{"/public"},
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, config.Security.ForceHTTPS)
|
|
assert.False(t, config.Security.EnablePKCE)
|
|
assert.Equal(t, []string{"admin@example.com"}, config.Security.AllowedUsers)
|
|
assert.Equal(t, []string{"example.com"}, config.Security.AllowedUserDomains)
|
|
assert.Equal(t, []string{"admins"}, config.Security.AllowedRolesAndGroups)
|
|
assert.Equal(t, []string{"/public"}, config.Security.ExcludedURLs)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Rate Limiting
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_RateLimitEnabled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"rateLimit": 100,
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, config.RateLimit.Enabled)
|
|
assert.Equal(t, 100, config.RateLimit.RequestsPerSecond)
|
|
assert.Equal(t, 200, config.RateLimit.Burst) // Default: 2x rate
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_RateLimitDisabled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"rateLimit": 0, // Disabled
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, config.RateLimit.Enabled)
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_RateLimitCapitalized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"ProviderURL": "https://auth.example.com",
|
|
"RateLimit": 50,
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, config.RateLimit.Enabled)
|
|
assert.Equal(t, 50, config.RateLimit.RequestsPerSecond)
|
|
assert.Equal(t, 100, config.RateLimit.Burst)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Token Configuration
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_TokenRefreshGracePeriod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"refreshGracePeriodSeconds": 300, // 5 minutes
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 300*time.Second, config.Token.RefreshGracePeriod)
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_TokenRefreshGracePeriodCapitalized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"ProviderURL": "https://auth.example.com",
|
|
"RefreshGracePeriodSeconds": 600,
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 600*time.Second, config.Token.RefreshGracePeriod)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Logging
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_LoggingLevelLowercase(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"logLevel": "DEBUG",
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "debug", config.Logging.Level) // Should be lowercased
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_LoggingLevelDefaultsToInfo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
// No logLevel specified
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "info", config.Logging.Level) // Default
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_LoggingLevelCapitalized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"ProviderURL": "https://auth.example.com",
|
|
"LogLevel": "ERROR",
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "error", config.Logging.Level)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Custom Headers
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_CustomHeaders(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"headers": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "X-Custom-Header",
|
|
"value": "custom-value",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "X-Another-Header",
|
|
"value": "another-value",
|
|
},
|
|
},
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, config.Middleware.CustomHeaders)
|
|
assert.Equal(t, "custom-value", config.Middleware.CustomHeaders["X-Custom-Header"])
|
|
assert.Equal(t, "another-value", config.Middleware.CustomHeaders["X-Another-Header"])
|
|
}
|
|
|
|
func TestMigrateLegacyToUnified_CustomHeadersEmptyName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"headers": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "", // Empty name
|
|
"value": "should-be-ignored",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "X-Valid-Header",
|
|
"value": "valid-value",
|
|
},
|
|
},
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, config.Middleware.CustomHeaders)
|
|
assert.NotContains(t, config.Middleware.CustomHeaders, "") // Empty name should be skipped
|
|
assert.Equal(t, "valid-value", config.Middleware.CustomHeaders["X-Valid-Header"])
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy to Unified Mapping Tests - Legacy Data Preservation
|
|
// =============================================================================
|
|
|
|
func TestMigrateLegacyToUnified_PreservesLegacyData(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"clientId": "test-client",
|
|
"customField": "custom-value", // Non-standard field
|
|
}
|
|
|
|
config, err := migrator.migrateLegacyToUnified(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, config.Legacy)
|
|
assert.Equal(t, legacyData, config.Legacy) // Original data should be preserved
|
|
}
|
|
|
|
// =============================================================================
|
|
// File Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestConfigMigrator_MigrateFile_ValidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
// Create temporary JSON config file
|
|
tmpFile := filepath.Join(t.TempDir(), "config.json")
|
|
|
|
configData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"clientId": "test-client",
|
|
}
|
|
|
|
jsonData, err := json.Marshal(configData)
|
|
require.NoError(t, err)
|
|
|
|
err = os.WriteFile(tmpFile, jsonData, 0644)
|
|
require.NoError(t, err)
|
|
|
|
config, err := migrator.MigrateFile(tmpFile)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestConfigMigrator_MigrateFile_ValidYAML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
tmpFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
|
|
yamlData := `
|
|
providerUrl: https://auth.example.com
|
|
clientId: test-client
|
|
`
|
|
|
|
err := os.WriteFile(tmpFile, []byte(yamlData), 0644)
|
|
require.NoError(t, err)
|
|
|
|
config, err := migrator.MigrateFile(tmpFile)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestConfigMigrator_MigrateFile_PathTraversalPrevention(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
// Attempt path traversal
|
|
maliciousPath := "../../../etc/passwd"
|
|
|
|
config, err := migrator.MigrateFile(maliciousPath)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, config)
|
|
assert.Contains(t, err.Error(), "path traversal")
|
|
}
|
|
|
|
func TestConfigMigrator_MigrateFile_NonExistentFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
nonExistentFile := filepath.Join(t.TempDir(), "does-not-exist.json")
|
|
|
|
config, err := migrator.MigrateFile(nonExistentFile)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, config)
|
|
}
|
|
|
|
func TestConfigMigrator_MigrateFile_InvalidPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
migrator := NewConfigMigrator()
|
|
|
|
// Use invalid characters
|
|
invalidPath := string([]byte{0x00}) + "config.json"
|
|
|
|
config, err := migrator.MigrateFile(invalidPath)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, config)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Auto-Migration Tests
|
|
// =============================================================================
|
|
|
|
func TestAutoMigrate_ByteSliceInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This test depends on features.IsUnifiedConfigEnabled() being true
|
|
// Skip if unified config is not enabled
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"clientId": "test-client",
|
|
}
|
|
|
|
jsonData, err := json.Marshal(legacyData)
|
|
require.NoError(t, err)
|
|
|
|
config, err := AutoMigrate(jsonData)
|
|
|
|
// If feature is disabled, config will be nil with no error
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestAutoMigrate_StringInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
jsonString := `{"providerUrl":"https://auth.example.com","clientId":"test-client"}`
|
|
|
|
config, err := AutoMigrate(jsonString)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestAutoMigrate_MapInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
legacyData := map[string]interface{}{
|
|
"providerUrl": "https://auth.example.com",
|
|
"clientId": "test-client",
|
|
}
|
|
|
|
config, err := AutoMigrate(legacyData)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "https://auth.example.com", config.Provider.IssuerURL)
|
|
}
|
|
|
|
func TestAutoMigrate_OldConfigInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
oldConfig := &Config{
|
|
ProviderURL: "https://auth.example.com",
|
|
ClientID: "test-client",
|
|
}
|
|
|
|
config, err := AutoMigrate(oldConfig)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
// FromOldConfig should map fields
|
|
}
|
|
|
|
func TestAutoMigrate_UnifiedConfigInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
unifiedConfig := NewUnifiedConfig()
|
|
unifiedConfig.Provider.IssuerURL = "https://auth.example.com"
|
|
|
|
config, err := AutoMigrate(unifiedConfig)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, unifiedConfig, config) // Should return same instance
|
|
}
|
|
|
|
func TestAutoMigrate_UnsupportedType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
unsupportedData := 12345 // int type not supported
|
|
|
|
config, err := AutoMigrate(unsupportedData)
|
|
|
|
// If feature is disabled, both will be nil
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsupported config type")
|
|
}
|
|
|
|
// Test that AutoMigrate handles nil map input
|
|
func TestAutoMigrate_NilMap(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var nilMap map[string]interface{}
|
|
|
|
config, err := AutoMigrate(nilMap)
|
|
|
|
// Should handle nil gracefully
|
|
if config == nil && err == nil {
|
|
// Feature disabled OR nil handled correctly
|
|
t.Skip("Unified config feature not enabled or nil handled")
|
|
}
|
|
|
|
// If feature is enabled, should either succeed with empty config or error
|
|
// (depending on migration logic)
|
|
if err != nil {
|
|
assert.NotNil(t, err)
|
|
}
|
|
}
|
|
|
|
// Test AutoMigrate with empty byte slice
|
|
func TestAutoMigrate_EmptyByteSlice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
emptyData := []byte("")
|
|
|
|
config, err := AutoMigrate(emptyData)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should handle empty data - either error or return config
|
|
// (error expected for invalid JSON)
|
|
if err != nil {
|
|
assert.NotNil(t, err)
|
|
}
|
|
}
|
|
|
|
// Test AutoMigrate with empty string
|
|
func TestAutoMigrate_EmptyString(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
emptyString := ""
|
|
|
|
config, err := AutoMigrate(emptyString)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should handle empty string - error expected
|
|
if err != nil {
|
|
assert.NotNil(t, err)
|
|
}
|
|
}
|
|
|
|
// Test AutoMigrate with invalid JSON string
|
|
func TestAutoMigrate_InvalidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
invalidJSON := "{invalid json}"
|
|
|
|
config, err := AutoMigrate(invalidJSON)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should error on invalid JSON
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// Test AutoMigrate with invalid JSON bytes
|
|
func TestAutoMigrate_InvalidJSONBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
invalidJSON := []byte("{not valid json")
|
|
|
|
config, err := AutoMigrate(invalidJSON)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should error on invalid JSON
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// Test AutoMigrate with nil old config pointer
|
|
func TestAutoMigrate_NilOldConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var nilConfig *Config
|
|
|
|
config, err := AutoMigrate(nilConfig)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Nil config should be handled - might panic or return error
|
|
// depending on FromOldConfig implementation
|
|
if err != nil {
|
|
assert.NotNil(t, err)
|
|
}
|
|
}
|
|
|
|
// Test AutoMigrate with nil unified config pointer
|
|
func TestAutoMigrate_NilUnifiedConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var nilUnified *UnifiedConfig
|
|
|
|
config, err := AutoMigrate(nilUnified)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should return nil unified config as-is
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, config)
|
|
}
|
|
|
|
// Test AutoMigrate with map containing unmarshalable values
|
|
func TestAutoMigrate_MapWithUnmarshalableValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a map with a value that can't be marshaled to JSON
|
|
badMap := map[string]interface{}{
|
|
"providerUrl": "https://example.com",
|
|
"badValue": make(chan int), // channels can't be marshaled
|
|
}
|
|
|
|
config, err := AutoMigrate(badMap)
|
|
|
|
if config == nil && err == nil {
|
|
t.Skip("Unified config feature not enabled")
|
|
}
|
|
|
|
// Should error during JSON marshaling
|
|
assert.Error(t, err)
|
|
assert.Nil(t, config)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getNestedMap
|
|
// =============================================================================
|
|
|
|
func TestGetNestedMap_Exists(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"nested": map[string]interface{}{
|
|
"key": "value",
|
|
},
|
|
}
|
|
|
|
result, ok := getNestedMap(m, "nested")
|
|
assert.True(t, ok)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, "value", result["key"])
|
|
}
|
|
|
|
func TestGetNestedMap_DoesNotExist(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"other": "value",
|
|
}
|
|
|
|
result, ok := getNestedMap(m, "nested")
|
|
assert.False(t, ok)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestGetNestedMap_WrongType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"nested": "not-a-map",
|
|
}
|
|
|
|
result, ok := getNestedMap(m, "nested")
|
|
assert.False(t, ok)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getStringValue
|
|
// =============================================================================
|
|
|
|
func TestGetStringValue_FirstKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
|
|
result := getStringValue(m, "key1", "key2")
|
|
assert.Equal(t, "value1", result)
|
|
}
|
|
|
|
func TestGetStringValue_FallbackKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key2": "value2",
|
|
}
|
|
|
|
result := getStringValue(m, "key1", "key2", "key3")
|
|
assert.Equal(t, "value2", result) // Falls back to key2
|
|
}
|
|
|
|
func TestGetStringValue_NoKeysExist(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"other": "value",
|
|
}
|
|
|
|
result := getStringValue(m, "key1", "key2")
|
|
assert.Equal(t, "", result) // Returns empty string
|
|
}
|
|
|
|
func TestGetStringValue_NilValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": nil,
|
|
}
|
|
|
|
result := getStringValue(m, "key1")
|
|
assert.Equal(t, "", result)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getStringFromInterface
|
|
// =============================================================================
|
|
|
|
func TestGetStringFromInterface_String(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := getStringFromInterface("test-string")
|
|
assert.Equal(t, "test-string", result)
|
|
}
|
|
|
|
func TestGetStringFromInterface_ByteSlice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := getStringFromInterface([]byte("test-bytes"))
|
|
assert.Equal(t, "test-bytes", result)
|
|
}
|
|
|
|
func TestGetStringFromInterface_Int(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := getStringFromInterface(42)
|
|
assert.Equal(t, "42", result)
|
|
}
|
|
|
|
func TestGetStringFromInterface_Nil(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := getStringFromInterface(nil)
|
|
assert.Equal(t, "", result)
|
|
}
|
|
|
|
func TestGetStringFromInterface_Bool(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := getStringFromInterface(true)
|
|
assert.Equal(t, "true", result)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getBoolValue
|
|
// =============================================================================
|
|
|
|
func TestGetBoolValue_BoolTrue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": true,
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.True(t, result)
|
|
}
|
|
|
|
func TestGetBoolValue_BoolFalse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": false,
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.False(t, result)
|
|
}
|
|
|
|
func TestGetBoolValue_StringTrue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "true",
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.True(t, result)
|
|
}
|
|
|
|
func TestGetBoolValue_StringTrueUppercase(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "TRUE",
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.True(t, result)
|
|
}
|
|
|
|
func TestGetBoolValue_StringFalse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "false",
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.False(t, result)
|
|
}
|
|
|
|
func TestGetBoolValue_Missing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"other": "value",
|
|
}
|
|
|
|
result := getBoolValue(m, "key1")
|
|
assert.False(t, result) // Default
|
|
}
|
|
|
|
func TestGetBoolValue_Fallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key2": true,
|
|
}
|
|
|
|
result := getBoolValue(m, "key1", "key2")
|
|
assert.True(t, result) // Falls back to key2
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getIntValue
|
|
// =============================================================================
|
|
|
|
func TestGetIntValue_Int(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": 42,
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 42, result)
|
|
}
|
|
|
|
func TestGetIntValue_Int64(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": int64(100),
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 100, result)
|
|
}
|
|
|
|
func TestGetIntValue_Float64(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": 42.7,
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 42, result) // Truncates to int
|
|
}
|
|
|
|
func TestGetIntValue_String(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "123",
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 123, result)
|
|
}
|
|
|
|
func TestGetIntValue_InvalidString(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": "not-a-number",
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 0, result) // Returns 0 for invalid parse
|
|
}
|
|
|
|
func TestGetIntValue_Missing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"other": "value",
|
|
}
|
|
|
|
result := getIntValue(m, "key1")
|
|
assert.Equal(t, 0, result)
|
|
}
|
|
|
|
func TestGetIntValue_Fallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key2": 99,
|
|
}
|
|
|
|
result := getIntValue(m, "key1", "key2")
|
|
assert.Equal(t, 99, result)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - getArrayValue
|
|
// =============================================================================
|
|
|
|
func TestGetArrayValue_InterfaceSlice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": []interface{}{"value1", "value2", "value3"},
|
|
}
|
|
|
|
result := getArrayValue(m, "key1")
|
|
assert.Equal(t, []string{"value1", "value2", "value3"}, result)
|
|
}
|
|
|
|
func TestGetArrayValue_StringSlice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": []string{"a", "b", "c"},
|
|
}
|
|
|
|
result := getArrayValue(m, "key1")
|
|
assert.Equal(t, []string{"a", "b", "c"}, result)
|
|
}
|
|
|
|
func TestGetArrayValue_InterfaceSliceWithNumbers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": []interface{}{1, 2, 3},
|
|
}
|
|
|
|
result := getArrayValue(m, "key1")
|
|
assert.Equal(t, []string{"1", "2", "3"}, result) // Converted to strings
|
|
}
|
|
|
|
func TestGetArrayValue_Missing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"other": "value",
|
|
}
|
|
|
|
result := getArrayValue(m, "key1")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestGetArrayValue_Fallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key2": []string{"fallback1", "fallback2"},
|
|
}
|
|
|
|
result := getArrayValue(m, "key1", "key2")
|
|
assert.Equal(t, []string{"fallback1", "fallback2"}, result)
|
|
}
|
|
|
|
func TestGetArrayValue_Empty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := map[string]interface{}{
|
|
"key1": []interface{}{},
|
|
}
|
|
|
|
result := getArrayValue(m, "key1")
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, 0, len(result))
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Function Tests - mapToStruct
|
|
// =============================================================================
|
|
|
|
func TestMapToStruct_ValidMapping(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type TestStruct struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
m := map[string]interface{}{
|
|
"name": "John Doe",
|
|
"age": 30,
|
|
"email": "john@example.com",
|
|
}
|
|
|
|
var target TestStruct
|
|
err := mapToStruct(m, &target)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "John Doe", target.Name)
|
|
assert.Equal(t, 30, target.Age)
|
|
assert.Equal(t, "john@example.com", target.Email)
|
|
}
|
|
|
|
func TestMapToStruct_PartialMapping(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type TestStruct struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
m := map[string]interface{}{
|
|
"name": "Jane Doe",
|
|
// age and email missing
|
|
}
|
|
|
|
var target TestStruct
|
|
err := mapToStruct(m, &target)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Jane Doe", target.Name)
|
|
assert.Equal(t, 0, target.Age) // Zero value
|
|
assert.Equal(t, "", target.Email) // Zero value
|
|
}
|
|
|
|
func TestMapToStruct_InvalidJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type TestStruct struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// Create a struct that can't be marshaled to JSON (e.g., with a channel)
|
|
m := make(chan int)
|
|
|
|
var target TestStruct
|
|
err := mapToStruct(m, &target)
|
|
|
|
assert.Error(t, err) // Should fail to marshal
|
|
}
|