1use std::{env, str::FromStr};
3use strum::Display;
4
5use crate::{
6 constants::{
7 DEFAULT_PROVIDER_FAILURE_EXPIRATION_SECS, DEFAULT_PROVIDER_FAILURE_THRESHOLD,
8 DEFAULT_PROVIDER_PAUSE_DURATION_SECS, MINIMUM_SECRET_VALUE_LENGTH,
9 STELLAR_FEE_FORWARDER_MAINNET, STELLAR_SOROSWAP_MAINNET_FACTORY,
10 STELLAR_SOROSWAP_MAINNET_NATIVE_WRAPPER, STELLAR_SOROSWAP_MAINNET_ROUTER,
11 },
12 models::SecretString,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Display)]
16pub enum RepositoryStorageType {
17 InMemory,
18 Redis,
19}
20
21impl FromStr for RepositoryStorageType {
22 type Err = String;
23
24 fn from_str(s: &str) -> Result<Self, Self::Err> {
25 match s.to_lowercase().as_str() {
26 "inmemory" | "in_memory" => Ok(Self::InMemory),
27 "redis" => Ok(Self::Redis),
28 _ => Err(format!("Invalid repository storage type: {s}")),
29 }
30 }
31}
32
33fn non_empty_const(s: &str) -> Option<String> {
35 if s.is_empty() {
36 None
37 } else {
38 Some(s.to_string())
39 }
40}
41
42#[derive(Debug, Clone)]
43pub struct ServerConfig {
44 pub host: String,
46 pub port: u16,
48 pub redis_url: String,
50 pub redis_reader_url: Option<String>,
54 pub config_file_path: String,
56 pub api_key: SecretString,
58 pub rate_limit_requests_per_second: u64,
60 pub rate_limit_burst_size: u32,
62 pub metrics_port: u16,
64 pub enable_swagger: bool,
66 pub redis_connection_timeout_ms: u64,
68 pub redis_key_prefix: String,
70 pub redis_pool_max_size: usize,
72 pub redis_reader_pool_max_size: usize,
75 pub redis_pool_timeout_ms: u64,
77 pub rpc_timeout_ms: u64,
79 pub provider_max_retries: u8,
81 pub provider_retry_base_delay_ms: u64,
83 pub provider_retry_max_delay_ms: u64,
85 pub provider_max_failovers: u8,
87 pub provider_failure_threshold: u32,
89 pub provider_pause_duration_secs: u64,
91 pub provider_failure_expiration_secs: u64,
93 pub repository_storage_type: RepositoryStorageType,
95 pub reset_storage_on_start: bool,
97 pub storage_encryption_key: Option<SecretString>,
99 pub transaction_expiration_hours: f64,
102 pub rpc_allowed_hosts: Vec<String>,
104 pub rpc_block_private_ips: bool,
106 pub relayer_concurrency_limit: usize,
108 pub max_connections: usize,
110 pub connection_backlog: u32,
113 pub request_timeout_seconds: u64,
115 pub stellar_mainnet_fee_forwarder_address: Option<String>,
117 pub stellar_testnet_fee_forwarder_address: Option<String>,
119 pub stellar_mainnet_soroswap_router_address: Option<String>,
121 pub stellar_testnet_soroswap_router_address: Option<String>,
123 pub stellar_mainnet_soroswap_factory_address: Option<String>,
125 pub stellar_testnet_soroswap_factory_address: Option<String>,
127 pub stellar_mainnet_soroswap_native_wrapper_address: Option<String>,
129 pub stellar_testnet_soroswap_native_wrapper_address: Option<String>,
131}
132
133impl ServerConfig {
134 pub fn from_env() -> Self {
161 Self {
162 host: Self::get_host(),
163 port: Self::get_port(),
164 redis_url: Self::get_redis_url(), redis_reader_url: Self::get_redis_reader_url_optional(),
166 redis_reader_pool_max_size: Self::get_redis_reader_pool_max_size(),
167 config_file_path: Self::get_config_file_path(),
168 api_key: Self::get_api_key(), rate_limit_requests_per_second: Self::get_rate_limit_requests_per_second(),
170 rate_limit_burst_size: Self::get_rate_limit_burst_size(),
171 metrics_port: Self::get_metrics_port(),
172 enable_swagger: Self::get_enable_swagger(),
173 redis_connection_timeout_ms: Self::get_redis_connection_timeout_ms(),
174 redis_key_prefix: Self::get_redis_key_prefix(),
175 redis_pool_max_size: Self::get_redis_pool_max_size(),
176 redis_pool_timeout_ms: Self::get_redis_pool_timeout_ms(),
177 rpc_timeout_ms: Self::get_rpc_timeout_ms(),
178 provider_max_retries: Self::get_provider_max_retries(),
179 provider_retry_base_delay_ms: Self::get_provider_retry_base_delay_ms(),
180 provider_retry_max_delay_ms: Self::get_provider_retry_max_delay_ms(),
181 provider_max_failovers: Self::get_provider_max_failovers(),
182 provider_failure_threshold: Self::get_provider_failure_threshold(),
183 provider_pause_duration_secs: Self::get_provider_pause_duration_secs(),
184 provider_failure_expiration_secs: Self::get_provider_failure_expiration_secs(),
185 repository_storage_type: Self::get_repository_storage_type(),
186 reset_storage_on_start: Self::get_reset_storage_on_start(),
187 storage_encryption_key: Self::get_storage_encryption_key(),
188 transaction_expiration_hours: Self::get_transaction_expiration_hours(),
189 rpc_allowed_hosts: Self::get_rpc_allowed_hosts(),
190 rpc_block_private_ips: Self::get_rpc_block_private_ips(),
191 relayer_concurrency_limit: Self::get_relayer_concurrency_limit(),
192 max_connections: Self::get_max_connections(),
193 connection_backlog: Self::get_connection_backlog(),
194 request_timeout_seconds: Self::get_request_timeout_seconds(),
195 stellar_mainnet_fee_forwarder_address: Self::get_stellar_mainnet_fee_forwarder_address(
196 ),
197 stellar_testnet_fee_forwarder_address: Self::get_stellar_testnet_fee_forwarder_address(
198 ),
199 stellar_mainnet_soroswap_router_address:
200 Self::get_stellar_mainnet_soroswap_router_address(),
201 stellar_testnet_soroswap_router_address:
202 Self::get_stellar_testnet_soroswap_router_address(),
203 stellar_mainnet_soroswap_factory_address:
204 Self::get_stellar_mainnet_soroswap_factory_address(),
205 stellar_testnet_soroswap_factory_address:
206 Self::get_stellar_testnet_soroswap_factory_address(),
207 stellar_mainnet_soroswap_native_wrapper_address:
208 Self::get_stellar_mainnet_soroswap_native_wrapper_address(),
209 stellar_testnet_soroswap_native_wrapper_address:
210 Self::get_stellar_testnet_soroswap_native_wrapper_address(),
211 }
212 }
213
214 pub fn get_host() -> String {
218 env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string())
219 }
220
221 pub fn get_port() -> u16 {
223 env::var("APP_PORT")
224 .unwrap_or_else(|_| "8080".to_string())
225 .parse()
226 .unwrap_or(8080)
227 }
228
229 pub fn get_redis_url() -> String {
231 env::var("REDIS_URL").expect("REDIS_URL must be set")
232 }
233
234 pub fn get_redis_url_optional() -> Option<String> {
236 env::var("REDIS_URL").ok()
237 }
238
239 pub fn get_redis_reader_url_optional() -> Option<String> {
243 env::var("REDIS_READER_URL").ok()
244 }
245
246 pub fn get_config_file_path() -> String {
248 let conf_dir = if env::var("IN_DOCKER")
249 .map(|val| val == "true")
250 .unwrap_or(false)
251 {
252 "config/".to_string()
253 } else {
254 env::var("CONFIG_DIR").unwrap_or_else(|_| "./config".to_string())
255 };
256
257 let conf_dir = format!("{}/", conf_dir.trim_end_matches('/'));
258 let config_file_name =
259 env::var("CONFIG_FILE_NAME").unwrap_or_else(|_| "config.json".to_string());
260
261 format!("{conf_dir}{config_file_name}")
262 }
263
264 pub fn get_queue_backend() -> String {
269 env::var("QUEUE_BACKEND").unwrap_or_else(|_| "redis".to_string())
270 }
271
272 pub fn get_sqs_queue_type() -> String {
278 env::var("SQS_QUEUE_TYPE").unwrap_or_else(|_| "auto".to_string())
279 }
280
281 pub fn get_aws_region() -> Result<String, String> {
289 env::var("AWS_REGION")
290 .map_err(|_| "AWS_REGION not set. Required for SQS backend.".to_string())
291 }
292
293 pub fn get_aws_account_id() -> Result<String, String> {
301 env::var("AWS_ACCOUNT_ID").map_err(|_| {
302 "AWS_ACCOUNT_ID not set. Required when SQS_QUEUE_URL_PREFIX is not provided."
303 .to_string()
304 })
305 }
306
307 pub fn get_api_key() -> SecretString {
309 let api_key = SecretString::new(&env::var("API_KEY").expect("API_KEY must be set"));
310
311 if !api_key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH) {
312 panic!(
313 "Security error: API_KEY must be at least {MINIMUM_SECRET_VALUE_LENGTH} characters long"
314 );
315 }
316
317 api_key
318 }
319
320 pub fn get_api_key_optional() -> Option<SecretString> {
322 env::var("API_KEY")
323 .ok()
324 .map(|key| SecretString::new(&key))
325 .filter(|key| key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH))
326 }
327
328 pub fn get_rate_limit_requests_per_second() -> u64 {
330 env::var("RATE_LIMIT_REQUESTS_PER_SECOND")
331 .unwrap_or_else(|_| "100".to_string())
332 .parse()
333 .unwrap_or(100)
334 }
335
336 pub fn get_rate_limit_burst_size() -> u32 {
338 env::var("RATE_LIMIT_BURST_SIZE")
339 .unwrap_or_else(|_| "300".to_string())
340 .parse()
341 .unwrap_or(300)
342 }
343
344 pub fn get_metrics_port() -> u16 {
346 env::var("METRICS_PORT")
347 .unwrap_or_else(|_| "8081".to_string())
348 .parse()
349 .unwrap_or(8081)
350 }
351
352 pub fn get_enable_swagger() -> bool {
354 env::var("ENABLE_SWAGGER")
355 .map(|v| v.to_lowercase() == "true")
356 .unwrap_or(false)
357 }
358
359 pub fn get_redis_connection_timeout_ms() -> u64 {
361 env::var("REDIS_CONNECTION_TIMEOUT_MS")
362 .unwrap_or_else(|_| "10000".to_string())
363 .parse()
364 .unwrap_or(10000)
365 }
366
367 pub fn get_redis_key_prefix() -> String {
369 env::var("REDIS_KEY_PREFIX").unwrap_or_else(|_| "oz-relayer".to_string())
370 }
371
372 pub fn get_redis_pool_max_size() -> usize {
375 env::var("REDIS_POOL_MAX_SIZE")
376 .unwrap_or_else(|_| "500".to_string())
377 .parse()
378 .ok()
379 .filter(|&v| v > 0)
380 .unwrap_or(500)
381 }
382
383 pub fn get_redis_reader_pool_max_size() -> usize {
386 env::var("REDIS_READER_POOL_MAX_SIZE")
387 .ok()
388 .and_then(|v| v.parse().ok())
389 .filter(|&v| v > 0)
390 .unwrap_or(1000)
391 }
392
393 pub fn get_redis_pool_timeout_ms() -> u64 {
396 env::var("REDIS_POOL_TIMEOUT_MS")
397 .unwrap_or_else(|_| "10000".to_string())
398 .parse()
399 .ok()
400 .filter(|&v| v > 0)
401 .unwrap_or(10000)
402 }
403
404 pub fn get_rpc_timeout_ms() -> u64 {
406 env::var("RPC_TIMEOUT_MS")
407 .unwrap_or_else(|_| "10000".to_string())
408 .parse()
409 .unwrap_or(10000)
410 }
411
412 pub fn get_provider_max_retries() -> u8 {
414 env::var("PROVIDER_MAX_RETRIES")
415 .unwrap_or_else(|_| "3".to_string())
416 .parse()
417 .unwrap_or(3)
418 }
419
420 pub fn get_provider_retry_base_delay_ms() -> u64 {
422 env::var("PROVIDER_RETRY_BASE_DELAY_MS")
423 .unwrap_or_else(|_| "100".to_string())
424 .parse()
425 .unwrap_or(100)
426 }
427
428 pub fn get_provider_retry_max_delay_ms() -> u64 {
430 env::var("PROVIDER_RETRY_MAX_DELAY_MS")
431 .unwrap_or_else(|_| "2000".to_string())
432 .parse()
433 .unwrap_or(2000)
434 }
435
436 pub fn get_provider_max_failovers() -> u8 {
438 env::var("PROVIDER_MAX_FAILOVERS")
439 .unwrap_or_else(|_| "3".to_string())
440 .parse()
441 .unwrap_or(3)
442 }
443
444 pub fn get_provider_failure_threshold() -> u32 {
446 env::var("PROVIDER_FAILURE_THRESHOLD")
447 .or_else(|_| env::var("RPC_FAILURE_THRESHOLD")) .unwrap_or_else(|_| DEFAULT_PROVIDER_FAILURE_THRESHOLD.to_string())
449 .parse()
450 .unwrap_or(DEFAULT_PROVIDER_FAILURE_THRESHOLD)
451 }
452
453 pub fn get_provider_pause_duration_secs() -> u64 {
458 env::var("PROVIDER_PAUSE_DURATION_SECS")
459 .or_else(|_| env::var("RPC_PAUSE_DURATION_SECS")) .unwrap_or_else(|_| DEFAULT_PROVIDER_PAUSE_DURATION_SECS.to_string())
461 .parse()
462 .unwrap_or(DEFAULT_PROVIDER_PAUSE_DURATION_SECS)
463 }
464
465 pub fn get_provider_failure_expiration_secs() -> u64 {
470 env::var("PROVIDER_FAILURE_EXPIRATION_SECS")
471 .unwrap_or_else(|_| DEFAULT_PROVIDER_FAILURE_EXPIRATION_SECS.to_string())
472 .parse()
473 .unwrap_or(DEFAULT_PROVIDER_FAILURE_EXPIRATION_SECS)
474 }
475
476 pub fn get_repository_storage_type() -> RepositoryStorageType {
478 env::var("REPOSITORY_STORAGE_TYPE")
479 .unwrap_or_else(|_| "in_memory".to_string())
480 .parse()
481 .unwrap_or(RepositoryStorageType::InMemory)
482 }
483
484 pub fn get_reset_storage_on_start() -> bool {
486 env::var("RESET_STORAGE_ON_START")
487 .map(|v| v.to_lowercase() == "true")
488 .unwrap_or(false)
489 }
490
491 pub fn get_storage_encryption_key() -> Option<SecretString> {
493 env::var("STORAGE_ENCRYPTION_KEY")
494 .map(|v| SecretString::new(&v))
495 .ok()
496 }
497
498 pub fn get_transaction_expiration_hours() -> f64 {
501 env::var("TRANSACTION_EXPIRATION_HOURS")
502 .unwrap_or_else(|_| "4".to_string())
503 .parse()
504 .unwrap_or(4.0)
505 }
506
507 pub fn get_rpc_allowed_hosts() -> Vec<String> {
509 env::var("RPC_ALLOWED_HOSTS")
510 .ok()
511 .map(|s| {
512 s.split(',')
513 .map(|host| host.trim().to_string())
514 .filter(|host| !host.is_empty())
515 .collect()
516 })
517 .unwrap_or_default()
518 }
519
520 pub fn get_rpc_block_private_ips() -> bool {
522 env::var("RPC_BLOCK_PRIVATE_IPS")
523 .map(|v| v.to_lowercase() == "true")
524 .unwrap_or(false)
525 }
526
527 pub fn get_relayer_concurrency_limit() -> usize {
529 env::var("RELAYER_CONCURRENCY_LIMIT")
530 .unwrap_or_else(|_| "100".to_string())
531 .parse()
532 .unwrap_or(100)
533 }
534
535 pub fn get_max_connections() -> usize {
537 env::var("MAX_CONNECTIONS")
538 .unwrap_or_else(|_| "256".to_string())
539 .parse()
540 .unwrap_or(256)
541 }
542
543 pub fn get_connection_backlog() -> u32 {
549 env::var("CONNECTION_BACKLOG")
550 .unwrap_or_else(|_| "511".to_string())
551 .parse()
552 .unwrap_or(511)
553 }
554
555 pub fn get_request_timeout_seconds() -> u64 {
561 env::var("REQUEST_TIMEOUT_SECONDS")
562 .unwrap_or_else(|_| "30".to_string())
563 .parse()
564 .unwrap_or(30)
565 }
566
567 pub fn get_distributed_mode() -> bool {
575 env::var("DISTRIBUTED_MODE")
576 .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
577 .unwrap_or(false)
578 }
579
580 pub fn get_stellar_mainnet_fee_forwarder_address() -> Option<String> {
585 env::var("STELLAR_MAINNET_FEE_FORWARDER_ADDRESS").ok()
586 }
587
588 pub fn get_stellar_testnet_fee_forwarder_address() -> Option<String> {
589 env::var("STELLAR_TESTNET_FEE_FORWARDER_ADDRESS").ok()
590 }
591
592 pub fn get_stellar_mainnet_soroswap_router_address() -> Option<String> {
593 env::var("STELLAR_MAINNET_SOROSWAP_ROUTER_ADDRESS").ok()
594 }
595
596 pub fn get_stellar_testnet_soroswap_router_address() -> Option<String> {
597 env::var("STELLAR_TESTNET_SOROSWAP_ROUTER_ADDRESS").ok()
598 }
599
600 pub fn get_stellar_mainnet_soroswap_factory_address() -> Option<String> {
601 env::var("STELLAR_MAINNET_SOROSWAP_FACTORY_ADDRESS").ok()
602 }
603
604 pub fn get_stellar_testnet_soroswap_factory_address() -> Option<String> {
605 env::var("STELLAR_TESTNET_SOROSWAP_FACTORY_ADDRESS").ok()
606 }
607
608 pub fn get_stellar_mainnet_soroswap_native_wrapper_address() -> Option<String> {
609 env::var("STELLAR_MAINNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS").ok()
610 }
611
612 pub fn get_stellar_testnet_soroswap_native_wrapper_address() -> Option<String> {
613 env::var("STELLAR_TESTNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS").ok()
614 }
615
616 pub fn resolve_stellar_fee_forwarder_address(is_testnet: bool) -> Option<String> {
624 if is_testnet {
625 Self::get_stellar_testnet_fee_forwarder_address()
626 } else {
627 Self::get_stellar_mainnet_fee_forwarder_address()
628 .or_else(|| non_empty_const(STELLAR_FEE_FORWARDER_MAINNET))
629 }
630 }
631
632 pub fn resolve_stellar_soroswap_router_address(is_testnet: bool) -> Option<String> {
634 if is_testnet {
635 Self::get_stellar_testnet_soroswap_router_address()
636 } else {
637 Self::get_stellar_mainnet_soroswap_router_address()
638 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_ROUTER.to_string()))
639 }
640 }
641
642 pub fn resolve_stellar_soroswap_factory_address(is_testnet: bool) -> Option<String> {
644 if is_testnet {
645 Self::get_stellar_testnet_soroswap_factory_address()
646 } else {
647 Self::get_stellar_mainnet_soroswap_factory_address()
648 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_FACTORY.to_string()))
649 }
650 }
651
652 pub fn resolve_stellar_soroswap_native_wrapper_address(is_testnet: bool) -> Option<String> {
654 if is_testnet {
655 Self::get_stellar_testnet_soroswap_native_wrapper_address()
656 } else {
657 Self::get_stellar_mainnet_soroswap_native_wrapper_address()
658 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_NATIVE_WRAPPER.to_string()))
659 }
660 }
661
662 pub fn get_worker_concurrency(worker_name: &str, default: usize) -> usize {
667 let env_var = format!(
668 "BACKGROUND_WORKER_{}_CONCURRENCY",
669 worker_name.to_uppercase()
670 );
671 env::var(&env_var)
672 .ok()
673 .and_then(|v| v.parse().ok())
674 .unwrap_or(default)
675 }
676
677 pub fn get_sqs_wait_time(queue_key: &str, default: u64) -> u64 {
684 let env_var = format!("SQS_{queue_key}_WAIT_TIME_SECONDS");
685 env::var(&env_var)
686 .ok()
687 .and_then(|v| v.parse().ok())
688 .unwrap_or(default)
689 .min(20)
690 }
691
692 pub fn get_sqs_poller_count(queue_key: &str, default: usize) -> usize {
701 let env_var = format!("SQS_{queue_key}_POLLER_COUNT");
702 env::var(&env_var)
703 .ok()
704 .and_then(|v| v.parse().ok())
705 .unwrap_or(default)
706 .max(1)
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use lazy_static::lazy_static;
714 use std::env;
715 use std::sync::Mutex;
716
717 lazy_static! {
719 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
720 }
721
722 fn setup() {
723 env::remove_var("HOST");
725 env::remove_var("APP_PORT");
726 env::remove_var("REDIS_URL");
727 env::remove_var("CONFIG_DIR");
728 env::remove_var("CONFIG_FILE_NAME");
729 env::remove_var("CONFIG_FILE_PATH");
730 env::remove_var("API_KEY");
731 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
732 env::remove_var("RATE_LIMIT_BURST_SIZE");
733 env::remove_var("METRICS_PORT");
734 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
735 env::remove_var("RPC_TIMEOUT_MS");
736 env::remove_var("PROVIDER_MAX_RETRIES");
737 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
738 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
739 env::remove_var("PROVIDER_MAX_FAILOVERS");
740 env::remove_var("REPOSITORY_STORAGE_TYPE");
741 env::remove_var("RESET_STORAGE_ON_START");
742 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
743 env::remove_var("REDIS_READER_URL");
744 env::set_var("REDIS_URL", "redis://localhost:6379");
746 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
747 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
748 }
749
750 #[test]
751 fn test_default_values() {
752 let _lock = match ENV_MUTEX.lock() {
753 Ok(guard) => guard,
754 Err(poisoned) => poisoned.into_inner(),
755 };
756 setup();
757
758 let config = ServerConfig::from_env();
759
760 assert_eq!(config.host, "0.0.0.0");
761 assert_eq!(config.port, 8080);
762 assert_eq!(config.redis_url, "redis://localhost:6379");
763 assert_eq!(config.config_file_path, "./config/config.json");
764 assert_eq!(
765 config.api_key,
766 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
767 );
768 assert_eq!(config.rate_limit_requests_per_second, 100);
769 assert_eq!(config.rate_limit_burst_size, 300);
770 assert_eq!(config.metrics_port, 8081);
771 assert_eq!(config.redis_connection_timeout_ms, 5000);
772 assert_eq!(config.rpc_timeout_ms, 10000);
773 assert_eq!(config.provider_max_retries, 3);
774 assert_eq!(config.provider_retry_base_delay_ms, 100);
775 assert_eq!(config.provider_retry_max_delay_ms, 2000);
776 assert_eq!(config.provider_max_failovers, 3);
777 assert_eq!(config.provider_failure_threshold, 3);
778 assert_eq!(config.provider_pause_duration_secs, 60);
779 assert_eq!(
780 config.repository_storage_type,
781 RepositoryStorageType::InMemory
782 );
783 assert!(!config.reset_storage_on_start);
784 assert_eq!(config.transaction_expiration_hours, 4.0);
785 }
786
787 #[test]
788 fn test_invalid_port_values() {
789 let _lock = match ENV_MUTEX.lock() {
790 Ok(guard) => guard,
791 Err(poisoned) => poisoned.into_inner(),
792 };
793 setup();
794 env::set_var("REDIS_URL", "redis://localhost:6379");
795 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
796 env::set_var("APP_PORT", "not_a_number");
797 env::set_var("METRICS_PORT", "also_not_a_number");
798 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "invalid");
799 env::set_var("RATE_LIMIT_BURST_SIZE", "invalid");
800 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "invalid");
801 env::set_var("RPC_TIMEOUT_MS", "invalid");
802 env::set_var("PROVIDER_MAX_RETRIES", "invalid");
803 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "invalid");
804 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "invalid");
805 env::set_var("PROVIDER_MAX_FAILOVERS", "invalid");
806 env::set_var("REPOSITORY_STORAGE_TYPE", "invalid");
807 env::set_var("RESET_STORAGE_ON_START", "invalid");
808 env::set_var("TRANSACTION_EXPIRATION_HOURS", "invalid");
809 let config = ServerConfig::from_env();
810
811 assert_eq!(config.port, 8080);
813 assert_eq!(config.metrics_port, 8081);
814 assert_eq!(config.rate_limit_requests_per_second, 100);
815 assert_eq!(config.rate_limit_burst_size, 300);
816 assert_eq!(config.redis_connection_timeout_ms, 10000);
817 assert_eq!(config.rpc_timeout_ms, 10000);
818 assert_eq!(config.provider_max_retries, 3);
819 assert_eq!(config.provider_retry_base_delay_ms, 100);
820 assert_eq!(config.provider_retry_max_delay_ms, 2000);
821 assert_eq!(config.provider_max_failovers, 3);
822 assert_eq!(
823 config.repository_storage_type,
824 RepositoryStorageType::InMemory
825 );
826 assert!(!config.reset_storage_on_start);
827 assert_eq!(config.transaction_expiration_hours, 4.0);
828 }
829
830 #[test]
831 fn test_custom_values() {
832 let _lock = match ENV_MUTEX.lock() {
833 Ok(guard) => guard,
834 Err(poisoned) => poisoned.into_inner(),
835 };
836 setup();
837
838 env::set_var("HOST", "127.0.0.1");
839 env::set_var("APP_PORT", "9090");
840 env::set_var("REDIS_URL", "redis://custom:6379");
841 env::set_var("CONFIG_DIR", "custom");
842 env::set_var("CONFIG_FILE_NAME", "path.json");
843 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
844 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "200");
845 env::set_var("RATE_LIMIT_BURST_SIZE", "500");
846 env::set_var("METRICS_PORT", "9091");
847 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "10000");
848 env::set_var("RPC_TIMEOUT_MS", "33333");
849 env::set_var("PROVIDER_MAX_RETRIES", "5");
850 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
851 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "3000");
852 env::set_var("PROVIDER_MAX_FAILOVERS", "4");
853 env::set_var("REPOSITORY_STORAGE_TYPE", "in_memory");
854 env::set_var("RESET_STORAGE_ON_START", "true");
855 env::set_var("TRANSACTION_EXPIRATION_HOURS", "6");
856 let config = ServerConfig::from_env();
857
858 assert_eq!(config.host, "127.0.0.1");
859 assert_eq!(config.port, 9090);
860 assert_eq!(config.redis_url, "redis://custom:6379");
861 assert_eq!(config.config_file_path, "custom/path.json");
862 assert_eq!(
863 config.api_key,
864 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
865 );
866 assert_eq!(config.rate_limit_requests_per_second, 200);
867 assert_eq!(config.rate_limit_burst_size, 500);
868 assert_eq!(config.metrics_port, 9091);
869 assert_eq!(config.redis_connection_timeout_ms, 10000);
870 assert_eq!(config.rpc_timeout_ms, 33333);
871 assert_eq!(config.provider_max_retries, 5);
872 assert_eq!(config.provider_retry_base_delay_ms, 200);
873 assert_eq!(config.provider_retry_max_delay_ms, 3000);
874 assert_eq!(config.provider_max_failovers, 4);
875 assert_eq!(
876 config.repository_storage_type,
877 RepositoryStorageType::InMemory
878 );
879 assert!(config.reset_storage_on_start);
880 assert_eq!(config.transaction_expiration_hours, 6.0);
881 }
882
883 #[test]
884 #[should_panic(expected = "Security error: API_KEY must be at least 32 characters long")]
885 fn test_invalid_api_key_length() {
886 let _lock = match ENV_MUTEX.lock() {
887 Ok(guard) => guard,
888 Err(poisoned) => poisoned.into_inner(),
889 };
890 setup();
891 env::set_var("REDIS_URL", "redis://localhost:6379");
892 env::set_var("API_KEY", "insufficient_length");
893 env::set_var("APP_PORT", "8080");
894 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "100");
895 env::set_var("RATE_LIMIT_BURST_SIZE", "300");
896 env::set_var("METRICS_PORT", "9091");
897 env::set_var("TRANSACTION_EXPIRATION_HOURS", "4");
898
899 let _ = ServerConfig::from_env();
900
901 panic!("Test should have panicked before reaching here");
902 }
903
904 #[test]
906 fn test_individual_getters_with_defaults() {
907 let _lock = match ENV_MUTEX.lock() {
908 Ok(guard) => guard,
909 Err(poisoned) => poisoned.into_inner(),
910 };
911
912 env::remove_var("HOST");
914 env::remove_var("APP_PORT");
915 env::remove_var("REDIS_URL");
916 env::remove_var("CONFIG_DIR");
917 env::remove_var("CONFIG_FILE_NAME");
918 env::remove_var("API_KEY");
919 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
920 env::remove_var("RATE_LIMIT_BURST_SIZE");
921 env::remove_var("METRICS_PORT");
922 env::remove_var("ENABLE_SWAGGER");
923 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
924 env::remove_var("REDIS_KEY_PREFIX");
925 env::remove_var("REDIS_READER_URL");
926 env::remove_var("RPC_TIMEOUT_MS");
927 env::remove_var("PROVIDER_MAX_RETRIES");
928 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
929 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
930 env::remove_var("PROVIDER_MAX_FAILOVERS");
931 env::remove_var("REPOSITORY_STORAGE_TYPE");
932 env::remove_var("RESET_STORAGE_ON_START");
933 env::remove_var("STORAGE_ENCRYPTION_KEY");
934 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
935 env::remove_var("REDIS_POOL_MAX_SIZE");
936 env::remove_var("REDIS_POOL_TIMEOUT_MS");
937
938 assert_eq!(ServerConfig::get_host(), "0.0.0.0");
940 assert_eq!(ServerConfig::get_port(), 8080);
941 assert_eq!(ServerConfig::get_redis_url_optional(), None);
942 assert_eq!(ServerConfig::get_config_file_path(), "./config/config.json");
943 assert_eq!(ServerConfig::get_api_key_optional(), None);
944 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 100);
945 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 300);
946 assert_eq!(ServerConfig::get_metrics_port(), 8081);
947 assert!(!ServerConfig::get_enable_swagger());
948 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 10000);
949 assert_eq!(ServerConfig::get_redis_key_prefix(), "oz-relayer");
950 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 10000);
951 assert_eq!(ServerConfig::get_provider_max_retries(), 3);
952 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 100);
953 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 2000);
954 assert_eq!(ServerConfig::get_provider_max_failovers(), 3);
955 assert_eq!(
956 ServerConfig::get_repository_storage_type(),
957 RepositoryStorageType::InMemory
958 );
959 assert!(!ServerConfig::get_reset_storage_on_start());
960 assert!(ServerConfig::get_storage_encryption_key().is_none());
961 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 4.0);
962 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
963 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
964 }
965
966 #[test]
967 fn test_individual_getters_with_custom_values() {
968 let _lock = match ENV_MUTEX.lock() {
969 Ok(guard) => guard,
970 Err(poisoned) => poisoned.into_inner(),
971 };
972
973 env::set_var("HOST", "192.168.1.1");
975 env::set_var("APP_PORT", "9999");
976 env::set_var("REDIS_URL", "redis://custom:6379");
977 env::set_var("CONFIG_DIR", "/custom/config");
978 env::set_var("CONFIG_FILE_NAME", "custom.json");
979 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
980 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "500");
981 env::set_var("RATE_LIMIT_BURST_SIZE", "1000");
982 env::set_var("METRICS_PORT", "9999");
983 env::set_var("ENABLE_SWAGGER", "true");
984 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
985 env::set_var("REDIS_KEY_PREFIX", "custom-prefix");
986 env::set_var("RPC_TIMEOUT_MS", "15000");
987 env::set_var("PROVIDER_MAX_RETRIES", "5");
988 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
989 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "5000");
990 env::set_var("PROVIDER_MAX_FAILOVERS", "10");
991 env::set_var("REPOSITORY_STORAGE_TYPE", "redis");
992 env::set_var("RESET_STORAGE_ON_START", "true");
993 env::set_var("STORAGE_ENCRYPTION_KEY", "my-encryption-key");
994 env::set_var("TRANSACTION_EXPIRATION_HOURS", "12");
995 env::set_var("REDIS_POOL_MAX_SIZE", "200");
996 env::set_var("REDIS_POOL_TIMEOUT_MS", "20000");
997
998 assert_eq!(ServerConfig::get_host(), "192.168.1.1");
1000 assert_eq!(ServerConfig::get_port(), 9999);
1001 assert_eq!(
1002 ServerConfig::get_redis_url_optional(),
1003 Some("redis://custom:6379".to_string())
1004 );
1005 assert_eq!(
1006 ServerConfig::get_config_file_path(),
1007 "/custom/config/custom.json"
1008 );
1009 assert!(ServerConfig::get_api_key_optional().is_some());
1010 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 500);
1011 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 1000);
1012 assert_eq!(ServerConfig::get_metrics_port(), 9999);
1013 assert!(ServerConfig::get_enable_swagger());
1014 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 5000);
1015 assert_eq!(ServerConfig::get_redis_key_prefix(), "custom-prefix");
1016 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 15000);
1017 assert_eq!(ServerConfig::get_provider_max_retries(), 5);
1018 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 200);
1019 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 5000);
1020 assert_eq!(ServerConfig::get_provider_max_failovers(), 10);
1021 assert_eq!(
1022 ServerConfig::get_repository_storage_type(),
1023 RepositoryStorageType::Redis
1024 );
1025 assert!(ServerConfig::get_reset_storage_on_start());
1026 assert!(ServerConfig::get_storage_encryption_key().is_some());
1027 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 12.0);
1028 assert_eq!(ServerConfig::get_redis_pool_max_size(), 200);
1029 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 20000);
1030 }
1031
1032 #[test]
1033 fn test_get_redis_pool_max_size() {
1034 let _lock = match ENV_MUTEX.lock() {
1035 Ok(guard) => guard,
1036 Err(poisoned) => poisoned.into_inner(),
1037 };
1038 env::remove_var("REDIS_POOL_MAX_SIZE");
1040 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1041
1042 env::set_var("REDIS_POOL_MAX_SIZE", "100");
1044 assert_eq!(ServerConfig::get_redis_pool_max_size(), 100);
1045
1046 env::set_var("REDIS_POOL_MAX_SIZE", "not_a_number");
1048 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1049
1050 env::set_var("REDIS_POOL_MAX_SIZE", "0");
1052 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1053
1054 env::set_var("REDIS_POOL_MAX_SIZE", "10000");
1056 assert_eq!(ServerConfig::get_redis_pool_max_size(), 10000);
1057
1058 env::remove_var("REDIS_POOL_MAX_SIZE");
1060 }
1061
1062 #[test]
1063 fn test_get_redis_pool_timeout_ms() {
1064 let _lock = match ENV_MUTEX.lock() {
1065 Ok(guard) => guard,
1066 Err(poisoned) => poisoned.into_inner(),
1067 };
1068
1069 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1071 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1072
1073 env::set_var("REDIS_POOL_TIMEOUT_MS", "15000");
1075 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 15000);
1076
1077 env::set_var("REDIS_POOL_TIMEOUT_MS", "not_a_number");
1079 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1080
1081 env::set_var("REDIS_POOL_TIMEOUT_MS", "0");
1083 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1084
1085 env::set_var("REDIS_POOL_TIMEOUT_MS", "60000");
1087 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 60000);
1088
1089 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1091 }
1092
1093 #[test]
1094 fn test_fractional_transaction_expiration_hours() {
1095 let _lock = match ENV_MUTEX.lock() {
1096 Ok(guard) => guard,
1097 Err(poisoned) => poisoned.into_inner(),
1098 };
1099 setup();
1100
1101 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.1");
1103 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.1);
1104
1105 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.5");
1107 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.5);
1108
1109 env::set_var("TRANSACTION_EXPIRATION_HOURS", "24");
1111 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 24.0);
1112
1113 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
1115 }
1116
1117 #[test]
1118 #[should_panic(expected = "REDIS_URL must be set")]
1119 fn test_get_redis_url_panics_when_not_set() {
1120 let _lock = match ENV_MUTEX.lock() {
1121 Ok(guard) => guard,
1122 Err(poisoned) => poisoned.into_inner(),
1123 };
1124
1125 env::remove_var("REDIS_URL");
1126 let _ = ServerConfig::get_redis_url();
1127 }
1128
1129 #[test]
1130 #[should_panic(expected = "API_KEY must be set")]
1131 fn test_get_api_key_panics_when_not_set() {
1132 let _lock = match ENV_MUTEX.lock() {
1133 Ok(guard) => guard,
1134 Err(poisoned) => poisoned.into_inner(),
1135 };
1136
1137 env::remove_var("API_KEY");
1138 let _ = ServerConfig::get_api_key();
1139 }
1140
1141 #[test]
1142 fn test_optional_getters_return_none_safely() {
1143 let _lock = match ENV_MUTEX.lock() {
1144 Ok(guard) => guard,
1145 Err(poisoned) => poisoned.into_inner(),
1146 };
1147
1148 env::remove_var("REDIS_URL");
1149 env::remove_var("API_KEY");
1150 env::remove_var("STORAGE_ENCRYPTION_KEY");
1151
1152 assert!(ServerConfig::get_redis_url_optional().is_none());
1153 assert!(ServerConfig::get_api_key_optional().is_none());
1154 assert!(ServerConfig::get_storage_encryption_key().is_none());
1155 }
1156
1157 #[test]
1158 fn test_refactored_from_env_equivalence() {
1159 let _lock = match ENV_MUTEX.lock() {
1160 Ok(guard) => guard,
1161 Err(poisoned) => poisoned.into_inner(),
1162 };
1163 setup();
1164
1165 env::set_var("HOST", "custom-host");
1167 env::set_var("APP_PORT", "7777");
1168 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "250");
1169 env::set_var("METRICS_PORT", "7778");
1170 env::set_var("ENABLE_SWAGGER", "true");
1171 env::set_var("PROVIDER_MAX_RETRIES", "7");
1172 env::set_var("TRANSACTION_EXPIRATION_HOURS", "8");
1173
1174 let config = ServerConfig::from_env();
1175
1176 assert_eq!(config.host, ServerConfig::get_host());
1178 assert_eq!(config.port, ServerConfig::get_port());
1179 assert_eq!(config.redis_url, ServerConfig::get_redis_url());
1180 assert_eq!(
1181 config.config_file_path,
1182 ServerConfig::get_config_file_path()
1183 );
1184 assert_eq!(config.api_key, ServerConfig::get_api_key());
1185 assert_eq!(
1186 config.rate_limit_requests_per_second,
1187 ServerConfig::get_rate_limit_requests_per_second()
1188 );
1189 assert_eq!(
1190 config.rate_limit_burst_size,
1191 ServerConfig::get_rate_limit_burst_size()
1192 );
1193 assert_eq!(config.metrics_port, ServerConfig::get_metrics_port());
1194 assert_eq!(config.enable_swagger, ServerConfig::get_enable_swagger());
1195 assert_eq!(
1196 config.redis_connection_timeout_ms,
1197 ServerConfig::get_redis_connection_timeout_ms()
1198 );
1199 assert_eq!(
1200 config.redis_key_prefix,
1201 ServerConfig::get_redis_key_prefix()
1202 );
1203 assert_eq!(config.rpc_timeout_ms, ServerConfig::get_rpc_timeout_ms());
1204 assert_eq!(
1205 config.provider_max_retries,
1206 ServerConfig::get_provider_max_retries()
1207 );
1208 assert_eq!(
1209 config.provider_retry_base_delay_ms,
1210 ServerConfig::get_provider_retry_base_delay_ms()
1211 );
1212 assert_eq!(
1213 config.provider_retry_max_delay_ms,
1214 ServerConfig::get_provider_retry_max_delay_ms()
1215 );
1216 assert_eq!(
1217 config.provider_max_failovers,
1218 ServerConfig::get_provider_max_failovers()
1219 );
1220 assert_eq!(
1221 config.repository_storage_type,
1222 ServerConfig::get_repository_storage_type()
1223 );
1224 assert_eq!(
1225 config.reset_storage_on_start,
1226 ServerConfig::get_reset_storage_on_start()
1227 );
1228 assert_eq!(
1229 config.storage_encryption_key,
1230 ServerConfig::get_storage_encryption_key()
1231 );
1232 assert_eq!(
1233 config.transaction_expiration_hours,
1234 ServerConfig::get_transaction_expiration_hours()
1235 );
1236 }
1237
1238 mod get_worker_concurrency_tests {
1239 use super::*;
1240 use serial_test::serial;
1241
1242 #[test]
1243 #[serial]
1244 fn test_returns_default_when_env_not_set() {
1245 let worker_name = "test_worker";
1246 let env_var = format!(
1247 "BACKGROUND_WORKER_{}_CONCURRENCY",
1248 worker_name.to_uppercase()
1249 );
1250
1251 env::remove_var(&env_var);
1253
1254 let default_value = 42;
1255 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1256
1257 assert_eq!(
1258 result, default_value,
1259 "Should return default value when env var is not set"
1260 );
1261 }
1262
1263 #[test]
1264 #[serial]
1265 fn test_returns_env_value_when_set() {
1266 let worker_name = "status_checker";
1267 let env_var = format!(
1268 "BACKGROUND_WORKER_{}_CONCURRENCY",
1269 worker_name.to_uppercase()
1270 );
1271
1272 env::set_var(&env_var, "100");
1274
1275 let default_value = 10;
1276 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1277
1278 assert_eq!(result, 100, "Should return env var value when set");
1279
1280 env::remove_var(&env_var);
1282 }
1283
1284 #[test]
1285 #[serial]
1286 fn test_returns_default_when_env_invalid() {
1287 let worker_name = "invalid_worker";
1288 let env_var = format!(
1289 "BACKGROUND_WORKER_{}_CONCURRENCY",
1290 worker_name.to_uppercase()
1291 );
1292
1293 env::set_var(&env_var, "not_a_number");
1295
1296 let default_value = 25;
1297 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1298
1299 assert_eq!(
1300 result, default_value,
1301 "Should return default value when env var is invalid"
1302 );
1303
1304 env::remove_var(&env_var);
1306 }
1307
1308 #[test]
1309 #[serial]
1310 fn test_returns_default_when_env_empty() {
1311 let worker_name = "empty_worker";
1312 let env_var = format!(
1313 "BACKGROUND_WORKER_{}_CONCURRENCY",
1314 worker_name.to_uppercase()
1315 );
1316
1317 env::set_var(&env_var, "");
1319
1320 let default_value = 15;
1321 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1322
1323 assert_eq!(
1324 result, default_value,
1325 "Should return default value when env var is empty"
1326 );
1327
1328 env::remove_var(&env_var);
1330 }
1331
1332 #[test]
1333 #[serial]
1334 fn test_returns_default_when_env_negative() {
1335 let worker_name = "negative_worker";
1336 let env_var = format!(
1337 "BACKGROUND_WORKER_{}_CONCURRENCY",
1338 worker_name.to_uppercase()
1339 );
1340
1341 env::set_var(&env_var, "-5");
1343
1344 let default_value = 20;
1345 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1346
1347 assert_eq!(
1348 result, default_value,
1349 "Should return default value when env var is negative"
1350 );
1351
1352 env::remove_var(&env_var);
1354 }
1355
1356 #[test]
1357 #[serial]
1358 fn test_env_var_name_formatting() {
1359 let worker_names = vec![
1361 (
1362 "transaction_sender",
1363 "BACKGROUND_WORKER_TRANSACTION_SENDER_CONCURRENCY",
1364 ),
1365 (
1366 "status_checker_evm",
1367 "BACKGROUND_WORKER_STATUS_CHECKER_EVM_CONCURRENCY",
1368 ),
1369 (
1370 "notification_sender",
1371 "BACKGROUND_WORKER_NOTIFICATION_SENDER_CONCURRENCY",
1372 ),
1373 ];
1374
1375 for (worker_name, expected_env_var) in worker_names {
1376 let actual_env_var = format!(
1377 "BACKGROUND_WORKER_{}_CONCURRENCY",
1378 worker_name.to_uppercase()
1379 );
1380 assert_eq!(
1381 actual_env_var, expected_env_var,
1382 "Env var name should be correctly formatted for worker: {worker_name}"
1383 );
1384 }
1385 }
1386
1387 #[test]
1388 #[serial]
1389 fn test_zero_value() {
1390 let worker_name = "zero_worker";
1391 let env_var = format!(
1392 "BACKGROUND_WORKER_{}_CONCURRENCY",
1393 worker_name.to_uppercase()
1394 );
1395
1396 env::set_var(&env_var, "0");
1398
1399 let default_value = 30;
1400 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1401
1402 assert_eq!(result, 0, "Should accept zero as a valid value");
1403
1404 env::remove_var(&env_var);
1406 }
1407
1408 #[test]
1409 #[serial]
1410 fn test_large_value() {
1411 let worker_name = "large_worker";
1412 let env_var = format!(
1413 "BACKGROUND_WORKER_{}_CONCURRENCY",
1414 worker_name.to_uppercase()
1415 );
1416
1417 env::set_var(&env_var, "10000");
1419
1420 let default_value = 50;
1421 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1422
1423 assert_eq!(result, 10000, "Should accept large values");
1424
1425 env::remove_var(&env_var);
1427 }
1428
1429 #[test]
1430 #[serial]
1431 fn test_whitespace_in_value() {
1432 let worker_name = "whitespace_worker";
1433 let env_var = format!(
1434 "BACKGROUND_WORKER_{}_CONCURRENCY",
1435 worker_name.to_uppercase()
1436 );
1437
1438 env::set_var(&env_var, " 75 ");
1440
1441 let default_value = 35;
1442 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1443
1444 assert_eq!(
1447 result, default_value,
1448 "Should return default value when value has whitespace"
1449 );
1450
1451 env::remove_var(&env_var);
1453 }
1454
1455 #[test]
1456 #[serial]
1457 fn test_float_value_returns_default() {
1458 let worker_name = "float_worker";
1459 let env_var = format!(
1460 "BACKGROUND_WORKER_{}_CONCURRENCY",
1461 worker_name.to_uppercase()
1462 );
1463
1464 env::set_var(&env_var, "12.5");
1466
1467 let default_value = 40;
1468 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1469
1470 assert_eq!(
1471 result, default_value,
1472 "Should return default value for float input"
1473 );
1474
1475 env::remove_var(&env_var);
1477 }
1478 }
1479
1480 mod get_relayer_concurrency_limit_tests {
1481 use super::*;
1482 use serial_test::serial;
1483
1484 #[test]
1485 #[serial]
1486 fn test_returns_default_when_env_not_set() {
1487 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1488 let result = ServerConfig::get_relayer_concurrency_limit();
1489 assert_eq!(result, 100, "Should return default value of 100");
1490 }
1491
1492 #[test]
1493 #[serial]
1494 fn test_returns_env_value_when_set() {
1495 env::set_var("RELAYER_CONCURRENCY_LIMIT", "250");
1496 let result = ServerConfig::get_relayer_concurrency_limit();
1497 assert_eq!(result, 250, "Should return env var value");
1498 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1499 }
1500
1501 #[test]
1502 #[serial]
1503 fn test_returns_default_when_env_invalid() {
1504 env::set_var("RELAYER_CONCURRENCY_LIMIT", "not_a_number");
1505 let result = ServerConfig::get_relayer_concurrency_limit();
1506 assert_eq!(result, 100, "Should return default value when invalid");
1507 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1508 }
1509
1510 #[test]
1511 #[serial]
1512 fn test_returns_default_when_env_empty() {
1513 env::set_var("RELAYER_CONCURRENCY_LIMIT", "");
1514 let result = ServerConfig::get_relayer_concurrency_limit();
1515 assert_eq!(result, 100, "Should return default value when empty");
1516 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1517 }
1518
1519 #[test]
1520 #[serial]
1521 fn test_zero_value() {
1522 env::set_var("RELAYER_CONCURRENCY_LIMIT", "0");
1523 let result = ServerConfig::get_relayer_concurrency_limit();
1524 assert_eq!(result, 0, "Should accept zero as valid value");
1525 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1526 }
1527
1528 #[test]
1529 #[serial]
1530 fn test_large_value() {
1531 env::set_var("RELAYER_CONCURRENCY_LIMIT", "5000");
1532 let result = ServerConfig::get_relayer_concurrency_limit();
1533 assert_eq!(result, 5000, "Should accept large values");
1534 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1535 }
1536
1537 #[test]
1538 #[serial]
1539 fn test_negative_value_returns_default() {
1540 env::set_var("RELAYER_CONCURRENCY_LIMIT", "-10");
1541 let result = ServerConfig::get_relayer_concurrency_limit();
1542 assert_eq!(result, 100, "Should return default for negative value");
1543 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1544 }
1545
1546 #[test]
1547 #[serial]
1548 fn test_float_value_returns_default() {
1549 env::set_var("RELAYER_CONCURRENCY_LIMIT", "100.5");
1550 let result = ServerConfig::get_relayer_concurrency_limit();
1551 assert_eq!(result, 100, "Should return default for float value");
1552 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1553 }
1554
1555 #[test]
1556 #[serial]
1557 fn test_whitespace_value_returns_default() {
1558 env::set_var("RELAYER_CONCURRENCY_LIMIT", " 150 ");
1559 let result = ServerConfig::get_relayer_concurrency_limit();
1560 assert_eq!(
1561 result, 100,
1562 "Should return default when value has whitespace"
1563 );
1564 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1565 }
1566 }
1567
1568 mod get_max_connections_tests {
1569 use super::*;
1570 use serial_test::serial;
1571
1572 #[test]
1573 #[serial]
1574 fn test_returns_default_when_env_not_set() {
1575 env::remove_var("MAX_CONNECTIONS");
1576 let result = ServerConfig::get_max_connections();
1577 assert_eq!(result, 256, "Should return default value of 256");
1578 }
1579
1580 #[test]
1581 #[serial]
1582 fn test_returns_env_value_when_set() {
1583 env::set_var("MAX_CONNECTIONS", "512");
1584 let result = ServerConfig::get_max_connections();
1585 assert_eq!(result, 512, "Should return env var value");
1586 env::remove_var("MAX_CONNECTIONS");
1587 }
1588
1589 #[test]
1590 #[serial]
1591 fn test_returns_default_when_env_invalid() {
1592 env::set_var("MAX_CONNECTIONS", "invalid");
1593 let result = ServerConfig::get_max_connections();
1594 assert_eq!(result, 256, "Should return default value when invalid");
1595 env::remove_var("MAX_CONNECTIONS");
1596 }
1597
1598 #[test]
1599 #[serial]
1600 fn test_returns_default_when_env_empty() {
1601 env::set_var("MAX_CONNECTIONS", "");
1602 let result = ServerConfig::get_max_connections();
1603 assert_eq!(result, 256, "Should return default value when empty");
1604 env::remove_var("MAX_CONNECTIONS");
1605 }
1606
1607 #[test]
1608 #[serial]
1609 fn test_zero_value() {
1610 env::set_var("MAX_CONNECTIONS", "0");
1611 let result = ServerConfig::get_max_connections();
1612 assert_eq!(result, 0, "Should accept zero as valid value");
1613 env::remove_var("MAX_CONNECTIONS");
1614 }
1615
1616 #[test]
1617 #[serial]
1618 fn test_large_value() {
1619 env::set_var("MAX_CONNECTIONS", "10000");
1620 let result = ServerConfig::get_max_connections();
1621 assert_eq!(result, 10000, "Should accept large values");
1622 env::remove_var("MAX_CONNECTIONS");
1623 }
1624
1625 #[test]
1626 #[serial]
1627 fn test_negative_value_returns_default() {
1628 env::set_var("MAX_CONNECTIONS", "-100");
1629 let result = ServerConfig::get_max_connections();
1630 assert_eq!(result, 256, "Should return default for negative value");
1631 env::remove_var("MAX_CONNECTIONS");
1632 }
1633
1634 #[test]
1635 #[serial]
1636 fn test_float_value_returns_default() {
1637 env::set_var("MAX_CONNECTIONS", "256.5");
1638 let result = ServerConfig::get_max_connections();
1639 assert_eq!(result, 256, "Should return default for float value");
1640 env::remove_var("MAX_CONNECTIONS");
1641 }
1642 }
1643
1644 mod get_connection_backlog_tests {
1645 use super::*;
1646 use serial_test::serial;
1647
1648 #[test]
1649 #[serial]
1650 fn test_returns_default_when_env_not_set() {
1651 env::remove_var("CONNECTION_BACKLOG");
1652 let result = ServerConfig::get_connection_backlog();
1653 assert_eq!(result, 511, "Should return default value of 511");
1654 }
1655
1656 #[test]
1657 #[serial]
1658 fn test_returns_env_value_when_set() {
1659 env::set_var("CONNECTION_BACKLOG", "1024");
1660 let result = ServerConfig::get_connection_backlog();
1661 assert_eq!(result, 1024, "Should return env var value");
1662 env::remove_var("CONNECTION_BACKLOG");
1663 }
1664
1665 #[test]
1666 #[serial]
1667 fn test_returns_default_when_env_invalid() {
1668 env::set_var("CONNECTION_BACKLOG", "not_a_number");
1669 let result = ServerConfig::get_connection_backlog();
1670 assert_eq!(result, 511, "Should return default value when invalid");
1671 env::remove_var("CONNECTION_BACKLOG");
1672 }
1673
1674 #[test]
1675 #[serial]
1676 fn test_returns_default_when_env_empty() {
1677 env::set_var("CONNECTION_BACKLOG", "");
1678 let result = ServerConfig::get_connection_backlog();
1679 assert_eq!(result, 511, "Should return default value when empty");
1680 env::remove_var("CONNECTION_BACKLOG");
1681 }
1682
1683 #[test]
1684 #[serial]
1685 fn test_zero_value() {
1686 env::set_var("CONNECTION_BACKLOG", "0");
1687 let result = ServerConfig::get_connection_backlog();
1688 assert_eq!(result, 0, "Should accept zero as valid value");
1689 env::remove_var("CONNECTION_BACKLOG");
1690 }
1691
1692 #[test]
1693 #[serial]
1694 fn test_large_value() {
1695 env::set_var("CONNECTION_BACKLOG", "65535");
1696 let result = ServerConfig::get_connection_backlog();
1697 assert_eq!(result, 65535, "Should accept large values");
1698 env::remove_var("CONNECTION_BACKLOG");
1699 }
1700
1701 #[test]
1702 #[serial]
1703 fn test_negative_value_returns_default() {
1704 env::set_var("CONNECTION_BACKLOG", "-50");
1705 let result = ServerConfig::get_connection_backlog();
1706 assert_eq!(result, 511, "Should return default for negative value");
1707 env::remove_var("CONNECTION_BACKLOG");
1708 }
1709
1710 #[test]
1711 #[serial]
1712 fn test_float_value_returns_default() {
1713 env::set_var("CONNECTION_BACKLOG", "511.5");
1714 let result = ServerConfig::get_connection_backlog();
1715 assert_eq!(result, 511, "Should return default for float value");
1716 env::remove_var("CONNECTION_BACKLOG");
1717 }
1718
1719 #[test]
1720 #[serial]
1721 fn test_common_production_values() {
1722 let test_cases = vec![
1724 (128, "Small server"),
1725 (511, "Default"),
1726 (1024, "Medium server"),
1727 (2048, "Large server"),
1728 ];
1729
1730 for (value, description) in test_cases {
1731 env::set_var("CONNECTION_BACKLOG", value.to_string());
1732 let result = ServerConfig::get_connection_backlog();
1733 assert_eq!(result, value, "Should accept {description}: {value}");
1734 }
1735
1736 env::remove_var("CONNECTION_BACKLOG");
1737 }
1738 }
1739
1740 mod get_request_timeout_seconds_tests {
1741 use super::*;
1742 use serial_test::serial;
1743
1744 #[test]
1745 #[serial]
1746 fn test_returns_default_when_env_not_set() {
1747 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1748 let result = ServerConfig::get_request_timeout_seconds();
1749 assert_eq!(result, 30, "Should return default value of 30");
1750 }
1751
1752 #[test]
1753 #[serial]
1754 fn test_returns_env_value_when_set() {
1755 env::set_var("REQUEST_TIMEOUT_SECONDS", "60");
1756 let result = ServerConfig::get_request_timeout_seconds();
1757 assert_eq!(result, 60, "Should return env var value");
1758 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1759 }
1760
1761 #[test]
1762 #[serial]
1763 fn test_returns_default_when_env_invalid() {
1764 env::set_var("REQUEST_TIMEOUT_SECONDS", "invalid");
1765 let result = ServerConfig::get_request_timeout_seconds();
1766 assert_eq!(result, 30, "Should return default value when invalid");
1767 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1768 }
1769
1770 #[test]
1771 #[serial]
1772 fn test_returns_default_when_env_empty() {
1773 env::set_var("REQUEST_TIMEOUT_SECONDS", "");
1774 let result = ServerConfig::get_request_timeout_seconds();
1775 assert_eq!(result, 30, "Should return default value when empty");
1776 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1777 }
1778
1779 #[test]
1780 #[serial]
1781 fn test_zero_value() {
1782 env::set_var("REQUEST_TIMEOUT_SECONDS", "0");
1783 let result = ServerConfig::get_request_timeout_seconds();
1784 assert_eq!(result, 0, "Should accept zero as valid value");
1785 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1786 }
1787
1788 #[test]
1789 #[serial]
1790 fn test_large_value() {
1791 env::set_var("REQUEST_TIMEOUT_SECONDS", "300");
1792 let result = ServerConfig::get_request_timeout_seconds();
1793 assert_eq!(result, 300, "Should accept large values");
1794 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1795 }
1796
1797 #[test]
1798 #[serial]
1799 fn test_negative_value_returns_default() {
1800 env::set_var("REQUEST_TIMEOUT_SECONDS", "-10");
1801 let result = ServerConfig::get_request_timeout_seconds();
1802 assert_eq!(result, 30, "Should return default for negative value");
1803 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1804 }
1805
1806 #[test]
1807 #[serial]
1808 fn test_float_value_returns_default() {
1809 env::set_var("REQUEST_TIMEOUT_SECONDS", "30.5");
1810 let result = ServerConfig::get_request_timeout_seconds();
1811 assert_eq!(result, 30, "Should return default for float value");
1812 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1813 }
1814
1815 #[test]
1816 #[serial]
1817 fn test_common_timeout_values() {
1818 let test_cases = vec![
1820 (10, "Short timeout"),
1821 (30, "Default timeout"),
1822 (60, "Moderate timeout"),
1823 (120, "Long timeout"),
1824 ];
1825
1826 for (value, description) in test_cases {
1827 env::set_var("REQUEST_TIMEOUT_SECONDS", value.to_string());
1828 let result = ServerConfig::get_request_timeout_seconds();
1829 assert_eq!(result, value, "Should accept {description}: {value}");
1830 }
1831
1832 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1833 }
1834 }
1835
1836 mod get_redis_reader_url_tests {
1837 use super::*;
1838 use serial_test::serial;
1839
1840 #[test]
1841 #[serial]
1842 fn test_returns_none_when_env_not_set() {
1843 env::remove_var("REDIS_READER_URL");
1844 let result = ServerConfig::get_redis_reader_url_optional();
1845 assert!(
1846 result.is_none(),
1847 "Should return None when env var is not set"
1848 );
1849 }
1850
1851 #[test]
1852 #[serial]
1853 fn test_returns_value_when_set() {
1854 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1855 let result = ServerConfig::get_redis_reader_url_optional();
1856 assert_eq!(
1857 result,
1858 Some("redis://reader:6379".to_string()),
1859 "Should return the env var value"
1860 );
1861 env::remove_var("REDIS_READER_URL");
1862 }
1863
1864 #[test]
1865 #[serial]
1866 fn test_returns_empty_string_when_set_to_empty() {
1867 env::set_var("REDIS_READER_URL", "");
1868 let result = ServerConfig::get_redis_reader_url_optional();
1869 assert_eq!(
1870 result,
1871 Some("".to_string()),
1872 "Should return empty string when set to empty"
1873 );
1874 env::remove_var("REDIS_READER_URL");
1875 }
1876
1877 #[test]
1878 #[serial]
1879 fn test_aws_elasticache_reader_url() {
1880 let reader_url = "redis://my-cluster-ro.xxx.cache.amazonaws.com:6379";
1882 env::set_var("REDIS_READER_URL", reader_url);
1883 let result = ServerConfig::get_redis_reader_url_optional();
1884 assert_eq!(
1885 result,
1886 Some(reader_url.to_string()),
1887 "Should accept AWS ElastiCache reader endpoint"
1888 );
1889 env::remove_var("REDIS_READER_URL");
1890 }
1891
1892 #[test]
1893 #[serial]
1894 fn test_config_includes_redis_reader_url() {
1895 env::set_var("REDIS_URL", "redis://primary:6379");
1896 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1897 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1898
1899 let config = ServerConfig::from_env();
1900
1901 assert_eq!(config.redis_url, "redis://primary:6379");
1902 assert_eq!(
1903 config.redis_reader_url,
1904 Some("redis://reader:6379".to_string())
1905 );
1906
1907 env::remove_var("REDIS_URL");
1908 env::remove_var("REDIS_READER_URL");
1909 env::remove_var("API_KEY");
1910 }
1911
1912 #[test]
1913 #[serial]
1914 fn test_config_without_redis_reader_url() {
1915 env::set_var("REDIS_URL", "redis://primary:6379");
1916 env::remove_var("REDIS_READER_URL");
1917 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1918
1919 let config = ServerConfig::from_env();
1920
1921 assert_eq!(config.redis_url, "redis://primary:6379");
1922 assert!(
1923 config.redis_reader_url.is_none(),
1924 "redis_reader_url should be None when not set"
1925 );
1926
1927 env::remove_var("REDIS_URL");
1928 env::remove_var("API_KEY");
1929 }
1930 }
1931
1932 mod get_sqs_queue_type_tests {
1933 use super::*;
1934 use serial_test::serial;
1935
1936 #[test]
1937 #[serial]
1938 fn test_returns_auto_when_env_not_set() {
1939 env::remove_var("SQS_QUEUE_TYPE");
1940 let result = ServerConfig::get_sqs_queue_type();
1941 assert_eq!(result, "auto", "Should default to 'auto'");
1942 }
1943
1944 #[test]
1945 #[serial]
1946 fn test_returns_fifo_when_set() {
1947 env::set_var("SQS_QUEUE_TYPE", "fifo");
1948 let result = ServerConfig::get_sqs_queue_type();
1949 assert_eq!(result, "fifo");
1950 env::remove_var("SQS_QUEUE_TYPE");
1951 }
1952
1953 #[test]
1954 #[serial]
1955 fn test_returns_standard_when_set() {
1956 env::set_var("SQS_QUEUE_TYPE", "standard");
1957 let result = ServerConfig::get_sqs_queue_type();
1958 assert_eq!(result, "standard");
1959 env::remove_var("SQS_QUEUE_TYPE");
1960 }
1961
1962 #[test]
1963 #[serial]
1964 fn test_returns_raw_value_for_unknown() {
1965 env::set_var("SQS_QUEUE_TYPE", "unknown");
1966 let result = ServerConfig::get_sqs_queue_type();
1967 assert_eq!(result, "unknown");
1968 env::remove_var("SQS_QUEUE_TYPE");
1969 }
1970 }
1971
1972 mod get_redis_reader_pool_max_size_tests {
1973 use super::*;
1974 use serial_test::serial;
1975
1976 #[test]
1977 #[serial]
1978 fn test_returns_default_when_env_not_set() {
1979 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1980 let result = ServerConfig::get_redis_reader_pool_max_size();
1981 assert_eq!(
1982 result, 1000,
1983 "Should return default 1000 when env var is not set"
1984 );
1985 }
1986
1987 #[test]
1988 #[serial]
1989 fn test_returns_value_when_set() {
1990 env::set_var("REDIS_READER_POOL_MAX_SIZE", "2000");
1991 let result = ServerConfig::get_redis_reader_pool_max_size();
1992 assert_eq!(result, 2000, "Should return the parsed value");
1993 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1994 }
1995
1996 #[test]
1997 #[serial]
1998 fn test_returns_default_when_invalid() {
1999 env::set_var("REDIS_READER_POOL_MAX_SIZE", "not_a_number");
2000 let result = ServerConfig::get_redis_reader_pool_max_size();
2001 assert_eq!(
2002 result, 1000,
2003 "Should return default 1000 for invalid values"
2004 );
2005 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2006 }
2007
2008 #[test]
2009 #[serial]
2010 fn test_returns_default_when_zero() {
2011 env::set_var("REDIS_READER_POOL_MAX_SIZE", "0");
2012 let result = ServerConfig::get_redis_reader_pool_max_size();
2013 assert_eq!(result, 1000, "Should return default 1000 when value is 0");
2014 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2015 }
2016
2017 #[test]
2018 #[serial]
2019 fn test_returns_default_when_negative() {
2020 env::set_var("REDIS_READER_POOL_MAX_SIZE", "-100");
2021 let result = ServerConfig::get_redis_reader_pool_max_size();
2022 assert_eq!(
2023 result, 1000,
2024 "Should return default 1000 for negative values"
2025 );
2026 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2027 }
2028
2029 #[test]
2030 #[serial]
2031 fn test_config_includes_reader_pool_max_size() {
2032 env::set_var("REDIS_URL", "redis://primary:6379");
2033 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2034 env::set_var("REDIS_READER_POOL_MAX_SIZE", "750");
2035
2036 let config = ServerConfig::from_env();
2037
2038 assert_eq!(
2039 config.redis_reader_pool_max_size, 750,
2040 "Should include reader pool max size in config"
2041 );
2042
2043 env::remove_var("REDIS_URL");
2044 env::remove_var("API_KEY");
2045 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2046 }
2047
2048 #[test]
2049 #[serial]
2050 fn test_config_uses_default_when_not_set() {
2051 env::set_var("REDIS_URL", "redis://primary:6379");
2052 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2053 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2054
2055 let config = ServerConfig::from_env();
2056
2057 assert_eq!(
2058 config.redis_reader_pool_max_size, 1000,
2059 "Should use default 1000 when not set"
2060 );
2061
2062 env::remove_var("REDIS_URL");
2063 env::remove_var("API_KEY");
2064 }
2065 }
2066
2067 mod get_sqs_wait_time_tests {
2068 use super::*;
2069 use serial_test::serial;
2070
2071 #[test]
2072 #[serial]
2073 fn test_returns_default_when_env_not_set() {
2074 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2075 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2076 assert_eq!(result, 5, "Should return default when env var is not set");
2077 }
2078
2079 #[test]
2080 #[serial]
2081 fn test_returns_parsed_value() {
2082 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "10");
2083 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2084 assert_eq!(result, 10, "Should return parsed value");
2085 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2086 }
2087
2088 #[test]
2089 #[serial]
2090 fn test_returns_default_when_invalid() {
2091 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "not_a_number");
2092 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2093 assert_eq!(result, 5, "Should return default for non-numeric input");
2094 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2095 }
2096
2097 #[test]
2098 #[serial]
2099 fn test_clamps_to_sqs_maximum_of_20() {
2100 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "30");
2101 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2102 assert_eq!(result, 20, "Should clamp to SQS maximum of 20 seconds");
2103 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2104 }
2105
2106 #[test]
2107 #[serial]
2108 fn test_allows_zero() {
2109 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "0");
2110 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2111 assert_eq!(result, 0, "Should allow zero (short polling)");
2112 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2113 }
2114 }
2115
2116 mod get_sqs_poller_count_tests {
2117 use super::*;
2118 use serial_test::serial;
2119
2120 #[test]
2121 #[serial]
2122 fn test_returns_default_when_env_not_set() {
2123 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2124 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2125 assert_eq!(result, 2, "Should return default when env var is not set");
2126 }
2127
2128 #[test]
2129 #[serial]
2130 fn test_returns_parsed_value() {
2131 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "4");
2132 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2133 assert_eq!(result, 4, "Should return parsed value");
2134 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2135 }
2136
2137 #[test]
2138 #[serial]
2139 fn test_returns_default_when_invalid() {
2140 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "not_a_number");
2141 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2142 assert_eq!(result, 2, "Should return default for non-numeric input");
2143 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2144 }
2145
2146 #[test]
2147 #[serial]
2148 fn test_clamps_zero_to_minimum_of_1() {
2149 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "0");
2150 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2151 assert_eq!(result, 1, "Should clamp zero to minimum of 1");
2152 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2153 }
2154
2155 #[test]
2156 #[serial]
2157 fn test_default_also_clamped_to_minimum_of_1() {
2158 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2159 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 0);
2160 assert_eq!(
2161 result, 1,
2162 "Default of 0 should also be clamped to minimum of 1"
2163 );
2164 }
2165 }
2166}