package config import ( "testing" "time" ) // TestConfig_Validate tests configuration validation rules func TestConfig_Validate(t *testing.T) { tests := []struct { name string config *Config wantErr bool errMsg string }{ { name: "valid config", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, DB: 0, PoolSize: 10, MinIdleConns: 5, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, MaxBackups: 30, MaxAge: 30, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, MaxBackups: 90, MaxAge: 90, }, }, Middleware: MiddlewareConfig{ RateLimiter: RateLimiterConfig{ Max: 100, Expiration: 1 * time.Minute, Storage: "memory", }, }, }, wantErr: false, }, { name: "empty server address", config: &Config{ Server: ServerConfig{ Address: "", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "server.address", }, { name: "read timeout too short", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 1 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "read_timeout", }, { name: "read timeout too long", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 400 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "read_timeout", }, { name: "write timeout out of range", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 1 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "write_timeout", }, { name: "shutdown timeout too short", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 5 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "shutdown_timeout", }, { name: "empty redis address", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "redis.address", }, { name: "invalid redis port - too high", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 99999, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "redis.port", }, { name: "invalid redis port - zero", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 0, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "redis.port", }, { name: "redis db out of range", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, DB: 20, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "redis.db", }, { name: "redis pool size too large", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 2000, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "pool_size", }, { name: "min idle conns exceeds pool size", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, MinIdleConns: 20, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "min_idle_conns", }, { name: "invalid log level", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "invalid", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "logging.level", }, { name: "empty app log filename", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "app_log.filename", }, { name: "app log max size out of range", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 2000, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, }, wantErr: true, errMsg: "app_log.max_size", }, { name: "invalid rate limiter storage", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, Middleware: MiddlewareConfig{ RateLimiter: RateLimiterConfig{ Max: 100, Storage: "invalid", }, }, }, wantErr: true, errMsg: "rate_limiter.storage", }, { name: "rate limiter max too high", config: &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, Middleware: MiddlewareConfig{ RateLimiter: RateLimiterConfig{ Max: 20000, Storage: "memory", }, }, }, wantErr: true, errMsg: "rate_limiter.max", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.config.Validate() if (err != nil) != tt.wantErr { t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && tt.errMsg != "" { if err == nil { t.Errorf("expected error containing %q, got nil", tt.errMsg) } else if err.Error() == "" { t.Errorf("expected error containing %q, got empty error", tt.errMsg) } // Note: We check that error message exists, not exact match // This is because error messages might change slightly } }) } } // TestSet tests the Set function func TestSet(t *testing.T) { // Valid config validCfg := &Config{ Server: ServerConfig{ Address: ":3000", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ShutdownTimeout: 30 * time.Second, }, Redis: RedisConfig{ Address: "localhost", Port: 6379, PoolSize: 10, }, Logging: LoggingConfig{ Level: "info", AppLog: LogRotationConfig{ Filename: "logs/app.log", MaxSize: 100, }, AccessLog: LogRotationConfig{ Filename: "logs/access.log", MaxSize: 500, }, }, } err := Set(validCfg) if err != nil { t.Errorf("Set() with valid config failed: %v", err) } // Verify it was set got := Get() if got.Server.Address != ":3000" { t.Errorf("Get() after Set() returned wrong address: got %s, want :3000", got.Server.Address) } // Test with nil config err = Set(nil) if err == nil { t.Error("Set(nil) should return error") } // Test with invalid config invalidCfg := &Config{ Server: ServerConfig{ Address: "", // Empty address is invalid }, } err = Set(invalidCfg) if err == nil { t.Error("Set() with invalid config should return error") } }