package config import ( "os" "testing" "time" ) func TestLoadDefaults(t *testing.T) { // Clear any environment variables that might interfere envVars := []string{ "APP_NAME", "BACKEND_PORT", "DATABASE_URL", "MERCADOPAGO_BASE_URL", "MARKETPLACE_COMMISSION", "JWT_SECRET", "JWT_EXPIRES_IN", "PASSWORD_PEPPER", "CORS_ORIGINS", } origEnvs := make(map[string]string) for _, key := range envVars { origEnvs[key] = os.Getenv(key) os.Unsetenv(key) } defer func() { for key, val := range origEnvs { if val != "" { os.Setenv(key, val) } } }() cfg, err := Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if cfg.AppName != "saveinmed-performance-core" { t.Errorf("expected AppName 'saveinmed-performance-core', got '%s'", cfg.AppName) } if cfg.Port != "8214" { t.Errorf("expected Port '8214', got '%s'", cfg.Port) } if cfg.JWTSecret != "dev-secret" { t.Errorf("expected JWTSecret 'dev-secret', got '%s'", cfg.JWTSecret) } if cfg.JWTExpiresIn != 24*time.Hour { t.Errorf("expected JWTExpiresIn 24h, got %v", cfg.JWTExpiresIn) } if cfg.MarketplaceCommission != 2.5 { t.Errorf("expected MarketplaceCommission 2.5, got %f", cfg.MarketplaceCommission) } if len(cfg.CORSOrigins) != 1 || cfg.CORSOrigins[0] != "*" { t.Errorf("expected CORSOrigins ['*'], got %v", cfg.CORSOrigins) } } func TestLoadFromEnv(t *testing.T) { os.Setenv("APP_NAME", "test-app") os.Setenv("BACKEND_PORT", "9999") os.Setenv("DATABASE_URL", "postgres://test:test@localhost:5432/test") os.Setenv("MARKETPLACE_COMMISSION", "5.0") os.Setenv("BUYER_FEE_RATE", "0.2") os.Setenv("JWT_SECRET", "super-secret") os.Setenv("JWT_EXPIRES_IN", "12h") os.Setenv("PASSWORD_PEPPER", "pepper123") os.Setenv("CORS_ORIGINS", "https://example.com,https://app.example.com") os.Setenv("BACKEND_HOST", "api.test.local") os.Setenv("SWAGGER_SCHEMES", "https, http") defer func() { os.Unsetenv("APP_NAME") os.Unsetenv("BACKEND_PORT") os.Unsetenv("DATABASE_URL") os.Unsetenv("MARKETPLACE_COMMISSION") os.Unsetenv("BUYER_FEE_RATE") os.Unsetenv("JWT_SECRET") os.Unsetenv("JWT_EXPIRES_IN") os.Unsetenv("PASSWORD_PEPPER") os.Unsetenv("CORS_ORIGINS") os.Unsetenv("BACKEND_HOST") os.Unsetenv("SWAGGER_SCHEMES") }() cfg, err := Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if cfg.AppName != "test-app" { t.Errorf("expected AppName 'test-app', got '%s'", cfg.AppName) } if cfg.Port != "9999" { t.Errorf("expected Port '9999', got '%s'", cfg.Port) } if cfg.DatabaseURL != "postgres://test:test@localhost:5432/test" { t.Errorf("expected custom DatabaseURL, got '%s'", cfg.DatabaseURL) } if cfg.MarketplaceCommission != 5.0 { t.Errorf("expected MarketplaceCommission 5.0, got %f", cfg.MarketplaceCommission) } if cfg.BuyerFeeRate != 0.2 { t.Errorf("expected BuyerFeeRate 0.2, got %f", cfg.BuyerFeeRate) } if cfg.JWTSecret != "super-secret" { t.Errorf("expected JWTSecret 'super-secret', got '%s'", cfg.JWTSecret) } if cfg.JWTExpiresIn != 12*time.Hour { t.Errorf("expected JWTExpiresIn 12h, got %v", cfg.JWTExpiresIn) } if cfg.PasswordPepper != "pepper123" { t.Errorf("expected PasswordPepper 'pepper123', got '%s'", cfg.PasswordPepper) } if len(cfg.CORSOrigins) != 2 { t.Errorf("expected 2 CORS origins, got %d", len(cfg.CORSOrigins)) } if cfg.BackendHost != "api.test.local" { t.Errorf("expected BackendHost 'api.test.local', got '%s'", cfg.BackendHost) } if len(cfg.SwaggerSchemes) != 2 || cfg.SwaggerSchemes[0] != "https" || cfg.SwaggerSchemes[1] != "http" { t.Errorf("expected SwaggerSchemes [https http], got %v", cfg.SwaggerSchemes) } } func TestAddr(t *testing.T) { cfg := Config{Port: "3000"} expected := ":3000" if cfg.Addr() != expected { t.Errorf("expected Addr '%s', got '%s'", expected, cfg.Addr()) } } func TestInvalidEnvValues(t *testing.T) { os.Setenv("MARKETPLACE_COMMISSION", "invalid") os.Setenv("JWT_EXPIRES_IN", "not-a-duration") os.Setenv("BUYER_FEE_RATE", "invalid-rate") defer func() { os.Unsetenv("MARKETPLACE_COMMISSION") os.Unsetenv("JWT_EXPIRES_IN") os.Unsetenv("BUYER_FEE_RATE") }() cfg, err := Load() if err != nil { t.Fatalf("failed to load config: %v", err) } // Should use defaults when values are invalid if cfg.MarketplaceCommission != 2.5 { t.Errorf("expected fallback MarketplaceCommission 2.5, got %f", cfg.MarketplaceCommission) } if cfg.JWTExpiresIn != 24*time.Hour { t.Errorf("expected fallback JWTExpiresIn 24h, got %v", cfg.JWTExpiresIn) } if cfg.BuyerFeeRate != 0.12 { t.Errorf("expected fallback BuyerFeeRate 0.12, got %f", cfg.BuyerFeeRate) } } func TestEmptyCORSOrigins(t *testing.T) { os.Setenv("CORS_ORIGINS", "") defer os.Unsetenv("CORS_ORIGINS") cfg, err := Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.CORSOrigins) != 1 || cfg.CORSOrigins[0] != "*" { t.Errorf("expected fallback CORSOrigins ['*'], got %v", cfg.CORSOrigins) } } func TestSwaggerSchemesTrimmed(t *testing.T) { os.Setenv("SWAGGER_SCHEMES", " https , ,http,") defer os.Unsetenv("SWAGGER_SCHEMES") cfg, err := Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.SwaggerSchemes) != 2 || cfg.SwaggerSchemes[0] != "https" || cfg.SwaggerSchemes[1] != "http" { t.Errorf("expected SwaggerSchemes [https http], got %v", cfg.SwaggerSchemes) } } func TestGetEnv(t *testing.T) { t.Setenv("TEST_CONFIG_STRING", "value") if got := getEnv("TEST_CONFIG_STRING", "fallback"); got != "value" { t.Errorf("expected getEnv to return value, got %q", got) } if got := getEnv("MISSING_CONFIG_STRING", "fallback"); got != "fallback" { t.Errorf("expected getEnv to return fallback, got %q", got) } } func TestGetEnvInt(t *testing.T) { tests := []struct { name string value string fallback int expected int }{ {name: "valid integer", value: "42", fallback: 10, expected: 42}, {name: "invalid integer", value: "not-a-number", fallback: 10, expected: 10}, {name: "empty value", value: "", fallback: 7, expected: 7}, {name: "negative integer", value: "-5", fallback: 3, expected: -5}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("TEST_CONFIG_INT", tt.value) if got := getEnvInt("TEST_CONFIG_INT", tt.fallback); got != tt.expected { t.Errorf("expected %d, got %d", tt.expected, got) } }) } } func TestGetEnvDuration(t *testing.T) { tests := []struct { name string value string fallback time.Duration expected time.Duration }{ {name: "valid duration", value: "45m", fallback: time.Hour, expected: 45 * time.Minute}, {name: "invalid duration", value: "not-a-duration", fallback: time.Minute, expected: time.Minute}, {name: "empty value", value: "", fallback: 30 * time.Second, expected: 30 * time.Second}, {name: "complex duration", value: "1h30m", fallback: time.Minute, expected: 90 * time.Minute}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("TEST_CONFIG_DURATION", tt.value) if got := getEnvDuration("TEST_CONFIG_DURATION", tt.fallback); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestGetEnvFloat(t *testing.T) { tests := []struct { name string value string fallback float64 expected float64 }{ {name: "valid float", value: "3.14", fallback: 1.2, expected: 3.14}, {name: "valid integer string", value: "2", fallback: 1.2, expected: 2}, {name: "invalid float", value: "invalid", fallback: 1.2, expected: 1.2}, {name: "empty value", value: "", fallback: 2.5, expected: 2.5}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("TEST_CONFIG_FLOAT", tt.value) if got := getEnvFloat("TEST_CONFIG_FLOAT", tt.fallback); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestGetEnvStringSlice(t *testing.T) { tests := []struct { name string value string fallback []string expected []string }{ {name: "comma separated", value: "a,b,c", fallback: []string{"fallback"}, expected: []string{"a", "b", "c"}}, {name: "single value", value: "solo", fallback: []string{"fallback"}, expected: []string{"solo"}}, {name: "trim spaces", value: " a , b ", fallback: []string{"fallback"}, expected: []string{"a", "b"}}, {name: "empty entries", value: "a,,b,", fallback: []string{"fallback"}, expected: []string{"a", "b"}}, {name: "trailing spaces", value: "one ,two ", fallback: []string{"fallback"}, expected: []string{"one", "two"}}, {name: "only separators", value: " , , ", fallback: []string{"fallback"}, expected: []string{"fallback"}}, {name: "empty value", value: "", fallback: []string{"fallback"}, expected: []string{"fallback"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("TEST_CONFIG_SLICE", tt.value) got := getEnvStringSlice("TEST_CONFIG_SLICE", tt.fallback) if len(got) != len(tt.expected) { t.Fatalf("expected %v, got %v", tt.expected, got) } for i, expected := range tt.expected { if got[i] != expected { t.Fatalf("expected %v, got %v", tt.expected, got) } } }) } }