package config import ( "strings" "testing" ) // TestValidateProduction_AllValid tests that a fully configured production setup passes validation func TestValidateProduction_AllValid(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "this-is-a-very-secure-secret-key-with-more-than-32-characters", }, Database: DatabaseConfig{ Hosts: []string{"cassandra1.prod.example.com:9042"}, Keyspace: "maplefile_prod", Username: "admin", Password: "secure_password_123", }, S3: S3Config{ AccessKey: "AKIAIOSFODNN7EXAMPLE", SecretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", BucketName: "maplefile-production", Endpoint: "https://s3.amazonaws.com", }, Mailgun: MailgunConfig{ APIKey: "key-1234567890abcdef1234567890abcdef", Domain: "mg.example.com", SenderEmail: "noreply@example.com", }, Cache: CacheConfig{ Host: "redis.prod.example.com", }, Security: SecurityConfig{ AllowedOrigins: []string{"https://app.example.com"}, }, } err := cfg.ValidateProduction() if err != nil { t.Errorf("Expected valid production config to pass validation, got error: %v", err) } } // TestValidateProduction_MissingJWTSecret tests JWT secret validation func TestValidateProduction_MissingJWTSecret(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "", // Missing }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "test", }, S3: S3Config{ AccessKey: "test", SecretKey: "test", BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "test", Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, } err := cfg.ValidateProduction() if err == nil { t.Error("Expected error for missing JWT_SECRET in production") } if !strings.Contains(err.Error(), "JWT_SECRET is required") { t.Errorf("Expected JWT_SECRET error, got: %v", err) } } // TestValidateProduction_ShortJWTSecret tests JWT secret length validation func TestValidateProduction_ShortJWTSecret(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "short", // Too short (less than 32 chars) }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "test", }, S3: S3Config{ AccessKey: "test", SecretKey: "test", BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "test", Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, } err := cfg.ValidateProduction() if err == nil { t.Error("Expected error for short JWT_SECRET in production") } if !strings.Contains(err.Error(), "at least 32 characters") { t.Errorf("Expected JWT_SECRET length error, got: %v", err) } } // TestValidateProduction_MissingS3Credentials tests S3 credential validation func TestValidateProduction_MissingS3Credentials(t *testing.T) { tests := []struct { name string accessKey string secretKey string wantError string }{ { name: "missing access key", accessKey: "", secretKey: "valid-secret", wantError: "S3_ACCESS_KEY is required", }, { name: "missing secret key", accessKey: "valid-access", secretKey: "", wantError: "S3_SECRET_KEY is required", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "this-is-a-very-secure-secret-key-with-more-than-32-characters", }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "test", }, S3: S3Config{ AccessKey: tt.accessKey, SecretKey: tt.secretKey, BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "test", Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, } err := cfg.ValidateProduction() if err == nil { t.Errorf("Expected error for %s in production", tt.name) } if !strings.Contains(err.Error(), tt.wantError) { t.Errorf("Expected error containing '%s', got: %v", tt.wantError, err) } }) } } // TestValidateProduction_MissingMailgunCredentials tests email service validation func TestValidateProduction_MissingMailgunCredentials(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "this-is-a-very-secure-secret-key-with-more-than-32-characters", }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "test", }, S3: S3Config{ AccessKey: "test", SecretKey: "test", BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "", // Missing Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, } err := cfg.ValidateProduction() if err == nil { t.Error("Expected error for missing MAILGUN_API_KEY in production") } if !strings.Contains(err.Error(), "MAILGUN_API_KEY is required") { t.Errorf("Expected MAILGUN_API_KEY error, got: %v", err) } } // TestValidateProduction_MissingDatabaseConfig tests database configuration validation func TestValidateProduction_MissingDatabaseConfig(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "this-is-a-very-secure-secret-key-with-more-than-32-characters", }, Database: DatabaseConfig{ Hosts: []string{}, // Missing Keyspace: "", // Missing }, S3: S3Config{ AccessKey: "test", SecretKey: "test", BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "test", Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, } err := cfg.ValidateProduction() if err == nil { t.Error("Expected error for missing database configuration in production") } if !strings.Contains(err.Error(), "DATABASE_HOSTS is required") { t.Errorf("Expected DATABASE_HOSTS error, got: %v", err) } } // TestValidateProduction_UnsafeOrigins tests CORS wildcard detection func TestValidateProduction_UnsafeOrigins(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "this-is-a-very-secure-secret-key-with-more-than-32-characters", }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "test", }, S3: S3Config{ AccessKey: "test", SecretKey: "test", BucketName: "test", Endpoint: "http://localhost:9000", }, Mailgun: MailgunConfig{ APIKey: "test", Domain: "test.com", SenderEmail: "test@test.com", }, Cache: CacheConfig{ Host: "localhost", }, Security: SecurityConfig{ AllowedOrigins: []string{"*"}, // Unsafe wildcard }, } err := cfg.ValidateProduction() if err == nil { t.Error("Expected error for wildcard CORS origin in production") } if !strings.Contains(err.Error(), "SECURITY_ALLOWED_ORIGINS='*'") { t.Errorf("Expected CORS wildcard warning, got: %v", err) } } // TestValidateProduction_MultipleErrors tests that all validation errors are collected func TestValidateProduction_MultipleErrors(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "", // Missing }, Database: DatabaseConfig{ Hosts: []string{}, // Missing Keyspace: "", // Missing }, S3: S3Config{ AccessKey: "", // Missing SecretKey: "", // Missing BucketName: "", Endpoint: "", }, Mailgun: MailgunConfig{ APIKey: "", // Missing Domain: "", SenderEmail: "", }, Cache: CacheConfig{ Host: "", }, } err := cfg.ValidateProduction() if err == nil { t.Fatal("Expected multiple validation errors") } errorMsg := err.Error() expectedErrors := []string{ "JWT_SECRET is required", "DATABASE_HOSTS is required", "DATABASE_KEYSPACE is required", "S3_ACCESS_KEY is required", "S3_SECRET_KEY is required", "S3_BUCKET is required", "S3_ENDPOINT is required", "MAILGUN_API_KEY is required", "MAILGUN_DOMAIN is required", "CACHE_HOST is required", } for _, expected := range expectedErrors { if !strings.Contains(errorMsg, expected) { t.Errorf("Expected error message to contain '%s', got: %v", expected, errorMsg) } } } // TestValidate_Development tests that development environments use basic validation func TestValidate_Development(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "development", }, JWT: JWTConfig{ Secret: "dev-secret", // Short secret OK in development }, Database: DatabaseConfig{ Hosts: []string{"localhost:9042"}, Keyspace: "maplefile_dev", }, S3: S3Config{ AccessKey: "", // OK in development SecretKey: "", // OK in development BucketName: "test", }, } // Should not fail with lenient development validation err := cfg.Validate() if err != nil { t.Errorf("Development environment should not require strict validation, got: %v", err) } } // TestValidate_ProductionCallsValidateProduction tests integration func TestValidate_ProductionCallsValidateProduction(t *testing.T) { cfg := &Config{ App: AppConfig{ Environment: "production", }, JWT: JWTConfig{ Secret: "", // This should trigger production validation }, } err := cfg.Validate() if err == nil { t.Error("Expected production Validate() to call ValidateProduction() and fail") } if !strings.Contains(err.Error(), "JWT_SECRET is required") { t.Errorf("Expected ValidateProduction error, got: %v", err) } }