1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::env;
4use std::fs;
5use std::path::PathBuf;
6
7use crate::observability::ObservabilityConfig;
8use crate::subscription::SubscriptionConfig;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Config {
13 pub server: ServerConfig,
14 pub auth: AuthConfig,
15 pub logging: LoggingConfig,
16 #[serde(default)]
17 pub http: HttpConfig,
18 #[serde(default)]
19 pub observability: ObservabilityConfig,
20 #[serde(default)]
21 pub subscriptions: SubscriptionConfig,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ServerConfig {
26 pub host: String,
28 pub port: u16,
30 pub max_connections: usize,
32 pub ssl_enabled: bool,
34 pub ssl_cert: Option<PathBuf>,
36 pub ssl_key: Option<PathBuf>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AuthConfig {
42 pub method: String,
44 pub password_file: Option<PathBuf>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct LoggingConfig {
50 pub level: String,
52 pub file: Option<PathBuf>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct HttpConfig {
58 pub enabled: bool,
60 pub host: String,
62 pub port: u16,
64 #[serde(default)]
66 pub auth: HttpAuthConfig,
67}
68
69impl Default for HttpConfig {
70 fn default() -> Self {
71 Self {
72 enabled: true,
73 host: "0.0.0.0".to_string(),
74 port: 8080,
75 auth: HttpAuthConfig::default(),
76 }
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct HttpAuthConfig {
83 pub enabled: bool,
85 pub methods: Vec<HttpAuthMethod>,
87 #[serde(default)]
89 pub api_keys: ApiKeyConfig,
90 #[serde(default)]
92 pub jwt: JwtConfig,
93}
94
95impl Default for HttpAuthConfig {
96 fn default() -> Self {
97 Self {
98 enabled: false,
99 methods: vec![HttpAuthMethod::ApiKey, HttpAuthMethod::Basic],
100 api_keys: ApiKeyConfig::default(),
101 jwt: JwtConfig::default(),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108#[serde(rename_all = "snake_case")]
109pub enum HttpAuthMethod {
110 ApiKey,
112 Basic,
114 Jwt,
116}
117
118#[derive(Debug, Clone, Default, Serialize, Deserialize)]
120pub struct ApiKeyConfig {
121 #[serde(default)]
123 pub keys: Vec<String>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct JwtConfig {
129 #[serde(default)]
131 pub secret: String,
132 #[serde(default)]
134 pub issuer: Option<String>,
135 #[serde(default)]
137 pub audience: Option<String>,
138 #[serde(default = "default_jwt_expiration")]
140 pub expiration_secs: u64,
141}
142
143fn default_jwt_expiration() -> u64 {
144 3600
145}
146
147impl Default for JwtConfig {
148 fn default() -> Self {
149 Self {
150 secret: String::new(),
151 issuer: Some("vibesql".to_string()),
152 audience: Some("vibesql-api".to_string()),
153 expiration_secs: default_jwt_expiration(),
154 }
155 }
156}
157
158impl Default for Config {
159 fn default() -> Self {
160 Self {
161 server: ServerConfig {
162 host: "0.0.0.0".to_string(),
163 port: 5432,
164 max_connections: 100,
165 ssl_enabled: false,
166 ssl_cert: None,
167 ssl_key: None,
168 },
169 auth: AuthConfig { method: "trust".to_string(), password_file: None },
170 logging: LoggingConfig { level: "info".to_string(), file: None },
171 http: HttpConfig::default(),
172 observability: ObservabilityConfig::default(),
173 subscriptions: SubscriptionConfig::default(),
174 }
175 }
176}
177
178impl Config {
179 pub fn load() -> Result<Self> {
185 let config_paths = vec![
186 PathBuf::from("vibesql-server.toml"),
187 dirs::config_dir()
188 .map(|p| p.join("vibesql").join("vibesql-server.toml"))
189 .unwrap_or_default(),
190 PathBuf::from("/etc/vibesql/vibesql-server.toml"),
191 ];
192
193 for path in config_paths {
194 if path.exists() {
195 let contents = fs::read_to_string(&path)?;
196 let mut config: Config = toml::from_str(&contents)?;
197 config.apply_env_overrides();
198 return Ok(config);
199 }
200 }
201
202 Err(anyhow::anyhow!("No configuration file found"))
204 }
205
206 #[allow(dead_code)]
208 pub fn load_from(path: &PathBuf) -> Result<Self> {
209 let contents = fs::read_to_string(path)?;
210 let mut config: Config = toml::from_str(&contents)?;
211 config.apply_env_overrides();
212 Ok(config)
213 }
214
215 pub fn apply_env_overrides(&mut self) {
245 if let Ok(val) = env::var("VIBESQL_SERVER_HOST") {
247 self.server.host = val;
248 }
249 if let Ok(val) = env::var("VIBESQL_SERVER_PORT") {
250 if let Ok(port) = val.parse() {
251 self.server.port = port;
252 }
253 }
254 if let Ok(val) = env::var("VIBESQL_SERVER_MAX_CONNECTIONS") {
255 if let Ok(max_conn) = val.parse() {
256 self.server.max_connections = max_conn;
257 }
258 }
259 if let Ok(val) = env::var("VIBESQL_SERVER_SSL_ENABLED") {
260 self.server.ssl_enabled = parse_bool(&val);
261 }
262 if let Ok(val) = env::var("VIBESQL_SERVER_SSL_CERT") {
263 self.server.ssl_cert = Some(PathBuf::from(val));
264 }
265 if let Ok(val) = env::var("VIBESQL_SERVER_SSL_KEY") {
266 self.server.ssl_key = Some(PathBuf::from(val));
267 }
268
269 if let Ok(val) = env::var("VIBESQL_AUTH_METHOD") {
271 self.auth.method = val;
272 }
273 if let Ok(val) = env::var("VIBESQL_AUTH_PASSWORD_FILE") {
274 self.auth.password_file = Some(PathBuf::from(val));
275 }
276
277 if let Ok(val) = env::var("VIBESQL_LOG_LEVEL") {
279 self.logging.level = val;
280 }
281 if let Ok(val) = env::var("VIBESQL_LOG_FILE") {
282 self.logging.file = Some(PathBuf::from(val));
283 }
284
285 if let Ok(val) = env::var("VIBESQL_HTTP_ENABLED") {
287 self.http.enabled = parse_bool(&val);
288 }
289 if let Ok(val) = env::var("VIBESQL_HTTP_HOST") {
290 self.http.host = val;
291 }
292 if let Ok(val) = env::var("VIBESQL_HTTP_PORT") {
293 if let Ok(port) = val.parse() {
294 self.http.port = port;
295 }
296 }
297
298 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_ENABLED") {
300 self.http.auth.enabled = parse_bool(&val);
301 }
302 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_METHODS") {
303 let methods: Vec<HttpAuthMethod> = val
304 .split(',')
305 .filter_map(|s| match s.trim().to_lowercase().as_str() {
306 "api_key" => Some(HttpAuthMethod::ApiKey),
307 "basic" => Some(HttpAuthMethod::Basic),
308 "jwt" => Some(HttpAuthMethod::Jwt),
309 _ => None,
310 })
311 .collect();
312 if !methods.is_empty() {
313 self.http.auth.methods = methods;
314 }
315 }
316 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_API_KEYS") {
317 let keys: Vec<String> = val
318 .split(',')
319 .map(|s| s.trim().to_string())
320 .filter(|s| !s.is_empty())
321 .collect();
322 if !keys.is_empty() {
323 self.http.auth.api_keys.keys = keys;
324 }
325 }
326
327 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_JWT_SECRET") {
329 self.http.auth.jwt.secret = val;
330 }
331 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_JWT_ISSUER") {
332 self.http.auth.jwt.issuer = Some(val);
333 }
334 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_JWT_AUDIENCE") {
335 self.http.auth.jwt.audience = Some(val);
336 }
337 if let Ok(val) = env::var("VIBESQL_HTTP_AUTH_JWT_EXPIRATION") {
338 if let Ok(secs) = val.parse() {
339 self.http.auth.jwt.expiration_secs = secs;
340 }
341 }
342 }
343}
344
345fn parse_bool(val: &str) -> bool {
349 matches!(val.to_lowercase().as_str(), "true" | "1" | "yes" | "on")
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use std::sync::Mutex;
356
357 static ENV_TEST_MUTEX: Mutex<()> = Mutex::new(());
359
360 #[test]
361 fn test_default_config() {
362 let config = Config::default();
363 assert_eq!(config.server.host, "0.0.0.0");
364 assert_eq!(config.server.port, 5432);
365 assert_eq!(config.server.max_connections, 100);
366 assert!(!config.server.ssl_enabled);
367 assert_eq!(config.auth.method, "trust");
368 }
369
370 #[test]
371 fn test_config_serialization() {
372 let config = Config::default();
373 let toml_str = toml::to_string(&config).unwrap();
374 let deserialized: Config = toml::from_str(&toml_str).unwrap();
375 assert_eq!(config.server.port, deserialized.server.port);
376 }
377
378 #[test]
379 fn test_selective_updates_config_defaults() {
380 let config = Config::default();
381 assert!(config.subscriptions.selective_updates.enabled);
382 assert_eq!(config.subscriptions.selective_updates.min_changed_columns, 1);
383 assert!((config.subscriptions.selective_updates.max_changed_columns_ratio - 0.5).abs() < 0.001);
384 }
385
386 #[test]
387 fn test_selective_updates_config_from_toml() {
388 let toml_str = r#"
389[server]
390host = "0.0.0.0"
391port = 5432
392max_connections = 100
393ssl_enabled = false
394
395[auth]
396method = "trust"
397
398[logging]
399level = "info"
400
401[subscriptions.selective_updates]
402enabled = false
403min_changed_columns = 2
404max_changed_columns_ratio = 0.75
405"#;
406 let config: Config = toml::from_str(toml_str).unwrap();
407 assert!(!config.subscriptions.selective_updates.enabled);
408 assert_eq!(config.subscriptions.selective_updates.min_changed_columns, 2);
409 assert!((config.subscriptions.selective_updates.max_changed_columns_ratio - 0.75).abs() < 0.001);
410 }
411
412 #[test]
413 fn test_selective_updates_config_partial_override() {
414 let toml_str = r#"
416[server]
417host = "0.0.0.0"
418port = 5432
419max_connections = 100
420ssl_enabled = false
421
422[auth]
423method = "trust"
424
425[logging]
426level = "info"
427
428[subscriptions.selective_updates]
429enabled = false
430"#;
431 let config: Config = toml::from_str(toml_str).unwrap();
432 assert!(!config.subscriptions.selective_updates.enabled);
433 assert_eq!(config.subscriptions.selective_updates.min_changed_columns, 1);
435 assert!((config.subscriptions.selective_updates.max_changed_columns_ratio - 0.5).abs() < 0.001);
436 }
437
438 #[test]
439 fn test_selective_updates_config_serialization() {
440 let config = Config::default();
441 let toml_str = toml::to_string(&config).unwrap();
442 let deserialized: Config = toml::from_str(&toml_str).unwrap();
443
444 assert_eq!(
445 config.subscriptions.selective_updates.enabled,
446 deserialized.subscriptions.selective_updates.enabled
447 );
448 assert_eq!(
449 config.subscriptions.selective_updates.min_changed_columns,
450 deserialized.subscriptions.selective_updates.min_changed_columns
451 );
452 assert!(
453 (config.subscriptions.selective_updates.max_changed_columns_ratio
454 - deserialized.subscriptions.selective_updates.max_changed_columns_ratio).abs() < 0.001
455 );
456 }
457
458 #[test]
459 fn test_parse_bool() {
460 assert!(parse_bool("true"));
461 assert!(parse_bool("TRUE"));
462 assert!(parse_bool("True"));
463 assert!(parse_bool("1"));
464 assert!(parse_bool("yes"));
465 assert!(parse_bool("YES"));
466 assert!(parse_bool("on"));
467 assert!(parse_bool("ON"));
468
469 assert!(!parse_bool("false"));
470 assert!(!parse_bool("0"));
471 assert!(!parse_bool("no"));
472 assert!(!parse_bool("off"));
473 assert!(!parse_bool(""));
474 assert!(!parse_bool("invalid"));
475 }
476
477 #[test]
478 fn test_env_override_server_host() {
479 let _lock = ENV_TEST_MUTEX.lock().unwrap();
480 let mut config = Config::default();
481 assert_eq!(config.server.host, "0.0.0.0");
482
483 env::set_var("VIBESQL_SERVER_HOST", "127.0.0.1");
484 config.apply_env_overrides();
485 assert_eq!(config.server.host, "127.0.0.1");
486 env::remove_var("VIBESQL_SERVER_HOST");
487 }
488
489 #[test]
490 fn test_env_override_server_port() {
491 let _lock = ENV_TEST_MUTEX.lock().unwrap();
492 let mut config = Config::default();
493 assert_eq!(config.server.port, 5432);
494
495 env::set_var("VIBESQL_SERVER_PORT", "5433");
496 config.apply_env_overrides();
497 assert_eq!(config.server.port, 5433);
498 env::remove_var("VIBESQL_SERVER_PORT");
499 }
500
501 #[test]
502 fn test_env_override_server_port_invalid() {
503 let _lock = ENV_TEST_MUTEX.lock().unwrap();
504 let mut config = Config::default();
505 assert_eq!(config.server.port, 5432);
506
507 env::set_var("VIBESQL_SERVER_PORT", "not_a_number");
509 config.apply_env_overrides();
510 assert_eq!(config.server.port, 5432);
511 env::remove_var("VIBESQL_SERVER_PORT");
512 }
513
514 #[test]
515 fn test_env_override_max_connections() {
516 let _lock = ENV_TEST_MUTEX.lock().unwrap();
517 let mut config = Config::default();
518 assert_eq!(config.server.max_connections, 100);
519
520 env::set_var("VIBESQL_SERVER_MAX_CONNECTIONS", "500");
521 config.apply_env_overrides();
522 assert_eq!(config.server.max_connections, 500);
523 env::remove_var("VIBESQL_SERVER_MAX_CONNECTIONS");
524 }
525
526 #[test]
527 fn test_env_override_ssl_enabled() {
528 let _lock = ENV_TEST_MUTEX.lock().unwrap();
529 let mut config = Config::default();
530 assert!(!config.server.ssl_enabled);
531
532 env::set_var("VIBESQL_SERVER_SSL_ENABLED", "true");
533 config.apply_env_overrides();
534 assert!(config.server.ssl_enabled);
535 env::remove_var("VIBESQL_SERVER_SSL_ENABLED");
536 }
537
538 #[test]
539 fn test_env_override_ssl_cert_and_key() {
540 let _lock = ENV_TEST_MUTEX.lock().unwrap();
541 let mut config = Config::default();
542 assert!(config.server.ssl_cert.is_none());
543 assert!(config.server.ssl_key.is_none());
544
545 env::set_var("VIBESQL_SERVER_SSL_CERT", "/path/to/cert.pem");
546 env::set_var("VIBESQL_SERVER_SSL_KEY", "/path/to/key.pem");
547 config.apply_env_overrides();
548 assert_eq!(config.server.ssl_cert, Some(PathBuf::from("/path/to/cert.pem")));
549 assert_eq!(config.server.ssl_key, Some(PathBuf::from("/path/to/key.pem")));
550 env::remove_var("VIBESQL_SERVER_SSL_CERT");
551 env::remove_var("VIBESQL_SERVER_SSL_KEY");
552 }
553
554 #[test]
555 fn test_env_override_auth_method() {
556 let _lock = ENV_TEST_MUTEX.lock().unwrap();
557 let mut config = Config::default();
558 assert_eq!(config.auth.method, "trust");
559
560 env::set_var("VIBESQL_AUTH_METHOD", "scram-sha-256");
561 config.apply_env_overrides();
562 assert_eq!(config.auth.method, "scram-sha-256");
563 env::remove_var("VIBESQL_AUTH_METHOD");
564 }
565
566 #[test]
567 fn test_env_override_auth_password_file() {
568 let _lock = ENV_TEST_MUTEX.lock().unwrap();
569 let mut config = Config::default();
570 assert!(config.auth.password_file.is_none());
571
572 env::set_var("VIBESQL_AUTH_PASSWORD_FILE", "/etc/vibesql/passwords");
573 config.apply_env_overrides();
574 assert_eq!(config.auth.password_file, Some(PathBuf::from("/etc/vibesql/passwords")));
575 env::remove_var("VIBESQL_AUTH_PASSWORD_FILE");
576 }
577
578 #[test]
579 fn test_env_override_log_level() {
580 let _lock = ENV_TEST_MUTEX.lock().unwrap();
581 let mut config = Config::default();
582 assert_eq!(config.logging.level, "info");
583
584 env::set_var("VIBESQL_LOG_LEVEL", "debug");
585 config.apply_env_overrides();
586 assert_eq!(config.logging.level, "debug");
587 env::remove_var("VIBESQL_LOG_LEVEL");
588 }
589
590 #[test]
591 fn test_env_override_log_file() {
592 let _lock = ENV_TEST_MUTEX.lock().unwrap();
593 let mut config = Config::default();
594 assert!(config.logging.file.is_none());
595
596 env::set_var("VIBESQL_LOG_FILE", "/var/log/vibesql/server.log");
597 config.apply_env_overrides();
598 assert_eq!(config.logging.file, Some(PathBuf::from("/var/log/vibesql/server.log")));
599 env::remove_var("VIBESQL_LOG_FILE");
600 }
601
602 #[test]
603 fn test_env_override_http_enabled() {
604 let _lock = ENV_TEST_MUTEX.lock().unwrap();
605 let mut config = Config::default();
606 assert!(config.http.enabled);
607
608 env::set_var("VIBESQL_HTTP_ENABLED", "false");
609 config.apply_env_overrides();
610 assert!(!config.http.enabled);
611 env::remove_var("VIBESQL_HTTP_ENABLED");
612 }
613
614 #[test]
615 fn test_env_override_http_host() {
616 let _lock = ENV_TEST_MUTEX.lock().unwrap();
617 let mut config = Config::default();
618 assert_eq!(config.http.host, "0.0.0.0");
619
620 env::set_var("VIBESQL_HTTP_HOST", "localhost");
621 config.apply_env_overrides();
622 assert_eq!(config.http.host, "localhost");
623 env::remove_var("VIBESQL_HTTP_HOST");
624 }
625
626 #[test]
627 fn test_env_override_http_port() {
628 let _lock = ENV_TEST_MUTEX.lock().unwrap();
629 let mut config = Config::default();
630 assert_eq!(config.http.port, 8080);
631
632 env::set_var("VIBESQL_HTTP_PORT", "9090");
633 config.apply_env_overrides();
634 assert_eq!(config.http.port, 9090);
635 env::remove_var("VIBESQL_HTTP_PORT");
636 }
637
638 #[test]
639 fn test_env_override_multiple_values() {
640 let _lock = ENV_TEST_MUTEX.lock().unwrap();
641 let mut config = Config::default();
642
643 env::set_var("VIBESQL_SERVER_HOST", "192.168.1.1");
644 env::set_var("VIBESQL_SERVER_PORT", "5433");
645 env::set_var("VIBESQL_AUTH_METHOD", "md5");
646 env::set_var("VIBESQL_LOG_LEVEL", "warn");
647 env::set_var("VIBESQL_HTTP_PORT", "8081");
648
649 config.apply_env_overrides();
650
651 assert_eq!(config.server.host, "192.168.1.1");
652 assert_eq!(config.server.port, 5433);
653 assert_eq!(config.auth.method, "md5");
654 assert_eq!(config.logging.level, "warn");
655 assert_eq!(config.http.port, 8081);
656
657 env::remove_var("VIBESQL_SERVER_HOST");
658 env::remove_var("VIBESQL_SERVER_PORT");
659 env::remove_var("VIBESQL_AUTH_METHOD");
660 env::remove_var("VIBESQL_LOG_LEVEL");
661 env::remove_var("VIBESQL_HTTP_PORT");
662 }
663
664 #[test]
665 fn test_env_override_http_auth_enabled() {
666 let _lock = ENV_TEST_MUTEX.lock().unwrap();
667 let mut config = Config::default();
668 assert!(!config.http.auth.enabled);
669
670 env::set_var("VIBESQL_HTTP_AUTH_ENABLED", "true");
671 config.apply_env_overrides();
672 assert!(config.http.auth.enabled);
673 env::remove_var("VIBESQL_HTTP_AUTH_ENABLED");
674 }
675
676 #[test]
677 fn test_env_override_http_auth_enabled_false() {
678 let _lock = ENV_TEST_MUTEX.lock().unwrap();
679 let mut config = Config::default();
680 config.http.auth.enabled = true;
681
682 env::set_var("VIBESQL_HTTP_AUTH_ENABLED", "false");
683 config.apply_env_overrides();
684 assert!(!config.http.auth.enabled);
685 env::remove_var("VIBESQL_HTTP_AUTH_ENABLED");
686 }
687
688 #[test]
689 fn test_env_override_http_auth_methods() {
690 let _lock = ENV_TEST_MUTEX.lock().unwrap();
691 let mut config = Config::default();
692 assert_eq!(config.http.auth.methods.len(), 2);
694 assert!(config.http.auth.methods.contains(&HttpAuthMethod::ApiKey));
695 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Basic));
696
697 env::set_var("VIBESQL_HTTP_AUTH_METHODS", "jwt,api_key");
698 config.apply_env_overrides();
699 assert_eq!(config.http.auth.methods.len(), 2);
700 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Jwt));
701 assert!(config.http.auth.methods.contains(&HttpAuthMethod::ApiKey));
702 assert!(!config.http.auth.methods.contains(&HttpAuthMethod::Basic));
703 env::remove_var("VIBESQL_HTTP_AUTH_METHODS");
704 }
705
706 #[test]
707 fn test_env_override_http_auth_methods_case_insensitive() {
708 let _lock = ENV_TEST_MUTEX.lock().unwrap();
709 let mut config = Config::default();
710
711 env::set_var("VIBESQL_HTTP_AUTH_METHODS", "JWT, API_KEY, BASIC");
712 config.apply_env_overrides();
713 assert_eq!(config.http.auth.methods.len(), 3);
714 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Jwt));
715 assert!(config.http.auth.methods.contains(&HttpAuthMethod::ApiKey));
716 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Basic));
717 env::remove_var("VIBESQL_HTTP_AUTH_METHODS");
718 }
719
720 #[test]
721 fn test_env_override_http_auth_methods_invalid_ignored() {
722 let _lock = ENV_TEST_MUTEX.lock().unwrap();
723 let mut config = Config::default();
724
725 env::set_var("VIBESQL_HTTP_AUTH_METHODS", "jwt,invalid_method,basic,unknown");
727 config.apply_env_overrides();
728 assert_eq!(config.http.auth.methods.len(), 2);
729 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Jwt));
730 assert!(config.http.auth.methods.contains(&HttpAuthMethod::Basic));
731 env::remove_var("VIBESQL_HTTP_AUTH_METHODS");
732 }
733
734 #[test]
735 fn test_env_override_http_auth_methods_empty_preserves_default() {
736 let _lock = ENV_TEST_MUTEX.lock().unwrap();
737 let mut config = Config::default();
738 let original_methods = config.http.auth.methods.clone();
739
740 env::set_var("VIBESQL_HTTP_AUTH_METHODS", "");
742 config.apply_env_overrides();
743 assert_eq!(config.http.auth.methods, original_methods);
744 env::remove_var("VIBESQL_HTTP_AUTH_METHODS");
745 }
746
747 #[test]
748 fn test_env_override_http_auth_methods_all_invalid_preserves_default() {
749 let _lock = ENV_TEST_MUTEX.lock().unwrap();
750 let mut config = Config::default();
751 let original_methods = config.http.auth.methods.clone();
752
753 env::set_var("VIBESQL_HTTP_AUTH_METHODS", "invalid,unknown,bad");
755 config.apply_env_overrides();
756 assert_eq!(config.http.auth.methods, original_methods);
757 env::remove_var("VIBESQL_HTTP_AUTH_METHODS");
758 }
759
760 #[test]
761 fn test_env_override_http_auth_api_keys() {
762 let _lock = ENV_TEST_MUTEX.lock().unwrap();
763 let mut config = Config::default();
764 assert!(config.http.auth.api_keys.keys.is_empty());
765
766 env::set_var("VIBESQL_HTTP_AUTH_API_KEYS", "key1,key2,key3");
767 config.apply_env_overrides();
768 assert_eq!(config.http.auth.api_keys.keys.len(), 3);
769 assert!(config.http.auth.api_keys.keys.contains(&"key1".to_string()));
770 assert!(config.http.auth.api_keys.keys.contains(&"key2".to_string()));
771 assert!(config.http.auth.api_keys.keys.contains(&"key3".to_string()));
772 env::remove_var("VIBESQL_HTTP_AUTH_API_KEYS");
773 }
774
775 #[test]
776 fn test_env_override_http_auth_api_keys_trims_whitespace() {
777 let _lock = ENV_TEST_MUTEX.lock().unwrap();
778 let mut config = Config::default();
779
780 env::set_var("VIBESQL_HTTP_AUTH_API_KEYS", " key1 , key2 , key3 ");
781 config.apply_env_overrides();
782 assert_eq!(config.http.auth.api_keys.keys.len(), 3);
783 assert!(config.http.auth.api_keys.keys.contains(&"key1".to_string()));
784 assert!(config.http.auth.api_keys.keys.contains(&"key2".to_string()));
785 assert!(config.http.auth.api_keys.keys.contains(&"key3".to_string()));
786 env::remove_var("VIBESQL_HTTP_AUTH_API_KEYS");
787 }
788
789 #[test]
790 fn test_env_override_http_auth_api_keys_empty_values_ignored() {
791 let _lock = ENV_TEST_MUTEX.lock().unwrap();
792 let mut config = Config::default();
793
794 env::set_var("VIBESQL_HTTP_AUTH_API_KEYS", "key1,,key2, ,key3");
796 config.apply_env_overrides();
797 assert_eq!(config.http.auth.api_keys.keys.len(), 3);
798 assert!(config.http.auth.api_keys.keys.contains(&"key1".to_string()));
799 assert!(config.http.auth.api_keys.keys.contains(&"key2".to_string()));
800 assert!(config.http.auth.api_keys.keys.contains(&"key3".to_string()));
801 env::remove_var("VIBESQL_HTTP_AUTH_API_KEYS");
802 }
803
804 #[test]
805 fn test_env_override_http_auth_api_keys_empty_preserves_default() {
806 let _lock = ENV_TEST_MUTEX.lock().unwrap();
807 let mut config = Config::default();
808 config.http.auth.api_keys.keys = vec!["original-key".to_string()];
810
811 env::set_var("VIBESQL_HTTP_AUTH_API_KEYS", "");
813 config.apply_env_overrides();
814 assert_eq!(config.http.auth.api_keys.keys, vec!["original-key".to_string()]);
815 env::remove_var("VIBESQL_HTTP_AUTH_API_KEYS");
816 }
817
818 #[test]
819 fn test_env_override_http_auth_api_keys_single_key() {
820 let _lock = ENV_TEST_MUTEX.lock().unwrap();
821 let mut config = Config::default();
822
823 env::set_var("VIBESQL_HTTP_AUTH_API_KEYS", "single-key");
824 config.apply_env_overrides();
825 assert_eq!(config.http.auth.api_keys.keys.len(), 1);
826 assert_eq!(config.http.auth.api_keys.keys[0], "single-key");
827 env::remove_var("VIBESQL_HTTP_AUTH_API_KEYS");
828 }
829
830 #[test]
831 fn test_env_override_jwt_secret() {
832 let _lock = ENV_TEST_MUTEX.lock().unwrap();
833 let mut config = Config::default();
834 assert_eq!(config.http.auth.jwt.secret, "");
835
836 env::set_var("VIBESQL_HTTP_AUTH_JWT_SECRET", "my-super-secret-key");
837 config.apply_env_overrides();
838 assert_eq!(config.http.auth.jwt.secret, "my-super-secret-key");
839 env::remove_var("VIBESQL_HTTP_AUTH_JWT_SECRET");
840 }
841
842 #[test]
843 fn test_env_override_jwt_issuer() {
844 let _lock = ENV_TEST_MUTEX.lock().unwrap();
845 let mut config = Config::default();
846 assert_eq!(config.http.auth.jwt.issuer, Some("vibesql".to_string()));
847
848 env::set_var("VIBESQL_HTTP_AUTH_JWT_ISSUER", "custom-issuer");
849 config.apply_env_overrides();
850 assert_eq!(config.http.auth.jwt.issuer, Some("custom-issuer".to_string()));
851 env::remove_var("VIBESQL_HTTP_AUTH_JWT_ISSUER");
852 }
853
854 #[test]
855 fn test_env_override_jwt_audience() {
856 let _lock = ENV_TEST_MUTEX.lock().unwrap();
857 let mut config = Config::default();
858 assert_eq!(config.http.auth.jwt.audience, Some("vibesql-api".to_string()));
859
860 env::set_var("VIBESQL_HTTP_AUTH_JWT_AUDIENCE", "custom-audience");
861 config.apply_env_overrides();
862 assert_eq!(config.http.auth.jwt.audience, Some("custom-audience".to_string()));
863 env::remove_var("VIBESQL_HTTP_AUTH_JWT_AUDIENCE");
864 }
865
866 #[test]
867 fn test_env_override_jwt_expiration() {
868 let _lock = ENV_TEST_MUTEX.lock().unwrap();
869 let mut config = Config::default();
870 assert_eq!(config.http.auth.jwt.expiration_secs, 3600);
871
872 env::set_var("VIBESQL_HTTP_AUTH_JWT_EXPIRATION", "7200");
873 config.apply_env_overrides();
874 assert_eq!(config.http.auth.jwt.expiration_secs, 7200);
875 env::remove_var("VIBESQL_HTTP_AUTH_JWT_EXPIRATION");
876 }
877
878 #[test]
879 fn test_env_override_jwt_expiration_invalid() {
880 let _lock = ENV_TEST_MUTEX.lock().unwrap();
881 let mut config = Config::default();
882 assert_eq!(config.http.auth.jwt.expiration_secs, 3600);
883
884 env::set_var("VIBESQL_HTTP_AUTH_JWT_EXPIRATION", "not_a_number");
886 config.apply_env_overrides();
887 assert_eq!(config.http.auth.jwt.expiration_secs, 3600);
888 env::remove_var("VIBESQL_HTTP_AUTH_JWT_EXPIRATION");
889 }
890}