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
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use lazy_static::lazy_static;
682 use std::env;
683 use std::sync::Mutex;
684
685 lazy_static! {
687 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
688 }
689
690 fn setup() {
691 env::remove_var("HOST");
693 env::remove_var("APP_PORT");
694 env::remove_var("REDIS_URL");
695 env::remove_var("CONFIG_DIR");
696 env::remove_var("CONFIG_FILE_NAME");
697 env::remove_var("CONFIG_FILE_PATH");
698 env::remove_var("API_KEY");
699 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
700 env::remove_var("RATE_LIMIT_BURST_SIZE");
701 env::remove_var("METRICS_PORT");
702 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
703 env::remove_var("RPC_TIMEOUT_MS");
704 env::remove_var("PROVIDER_MAX_RETRIES");
705 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
706 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
707 env::remove_var("PROVIDER_MAX_FAILOVERS");
708 env::remove_var("REPOSITORY_STORAGE_TYPE");
709 env::remove_var("RESET_STORAGE_ON_START");
710 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
711 env::remove_var("REDIS_READER_URL");
712 env::set_var("REDIS_URL", "redis://localhost:6379");
714 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
715 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
716 }
717
718 #[test]
719 fn test_default_values() {
720 let _lock = match ENV_MUTEX.lock() {
721 Ok(guard) => guard,
722 Err(poisoned) => poisoned.into_inner(),
723 };
724 setup();
725
726 let config = ServerConfig::from_env();
727
728 assert_eq!(config.host, "0.0.0.0");
729 assert_eq!(config.port, 8080);
730 assert_eq!(config.redis_url, "redis://localhost:6379");
731 assert_eq!(config.config_file_path, "./config/config.json");
732 assert_eq!(
733 config.api_key,
734 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
735 );
736 assert_eq!(config.rate_limit_requests_per_second, 100);
737 assert_eq!(config.rate_limit_burst_size, 300);
738 assert_eq!(config.metrics_port, 8081);
739 assert_eq!(config.redis_connection_timeout_ms, 5000);
740 assert_eq!(config.rpc_timeout_ms, 10000);
741 assert_eq!(config.provider_max_retries, 3);
742 assert_eq!(config.provider_retry_base_delay_ms, 100);
743 assert_eq!(config.provider_retry_max_delay_ms, 2000);
744 assert_eq!(config.provider_max_failovers, 3);
745 assert_eq!(config.provider_failure_threshold, 3);
746 assert_eq!(config.provider_pause_duration_secs, 60);
747 assert_eq!(
748 config.repository_storage_type,
749 RepositoryStorageType::InMemory
750 );
751 assert!(!config.reset_storage_on_start);
752 assert_eq!(config.transaction_expiration_hours, 4.0);
753 }
754
755 #[test]
756 fn test_invalid_port_values() {
757 let _lock = match ENV_MUTEX.lock() {
758 Ok(guard) => guard,
759 Err(poisoned) => poisoned.into_inner(),
760 };
761 setup();
762 env::set_var("REDIS_URL", "redis://localhost:6379");
763 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
764 env::set_var("APP_PORT", "not_a_number");
765 env::set_var("METRICS_PORT", "also_not_a_number");
766 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "invalid");
767 env::set_var("RATE_LIMIT_BURST_SIZE", "invalid");
768 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "invalid");
769 env::set_var("RPC_TIMEOUT_MS", "invalid");
770 env::set_var("PROVIDER_MAX_RETRIES", "invalid");
771 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "invalid");
772 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "invalid");
773 env::set_var("PROVIDER_MAX_FAILOVERS", "invalid");
774 env::set_var("REPOSITORY_STORAGE_TYPE", "invalid");
775 env::set_var("RESET_STORAGE_ON_START", "invalid");
776 env::set_var("TRANSACTION_EXPIRATION_HOURS", "invalid");
777 let config = ServerConfig::from_env();
778
779 assert_eq!(config.port, 8080);
781 assert_eq!(config.metrics_port, 8081);
782 assert_eq!(config.rate_limit_requests_per_second, 100);
783 assert_eq!(config.rate_limit_burst_size, 300);
784 assert_eq!(config.redis_connection_timeout_ms, 10000);
785 assert_eq!(config.rpc_timeout_ms, 10000);
786 assert_eq!(config.provider_max_retries, 3);
787 assert_eq!(config.provider_retry_base_delay_ms, 100);
788 assert_eq!(config.provider_retry_max_delay_ms, 2000);
789 assert_eq!(config.provider_max_failovers, 3);
790 assert_eq!(
791 config.repository_storage_type,
792 RepositoryStorageType::InMemory
793 );
794 assert!(!config.reset_storage_on_start);
795 assert_eq!(config.transaction_expiration_hours, 4.0);
796 }
797
798 #[test]
799 fn test_custom_values() {
800 let _lock = match ENV_MUTEX.lock() {
801 Ok(guard) => guard,
802 Err(poisoned) => poisoned.into_inner(),
803 };
804 setup();
805
806 env::set_var("HOST", "127.0.0.1");
807 env::set_var("APP_PORT", "9090");
808 env::set_var("REDIS_URL", "redis://custom:6379");
809 env::set_var("CONFIG_DIR", "custom");
810 env::set_var("CONFIG_FILE_NAME", "path.json");
811 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
812 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "200");
813 env::set_var("RATE_LIMIT_BURST_SIZE", "500");
814 env::set_var("METRICS_PORT", "9091");
815 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "10000");
816 env::set_var("RPC_TIMEOUT_MS", "33333");
817 env::set_var("PROVIDER_MAX_RETRIES", "5");
818 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
819 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "3000");
820 env::set_var("PROVIDER_MAX_FAILOVERS", "4");
821 env::set_var("REPOSITORY_STORAGE_TYPE", "in_memory");
822 env::set_var("RESET_STORAGE_ON_START", "true");
823 env::set_var("TRANSACTION_EXPIRATION_HOURS", "6");
824 let config = ServerConfig::from_env();
825
826 assert_eq!(config.host, "127.0.0.1");
827 assert_eq!(config.port, 9090);
828 assert_eq!(config.redis_url, "redis://custom:6379");
829 assert_eq!(config.config_file_path, "custom/path.json");
830 assert_eq!(
831 config.api_key,
832 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
833 );
834 assert_eq!(config.rate_limit_requests_per_second, 200);
835 assert_eq!(config.rate_limit_burst_size, 500);
836 assert_eq!(config.metrics_port, 9091);
837 assert_eq!(config.redis_connection_timeout_ms, 10000);
838 assert_eq!(config.rpc_timeout_ms, 33333);
839 assert_eq!(config.provider_max_retries, 5);
840 assert_eq!(config.provider_retry_base_delay_ms, 200);
841 assert_eq!(config.provider_retry_max_delay_ms, 3000);
842 assert_eq!(config.provider_max_failovers, 4);
843 assert_eq!(
844 config.repository_storage_type,
845 RepositoryStorageType::InMemory
846 );
847 assert!(config.reset_storage_on_start);
848 assert_eq!(config.transaction_expiration_hours, 6.0);
849 }
850
851 #[test]
852 #[should_panic(expected = "Security error: API_KEY must be at least 32 characters long")]
853 fn test_invalid_api_key_length() {
854 let _lock = match ENV_MUTEX.lock() {
855 Ok(guard) => guard,
856 Err(poisoned) => poisoned.into_inner(),
857 };
858 setup();
859 env::set_var("REDIS_URL", "redis://localhost:6379");
860 env::set_var("API_KEY", "insufficient_length");
861 env::set_var("APP_PORT", "8080");
862 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "100");
863 env::set_var("RATE_LIMIT_BURST_SIZE", "300");
864 env::set_var("METRICS_PORT", "9091");
865 env::set_var("TRANSACTION_EXPIRATION_HOURS", "4");
866
867 let _ = ServerConfig::from_env();
868
869 panic!("Test should have panicked before reaching here");
870 }
871
872 #[test]
874 fn test_individual_getters_with_defaults() {
875 let _lock = match ENV_MUTEX.lock() {
876 Ok(guard) => guard,
877 Err(poisoned) => poisoned.into_inner(),
878 };
879
880 env::remove_var("HOST");
882 env::remove_var("APP_PORT");
883 env::remove_var("REDIS_URL");
884 env::remove_var("CONFIG_DIR");
885 env::remove_var("CONFIG_FILE_NAME");
886 env::remove_var("API_KEY");
887 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
888 env::remove_var("RATE_LIMIT_BURST_SIZE");
889 env::remove_var("METRICS_PORT");
890 env::remove_var("ENABLE_SWAGGER");
891 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
892 env::remove_var("REDIS_KEY_PREFIX");
893 env::remove_var("REDIS_READER_URL");
894 env::remove_var("RPC_TIMEOUT_MS");
895 env::remove_var("PROVIDER_MAX_RETRIES");
896 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
897 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
898 env::remove_var("PROVIDER_MAX_FAILOVERS");
899 env::remove_var("REPOSITORY_STORAGE_TYPE");
900 env::remove_var("RESET_STORAGE_ON_START");
901 env::remove_var("STORAGE_ENCRYPTION_KEY");
902 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
903 env::remove_var("REDIS_POOL_MAX_SIZE");
904 env::remove_var("REDIS_POOL_TIMEOUT_MS");
905
906 assert_eq!(ServerConfig::get_host(), "0.0.0.0");
908 assert_eq!(ServerConfig::get_port(), 8080);
909 assert_eq!(ServerConfig::get_redis_url_optional(), None);
910 assert_eq!(ServerConfig::get_config_file_path(), "./config/config.json");
911 assert_eq!(ServerConfig::get_api_key_optional(), None);
912 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 100);
913 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 300);
914 assert_eq!(ServerConfig::get_metrics_port(), 8081);
915 assert!(!ServerConfig::get_enable_swagger());
916 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 10000);
917 assert_eq!(ServerConfig::get_redis_key_prefix(), "oz-relayer");
918 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 10000);
919 assert_eq!(ServerConfig::get_provider_max_retries(), 3);
920 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 100);
921 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 2000);
922 assert_eq!(ServerConfig::get_provider_max_failovers(), 3);
923 assert_eq!(
924 ServerConfig::get_repository_storage_type(),
925 RepositoryStorageType::InMemory
926 );
927 assert!(!ServerConfig::get_reset_storage_on_start());
928 assert!(ServerConfig::get_storage_encryption_key().is_none());
929 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 4.0);
930 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
931 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
932 }
933
934 #[test]
935 fn test_individual_getters_with_custom_values() {
936 let _lock = match ENV_MUTEX.lock() {
937 Ok(guard) => guard,
938 Err(poisoned) => poisoned.into_inner(),
939 };
940
941 env::set_var("HOST", "192.168.1.1");
943 env::set_var("APP_PORT", "9999");
944 env::set_var("REDIS_URL", "redis://custom:6379");
945 env::set_var("CONFIG_DIR", "/custom/config");
946 env::set_var("CONFIG_FILE_NAME", "custom.json");
947 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
948 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "500");
949 env::set_var("RATE_LIMIT_BURST_SIZE", "1000");
950 env::set_var("METRICS_PORT", "9999");
951 env::set_var("ENABLE_SWAGGER", "true");
952 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
953 env::set_var("REDIS_KEY_PREFIX", "custom-prefix");
954 env::set_var("RPC_TIMEOUT_MS", "15000");
955 env::set_var("PROVIDER_MAX_RETRIES", "5");
956 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
957 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "5000");
958 env::set_var("PROVIDER_MAX_FAILOVERS", "10");
959 env::set_var("REPOSITORY_STORAGE_TYPE", "redis");
960 env::set_var("RESET_STORAGE_ON_START", "true");
961 env::set_var("STORAGE_ENCRYPTION_KEY", "my-encryption-key");
962 env::set_var("TRANSACTION_EXPIRATION_HOURS", "12");
963 env::set_var("REDIS_POOL_MAX_SIZE", "200");
964 env::set_var("REDIS_POOL_TIMEOUT_MS", "20000");
965
966 assert_eq!(ServerConfig::get_host(), "192.168.1.1");
968 assert_eq!(ServerConfig::get_port(), 9999);
969 assert_eq!(
970 ServerConfig::get_redis_url_optional(),
971 Some("redis://custom:6379".to_string())
972 );
973 assert_eq!(
974 ServerConfig::get_config_file_path(),
975 "/custom/config/custom.json"
976 );
977 assert!(ServerConfig::get_api_key_optional().is_some());
978 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 500);
979 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 1000);
980 assert_eq!(ServerConfig::get_metrics_port(), 9999);
981 assert!(ServerConfig::get_enable_swagger());
982 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 5000);
983 assert_eq!(ServerConfig::get_redis_key_prefix(), "custom-prefix");
984 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 15000);
985 assert_eq!(ServerConfig::get_provider_max_retries(), 5);
986 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 200);
987 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 5000);
988 assert_eq!(ServerConfig::get_provider_max_failovers(), 10);
989 assert_eq!(
990 ServerConfig::get_repository_storage_type(),
991 RepositoryStorageType::Redis
992 );
993 assert!(ServerConfig::get_reset_storage_on_start());
994 assert!(ServerConfig::get_storage_encryption_key().is_some());
995 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 12.0);
996 assert_eq!(ServerConfig::get_redis_pool_max_size(), 200);
997 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 20000);
998 }
999
1000 #[test]
1001 fn test_get_redis_pool_max_size() {
1002 let _lock = match ENV_MUTEX.lock() {
1003 Ok(guard) => guard,
1004 Err(poisoned) => poisoned.into_inner(),
1005 };
1006 env::remove_var("REDIS_POOL_MAX_SIZE");
1008 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1009
1010 env::set_var("REDIS_POOL_MAX_SIZE", "100");
1012 assert_eq!(ServerConfig::get_redis_pool_max_size(), 100);
1013
1014 env::set_var("REDIS_POOL_MAX_SIZE", "not_a_number");
1016 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1017
1018 env::set_var("REDIS_POOL_MAX_SIZE", "0");
1020 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1021
1022 env::set_var("REDIS_POOL_MAX_SIZE", "10000");
1024 assert_eq!(ServerConfig::get_redis_pool_max_size(), 10000);
1025
1026 env::remove_var("REDIS_POOL_MAX_SIZE");
1028 }
1029
1030 #[test]
1031 fn test_get_redis_pool_timeout_ms() {
1032 let _lock = match ENV_MUTEX.lock() {
1033 Ok(guard) => guard,
1034 Err(poisoned) => poisoned.into_inner(),
1035 };
1036
1037 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1039 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1040
1041 env::set_var("REDIS_POOL_TIMEOUT_MS", "15000");
1043 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 15000);
1044
1045 env::set_var("REDIS_POOL_TIMEOUT_MS", "not_a_number");
1047 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1048
1049 env::set_var("REDIS_POOL_TIMEOUT_MS", "0");
1051 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1052
1053 env::set_var("REDIS_POOL_TIMEOUT_MS", "60000");
1055 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 60000);
1056
1057 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1059 }
1060
1061 #[test]
1062 fn test_fractional_transaction_expiration_hours() {
1063 let _lock = match ENV_MUTEX.lock() {
1064 Ok(guard) => guard,
1065 Err(poisoned) => poisoned.into_inner(),
1066 };
1067 setup();
1068
1069 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.1");
1071 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.1);
1072
1073 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.5");
1075 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.5);
1076
1077 env::set_var("TRANSACTION_EXPIRATION_HOURS", "24");
1079 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 24.0);
1080
1081 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
1083 }
1084
1085 #[test]
1086 #[should_panic(expected = "REDIS_URL must be set")]
1087 fn test_get_redis_url_panics_when_not_set() {
1088 let _lock = match ENV_MUTEX.lock() {
1089 Ok(guard) => guard,
1090 Err(poisoned) => poisoned.into_inner(),
1091 };
1092
1093 env::remove_var("REDIS_URL");
1094 let _ = ServerConfig::get_redis_url();
1095 }
1096
1097 #[test]
1098 #[should_panic(expected = "API_KEY must be set")]
1099 fn test_get_api_key_panics_when_not_set() {
1100 let _lock = match ENV_MUTEX.lock() {
1101 Ok(guard) => guard,
1102 Err(poisoned) => poisoned.into_inner(),
1103 };
1104
1105 env::remove_var("API_KEY");
1106 let _ = ServerConfig::get_api_key();
1107 }
1108
1109 #[test]
1110 fn test_optional_getters_return_none_safely() {
1111 let _lock = match ENV_MUTEX.lock() {
1112 Ok(guard) => guard,
1113 Err(poisoned) => poisoned.into_inner(),
1114 };
1115
1116 env::remove_var("REDIS_URL");
1117 env::remove_var("API_KEY");
1118 env::remove_var("STORAGE_ENCRYPTION_KEY");
1119
1120 assert!(ServerConfig::get_redis_url_optional().is_none());
1121 assert!(ServerConfig::get_api_key_optional().is_none());
1122 assert!(ServerConfig::get_storage_encryption_key().is_none());
1123 }
1124
1125 #[test]
1126 fn test_refactored_from_env_equivalence() {
1127 let _lock = match ENV_MUTEX.lock() {
1128 Ok(guard) => guard,
1129 Err(poisoned) => poisoned.into_inner(),
1130 };
1131 setup();
1132
1133 env::set_var("HOST", "custom-host");
1135 env::set_var("APP_PORT", "7777");
1136 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "250");
1137 env::set_var("METRICS_PORT", "7778");
1138 env::set_var("ENABLE_SWAGGER", "true");
1139 env::set_var("PROVIDER_MAX_RETRIES", "7");
1140 env::set_var("TRANSACTION_EXPIRATION_HOURS", "8");
1141
1142 let config = ServerConfig::from_env();
1143
1144 assert_eq!(config.host, ServerConfig::get_host());
1146 assert_eq!(config.port, ServerConfig::get_port());
1147 assert_eq!(config.redis_url, ServerConfig::get_redis_url());
1148 assert_eq!(
1149 config.config_file_path,
1150 ServerConfig::get_config_file_path()
1151 );
1152 assert_eq!(config.api_key, ServerConfig::get_api_key());
1153 assert_eq!(
1154 config.rate_limit_requests_per_second,
1155 ServerConfig::get_rate_limit_requests_per_second()
1156 );
1157 assert_eq!(
1158 config.rate_limit_burst_size,
1159 ServerConfig::get_rate_limit_burst_size()
1160 );
1161 assert_eq!(config.metrics_port, ServerConfig::get_metrics_port());
1162 assert_eq!(config.enable_swagger, ServerConfig::get_enable_swagger());
1163 assert_eq!(
1164 config.redis_connection_timeout_ms,
1165 ServerConfig::get_redis_connection_timeout_ms()
1166 );
1167 assert_eq!(
1168 config.redis_key_prefix,
1169 ServerConfig::get_redis_key_prefix()
1170 );
1171 assert_eq!(config.rpc_timeout_ms, ServerConfig::get_rpc_timeout_ms());
1172 assert_eq!(
1173 config.provider_max_retries,
1174 ServerConfig::get_provider_max_retries()
1175 );
1176 assert_eq!(
1177 config.provider_retry_base_delay_ms,
1178 ServerConfig::get_provider_retry_base_delay_ms()
1179 );
1180 assert_eq!(
1181 config.provider_retry_max_delay_ms,
1182 ServerConfig::get_provider_retry_max_delay_ms()
1183 );
1184 assert_eq!(
1185 config.provider_max_failovers,
1186 ServerConfig::get_provider_max_failovers()
1187 );
1188 assert_eq!(
1189 config.repository_storage_type,
1190 ServerConfig::get_repository_storage_type()
1191 );
1192 assert_eq!(
1193 config.reset_storage_on_start,
1194 ServerConfig::get_reset_storage_on_start()
1195 );
1196 assert_eq!(
1197 config.storage_encryption_key,
1198 ServerConfig::get_storage_encryption_key()
1199 );
1200 assert_eq!(
1201 config.transaction_expiration_hours,
1202 ServerConfig::get_transaction_expiration_hours()
1203 );
1204 }
1205
1206 mod get_worker_concurrency_tests {
1207 use super::*;
1208 use serial_test::serial;
1209
1210 #[test]
1211 #[serial]
1212 fn test_returns_default_when_env_not_set() {
1213 let worker_name = "test_worker";
1214 let env_var = format!(
1215 "BACKGROUND_WORKER_{}_CONCURRENCY",
1216 worker_name.to_uppercase()
1217 );
1218
1219 env::remove_var(&env_var);
1221
1222 let default_value = 42;
1223 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1224
1225 assert_eq!(
1226 result, default_value,
1227 "Should return default value when env var is not set"
1228 );
1229 }
1230
1231 #[test]
1232 #[serial]
1233 fn test_returns_env_value_when_set() {
1234 let worker_name = "status_checker";
1235 let env_var = format!(
1236 "BACKGROUND_WORKER_{}_CONCURRENCY",
1237 worker_name.to_uppercase()
1238 );
1239
1240 env::set_var(&env_var, "100");
1242
1243 let default_value = 10;
1244 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1245
1246 assert_eq!(result, 100, "Should return env var value when set");
1247
1248 env::remove_var(&env_var);
1250 }
1251
1252 #[test]
1253 #[serial]
1254 fn test_returns_default_when_env_invalid() {
1255 let worker_name = "invalid_worker";
1256 let env_var = format!(
1257 "BACKGROUND_WORKER_{}_CONCURRENCY",
1258 worker_name.to_uppercase()
1259 );
1260
1261 env::set_var(&env_var, "not_a_number");
1263
1264 let default_value = 25;
1265 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1266
1267 assert_eq!(
1268 result, default_value,
1269 "Should return default value when env var is invalid"
1270 );
1271
1272 env::remove_var(&env_var);
1274 }
1275
1276 #[test]
1277 #[serial]
1278 fn test_returns_default_when_env_empty() {
1279 let worker_name = "empty_worker";
1280 let env_var = format!(
1281 "BACKGROUND_WORKER_{}_CONCURRENCY",
1282 worker_name.to_uppercase()
1283 );
1284
1285 env::set_var(&env_var, "");
1287
1288 let default_value = 15;
1289 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1290
1291 assert_eq!(
1292 result, default_value,
1293 "Should return default value when env var is empty"
1294 );
1295
1296 env::remove_var(&env_var);
1298 }
1299
1300 #[test]
1301 #[serial]
1302 fn test_returns_default_when_env_negative() {
1303 let worker_name = "negative_worker";
1304 let env_var = format!(
1305 "BACKGROUND_WORKER_{}_CONCURRENCY",
1306 worker_name.to_uppercase()
1307 );
1308
1309 env::set_var(&env_var, "-5");
1311
1312 let default_value = 20;
1313 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1314
1315 assert_eq!(
1316 result, default_value,
1317 "Should return default value when env var is negative"
1318 );
1319
1320 env::remove_var(&env_var);
1322 }
1323
1324 #[test]
1325 #[serial]
1326 fn test_env_var_name_formatting() {
1327 let worker_names = vec![
1329 (
1330 "transaction_sender",
1331 "BACKGROUND_WORKER_TRANSACTION_SENDER_CONCURRENCY",
1332 ),
1333 (
1334 "status_checker_evm",
1335 "BACKGROUND_WORKER_STATUS_CHECKER_EVM_CONCURRENCY",
1336 ),
1337 (
1338 "notification_sender",
1339 "BACKGROUND_WORKER_NOTIFICATION_SENDER_CONCURRENCY",
1340 ),
1341 ];
1342
1343 for (worker_name, expected_env_var) in worker_names {
1344 let actual_env_var = format!(
1345 "BACKGROUND_WORKER_{}_CONCURRENCY",
1346 worker_name.to_uppercase()
1347 );
1348 assert_eq!(
1349 actual_env_var, expected_env_var,
1350 "Env var name should be correctly formatted for worker: {worker_name}"
1351 );
1352 }
1353 }
1354
1355 #[test]
1356 #[serial]
1357 fn test_zero_value() {
1358 let worker_name = "zero_worker";
1359 let env_var = format!(
1360 "BACKGROUND_WORKER_{}_CONCURRENCY",
1361 worker_name.to_uppercase()
1362 );
1363
1364 env::set_var(&env_var, "0");
1366
1367 let default_value = 30;
1368 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1369
1370 assert_eq!(result, 0, "Should accept zero as a valid value");
1371
1372 env::remove_var(&env_var);
1374 }
1375
1376 #[test]
1377 #[serial]
1378 fn test_large_value() {
1379 let worker_name = "large_worker";
1380 let env_var = format!(
1381 "BACKGROUND_WORKER_{}_CONCURRENCY",
1382 worker_name.to_uppercase()
1383 );
1384
1385 env::set_var(&env_var, "10000");
1387
1388 let default_value = 50;
1389 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1390
1391 assert_eq!(result, 10000, "Should accept large values");
1392
1393 env::remove_var(&env_var);
1395 }
1396
1397 #[test]
1398 #[serial]
1399 fn test_whitespace_in_value() {
1400 let worker_name = "whitespace_worker";
1401 let env_var = format!(
1402 "BACKGROUND_WORKER_{}_CONCURRENCY",
1403 worker_name.to_uppercase()
1404 );
1405
1406 env::set_var(&env_var, " 75 ");
1408
1409 let default_value = 35;
1410 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1411
1412 assert_eq!(
1415 result, default_value,
1416 "Should return default value when value has whitespace"
1417 );
1418
1419 env::remove_var(&env_var);
1421 }
1422
1423 #[test]
1424 #[serial]
1425 fn test_float_value_returns_default() {
1426 let worker_name = "float_worker";
1427 let env_var = format!(
1428 "BACKGROUND_WORKER_{}_CONCURRENCY",
1429 worker_name.to_uppercase()
1430 );
1431
1432 env::set_var(&env_var, "12.5");
1434
1435 let default_value = 40;
1436 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1437
1438 assert_eq!(
1439 result, default_value,
1440 "Should return default value for float input"
1441 );
1442
1443 env::remove_var(&env_var);
1445 }
1446 }
1447
1448 mod get_relayer_concurrency_limit_tests {
1449 use super::*;
1450 use serial_test::serial;
1451
1452 #[test]
1453 #[serial]
1454 fn test_returns_default_when_env_not_set() {
1455 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1456 let result = ServerConfig::get_relayer_concurrency_limit();
1457 assert_eq!(result, 100, "Should return default value of 100");
1458 }
1459
1460 #[test]
1461 #[serial]
1462 fn test_returns_env_value_when_set() {
1463 env::set_var("RELAYER_CONCURRENCY_LIMIT", "250");
1464 let result = ServerConfig::get_relayer_concurrency_limit();
1465 assert_eq!(result, 250, "Should return env var value");
1466 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1467 }
1468
1469 #[test]
1470 #[serial]
1471 fn test_returns_default_when_env_invalid() {
1472 env::set_var("RELAYER_CONCURRENCY_LIMIT", "not_a_number");
1473 let result = ServerConfig::get_relayer_concurrency_limit();
1474 assert_eq!(result, 100, "Should return default value when invalid");
1475 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1476 }
1477
1478 #[test]
1479 #[serial]
1480 fn test_returns_default_when_env_empty() {
1481 env::set_var("RELAYER_CONCURRENCY_LIMIT", "");
1482 let result = ServerConfig::get_relayer_concurrency_limit();
1483 assert_eq!(result, 100, "Should return default value when empty");
1484 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1485 }
1486
1487 #[test]
1488 #[serial]
1489 fn test_zero_value() {
1490 env::set_var("RELAYER_CONCURRENCY_LIMIT", "0");
1491 let result = ServerConfig::get_relayer_concurrency_limit();
1492 assert_eq!(result, 0, "Should accept zero as valid value");
1493 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1494 }
1495
1496 #[test]
1497 #[serial]
1498 fn test_large_value() {
1499 env::set_var("RELAYER_CONCURRENCY_LIMIT", "5000");
1500 let result = ServerConfig::get_relayer_concurrency_limit();
1501 assert_eq!(result, 5000, "Should accept large values");
1502 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1503 }
1504
1505 #[test]
1506 #[serial]
1507 fn test_negative_value_returns_default() {
1508 env::set_var("RELAYER_CONCURRENCY_LIMIT", "-10");
1509 let result = ServerConfig::get_relayer_concurrency_limit();
1510 assert_eq!(result, 100, "Should return default for negative value");
1511 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1512 }
1513
1514 #[test]
1515 #[serial]
1516 fn test_float_value_returns_default() {
1517 env::set_var("RELAYER_CONCURRENCY_LIMIT", "100.5");
1518 let result = ServerConfig::get_relayer_concurrency_limit();
1519 assert_eq!(result, 100, "Should return default for float value");
1520 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1521 }
1522
1523 #[test]
1524 #[serial]
1525 fn test_whitespace_value_returns_default() {
1526 env::set_var("RELAYER_CONCURRENCY_LIMIT", " 150 ");
1527 let result = ServerConfig::get_relayer_concurrency_limit();
1528 assert_eq!(
1529 result, 100,
1530 "Should return default when value has whitespace"
1531 );
1532 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1533 }
1534 }
1535
1536 mod get_max_connections_tests {
1537 use super::*;
1538 use serial_test::serial;
1539
1540 #[test]
1541 #[serial]
1542 fn test_returns_default_when_env_not_set() {
1543 env::remove_var("MAX_CONNECTIONS");
1544 let result = ServerConfig::get_max_connections();
1545 assert_eq!(result, 256, "Should return default value of 256");
1546 }
1547
1548 #[test]
1549 #[serial]
1550 fn test_returns_env_value_when_set() {
1551 env::set_var("MAX_CONNECTIONS", "512");
1552 let result = ServerConfig::get_max_connections();
1553 assert_eq!(result, 512, "Should return env var value");
1554 env::remove_var("MAX_CONNECTIONS");
1555 }
1556
1557 #[test]
1558 #[serial]
1559 fn test_returns_default_when_env_invalid() {
1560 env::set_var("MAX_CONNECTIONS", "invalid");
1561 let result = ServerConfig::get_max_connections();
1562 assert_eq!(result, 256, "Should return default value when invalid");
1563 env::remove_var("MAX_CONNECTIONS");
1564 }
1565
1566 #[test]
1567 #[serial]
1568 fn test_returns_default_when_env_empty() {
1569 env::set_var("MAX_CONNECTIONS", "");
1570 let result = ServerConfig::get_max_connections();
1571 assert_eq!(result, 256, "Should return default value when empty");
1572 env::remove_var("MAX_CONNECTIONS");
1573 }
1574
1575 #[test]
1576 #[serial]
1577 fn test_zero_value() {
1578 env::set_var("MAX_CONNECTIONS", "0");
1579 let result = ServerConfig::get_max_connections();
1580 assert_eq!(result, 0, "Should accept zero as valid value");
1581 env::remove_var("MAX_CONNECTIONS");
1582 }
1583
1584 #[test]
1585 #[serial]
1586 fn test_large_value() {
1587 env::set_var("MAX_CONNECTIONS", "10000");
1588 let result = ServerConfig::get_max_connections();
1589 assert_eq!(result, 10000, "Should accept large values");
1590 env::remove_var("MAX_CONNECTIONS");
1591 }
1592
1593 #[test]
1594 #[serial]
1595 fn test_negative_value_returns_default() {
1596 env::set_var("MAX_CONNECTIONS", "-100");
1597 let result = ServerConfig::get_max_connections();
1598 assert_eq!(result, 256, "Should return default for negative value");
1599 env::remove_var("MAX_CONNECTIONS");
1600 }
1601
1602 #[test]
1603 #[serial]
1604 fn test_float_value_returns_default() {
1605 env::set_var("MAX_CONNECTIONS", "256.5");
1606 let result = ServerConfig::get_max_connections();
1607 assert_eq!(result, 256, "Should return default for float value");
1608 env::remove_var("MAX_CONNECTIONS");
1609 }
1610 }
1611
1612 mod get_connection_backlog_tests {
1613 use super::*;
1614 use serial_test::serial;
1615
1616 #[test]
1617 #[serial]
1618 fn test_returns_default_when_env_not_set() {
1619 env::remove_var("CONNECTION_BACKLOG");
1620 let result = ServerConfig::get_connection_backlog();
1621 assert_eq!(result, 511, "Should return default value of 511");
1622 }
1623
1624 #[test]
1625 #[serial]
1626 fn test_returns_env_value_when_set() {
1627 env::set_var("CONNECTION_BACKLOG", "1024");
1628 let result = ServerConfig::get_connection_backlog();
1629 assert_eq!(result, 1024, "Should return env var value");
1630 env::remove_var("CONNECTION_BACKLOG");
1631 }
1632
1633 #[test]
1634 #[serial]
1635 fn test_returns_default_when_env_invalid() {
1636 env::set_var("CONNECTION_BACKLOG", "not_a_number");
1637 let result = ServerConfig::get_connection_backlog();
1638 assert_eq!(result, 511, "Should return default value when invalid");
1639 env::remove_var("CONNECTION_BACKLOG");
1640 }
1641
1642 #[test]
1643 #[serial]
1644 fn test_returns_default_when_env_empty() {
1645 env::set_var("CONNECTION_BACKLOG", "");
1646 let result = ServerConfig::get_connection_backlog();
1647 assert_eq!(result, 511, "Should return default value when empty");
1648 env::remove_var("CONNECTION_BACKLOG");
1649 }
1650
1651 #[test]
1652 #[serial]
1653 fn test_zero_value() {
1654 env::set_var("CONNECTION_BACKLOG", "0");
1655 let result = ServerConfig::get_connection_backlog();
1656 assert_eq!(result, 0, "Should accept zero as valid value");
1657 env::remove_var("CONNECTION_BACKLOG");
1658 }
1659
1660 #[test]
1661 #[serial]
1662 fn test_large_value() {
1663 env::set_var("CONNECTION_BACKLOG", "65535");
1664 let result = ServerConfig::get_connection_backlog();
1665 assert_eq!(result, 65535, "Should accept large values");
1666 env::remove_var("CONNECTION_BACKLOG");
1667 }
1668
1669 #[test]
1670 #[serial]
1671 fn test_negative_value_returns_default() {
1672 env::set_var("CONNECTION_BACKLOG", "-50");
1673 let result = ServerConfig::get_connection_backlog();
1674 assert_eq!(result, 511, "Should return default for negative value");
1675 env::remove_var("CONNECTION_BACKLOG");
1676 }
1677
1678 #[test]
1679 #[serial]
1680 fn test_float_value_returns_default() {
1681 env::set_var("CONNECTION_BACKLOG", "511.5");
1682 let result = ServerConfig::get_connection_backlog();
1683 assert_eq!(result, 511, "Should return default for float value");
1684 env::remove_var("CONNECTION_BACKLOG");
1685 }
1686
1687 #[test]
1688 #[serial]
1689 fn test_common_production_values() {
1690 let test_cases = vec![
1692 (128, "Small server"),
1693 (511, "Default"),
1694 (1024, "Medium server"),
1695 (2048, "Large server"),
1696 ];
1697
1698 for (value, description) in test_cases {
1699 env::set_var("CONNECTION_BACKLOG", value.to_string());
1700 let result = ServerConfig::get_connection_backlog();
1701 assert_eq!(result, value, "Should accept {description}: {value}");
1702 }
1703
1704 env::remove_var("CONNECTION_BACKLOG");
1705 }
1706 }
1707
1708 mod get_request_timeout_seconds_tests {
1709 use super::*;
1710 use serial_test::serial;
1711
1712 #[test]
1713 #[serial]
1714 fn test_returns_default_when_env_not_set() {
1715 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1716 let result = ServerConfig::get_request_timeout_seconds();
1717 assert_eq!(result, 30, "Should return default value of 30");
1718 }
1719
1720 #[test]
1721 #[serial]
1722 fn test_returns_env_value_when_set() {
1723 env::set_var("REQUEST_TIMEOUT_SECONDS", "60");
1724 let result = ServerConfig::get_request_timeout_seconds();
1725 assert_eq!(result, 60, "Should return env var value");
1726 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1727 }
1728
1729 #[test]
1730 #[serial]
1731 fn test_returns_default_when_env_invalid() {
1732 env::set_var("REQUEST_TIMEOUT_SECONDS", "invalid");
1733 let result = ServerConfig::get_request_timeout_seconds();
1734 assert_eq!(result, 30, "Should return default value when invalid");
1735 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1736 }
1737
1738 #[test]
1739 #[serial]
1740 fn test_returns_default_when_env_empty() {
1741 env::set_var("REQUEST_TIMEOUT_SECONDS", "");
1742 let result = ServerConfig::get_request_timeout_seconds();
1743 assert_eq!(result, 30, "Should return default value when empty");
1744 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1745 }
1746
1747 #[test]
1748 #[serial]
1749 fn test_zero_value() {
1750 env::set_var("REQUEST_TIMEOUT_SECONDS", "0");
1751 let result = ServerConfig::get_request_timeout_seconds();
1752 assert_eq!(result, 0, "Should accept zero as valid value");
1753 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1754 }
1755
1756 #[test]
1757 #[serial]
1758 fn test_large_value() {
1759 env::set_var("REQUEST_TIMEOUT_SECONDS", "300");
1760 let result = ServerConfig::get_request_timeout_seconds();
1761 assert_eq!(result, 300, "Should accept large values");
1762 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1763 }
1764
1765 #[test]
1766 #[serial]
1767 fn test_negative_value_returns_default() {
1768 env::set_var("REQUEST_TIMEOUT_SECONDS", "-10");
1769 let result = ServerConfig::get_request_timeout_seconds();
1770 assert_eq!(result, 30, "Should return default for negative value");
1771 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1772 }
1773
1774 #[test]
1775 #[serial]
1776 fn test_float_value_returns_default() {
1777 env::set_var("REQUEST_TIMEOUT_SECONDS", "30.5");
1778 let result = ServerConfig::get_request_timeout_seconds();
1779 assert_eq!(result, 30, "Should return default for float value");
1780 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1781 }
1782
1783 #[test]
1784 #[serial]
1785 fn test_common_timeout_values() {
1786 let test_cases = vec![
1788 (10, "Short timeout"),
1789 (30, "Default timeout"),
1790 (60, "Moderate timeout"),
1791 (120, "Long timeout"),
1792 ];
1793
1794 for (value, description) in test_cases {
1795 env::set_var("REQUEST_TIMEOUT_SECONDS", value.to_string());
1796 let result = ServerConfig::get_request_timeout_seconds();
1797 assert_eq!(result, value, "Should accept {description}: {value}");
1798 }
1799
1800 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1801 }
1802 }
1803
1804 mod get_redis_reader_url_tests {
1805 use super::*;
1806 use serial_test::serial;
1807
1808 #[test]
1809 #[serial]
1810 fn test_returns_none_when_env_not_set() {
1811 env::remove_var("REDIS_READER_URL");
1812 let result = ServerConfig::get_redis_reader_url_optional();
1813 assert!(
1814 result.is_none(),
1815 "Should return None when env var is not set"
1816 );
1817 }
1818
1819 #[test]
1820 #[serial]
1821 fn test_returns_value_when_set() {
1822 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1823 let result = ServerConfig::get_redis_reader_url_optional();
1824 assert_eq!(
1825 result,
1826 Some("redis://reader:6379".to_string()),
1827 "Should return the env var value"
1828 );
1829 env::remove_var("REDIS_READER_URL");
1830 }
1831
1832 #[test]
1833 #[serial]
1834 fn test_returns_empty_string_when_set_to_empty() {
1835 env::set_var("REDIS_READER_URL", "");
1836 let result = ServerConfig::get_redis_reader_url_optional();
1837 assert_eq!(
1838 result,
1839 Some("".to_string()),
1840 "Should return empty string when set to empty"
1841 );
1842 env::remove_var("REDIS_READER_URL");
1843 }
1844
1845 #[test]
1846 #[serial]
1847 fn test_aws_elasticache_reader_url() {
1848 let reader_url = "redis://my-cluster-ro.xxx.cache.amazonaws.com:6379";
1850 env::set_var("REDIS_READER_URL", reader_url);
1851 let result = ServerConfig::get_redis_reader_url_optional();
1852 assert_eq!(
1853 result,
1854 Some(reader_url.to_string()),
1855 "Should accept AWS ElastiCache reader endpoint"
1856 );
1857 env::remove_var("REDIS_READER_URL");
1858 }
1859
1860 #[test]
1861 #[serial]
1862 fn test_config_includes_redis_reader_url() {
1863 env::set_var("REDIS_URL", "redis://primary:6379");
1864 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1865 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1866
1867 let config = ServerConfig::from_env();
1868
1869 assert_eq!(config.redis_url, "redis://primary:6379");
1870 assert_eq!(
1871 config.redis_reader_url,
1872 Some("redis://reader:6379".to_string())
1873 );
1874
1875 env::remove_var("REDIS_URL");
1876 env::remove_var("REDIS_READER_URL");
1877 env::remove_var("API_KEY");
1878 }
1879
1880 #[test]
1881 #[serial]
1882 fn test_config_without_redis_reader_url() {
1883 env::set_var("REDIS_URL", "redis://primary:6379");
1884 env::remove_var("REDIS_READER_URL");
1885 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1886
1887 let config = ServerConfig::from_env();
1888
1889 assert_eq!(config.redis_url, "redis://primary:6379");
1890 assert!(
1891 config.redis_reader_url.is_none(),
1892 "redis_reader_url should be None when not set"
1893 );
1894
1895 env::remove_var("REDIS_URL");
1896 env::remove_var("API_KEY");
1897 }
1898 }
1899
1900 mod get_sqs_queue_type_tests {
1901 use super::*;
1902 use serial_test::serial;
1903
1904 #[test]
1905 #[serial]
1906 fn test_returns_auto_when_env_not_set() {
1907 env::remove_var("SQS_QUEUE_TYPE");
1908 let result = ServerConfig::get_sqs_queue_type();
1909 assert_eq!(result, "auto", "Should default to 'auto'");
1910 }
1911
1912 #[test]
1913 #[serial]
1914 fn test_returns_fifo_when_set() {
1915 env::set_var("SQS_QUEUE_TYPE", "fifo");
1916 let result = ServerConfig::get_sqs_queue_type();
1917 assert_eq!(result, "fifo");
1918 env::remove_var("SQS_QUEUE_TYPE");
1919 }
1920
1921 #[test]
1922 #[serial]
1923 fn test_returns_standard_when_set() {
1924 env::set_var("SQS_QUEUE_TYPE", "standard");
1925 let result = ServerConfig::get_sqs_queue_type();
1926 assert_eq!(result, "standard");
1927 env::remove_var("SQS_QUEUE_TYPE");
1928 }
1929
1930 #[test]
1931 #[serial]
1932 fn test_returns_raw_value_for_unknown() {
1933 env::set_var("SQS_QUEUE_TYPE", "unknown");
1934 let result = ServerConfig::get_sqs_queue_type();
1935 assert_eq!(result, "unknown");
1936 env::remove_var("SQS_QUEUE_TYPE");
1937 }
1938 }
1939
1940 mod get_redis_reader_pool_max_size_tests {
1941 use super::*;
1942 use serial_test::serial;
1943
1944 #[test]
1945 #[serial]
1946 fn test_returns_default_when_env_not_set() {
1947 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1948 let result = ServerConfig::get_redis_reader_pool_max_size();
1949 assert_eq!(
1950 result, 1000,
1951 "Should return default 1000 when env var is not set"
1952 );
1953 }
1954
1955 #[test]
1956 #[serial]
1957 fn test_returns_value_when_set() {
1958 env::set_var("REDIS_READER_POOL_MAX_SIZE", "2000");
1959 let result = ServerConfig::get_redis_reader_pool_max_size();
1960 assert_eq!(result, 2000, "Should return the parsed value");
1961 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1962 }
1963
1964 #[test]
1965 #[serial]
1966 fn test_returns_default_when_invalid() {
1967 env::set_var("REDIS_READER_POOL_MAX_SIZE", "not_a_number");
1968 let result = ServerConfig::get_redis_reader_pool_max_size();
1969 assert_eq!(
1970 result, 1000,
1971 "Should return default 1000 for invalid values"
1972 );
1973 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1974 }
1975
1976 #[test]
1977 #[serial]
1978 fn test_returns_default_when_zero() {
1979 env::set_var("REDIS_READER_POOL_MAX_SIZE", "0");
1980 let result = ServerConfig::get_redis_reader_pool_max_size();
1981 assert_eq!(result, 1000, "Should return default 1000 when value is 0");
1982 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1983 }
1984
1985 #[test]
1986 #[serial]
1987 fn test_returns_default_when_negative() {
1988 env::set_var("REDIS_READER_POOL_MAX_SIZE", "-100");
1989 let result = ServerConfig::get_redis_reader_pool_max_size();
1990 assert_eq!(
1991 result, 1000,
1992 "Should return default 1000 for negative values"
1993 );
1994 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
1995 }
1996
1997 #[test]
1998 #[serial]
1999 fn test_config_includes_reader_pool_max_size() {
2000 env::set_var("REDIS_URL", "redis://primary:6379");
2001 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2002 env::set_var("REDIS_READER_POOL_MAX_SIZE", "750");
2003
2004 let config = ServerConfig::from_env();
2005
2006 assert_eq!(
2007 config.redis_reader_pool_max_size, 750,
2008 "Should include reader pool max size in config"
2009 );
2010
2011 env::remove_var("REDIS_URL");
2012 env::remove_var("API_KEY");
2013 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2014 }
2015
2016 #[test]
2017 #[serial]
2018 fn test_config_uses_default_when_not_set() {
2019 env::set_var("REDIS_URL", "redis://primary:6379");
2020 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2021 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2022
2023 let config = ServerConfig::from_env();
2024
2025 assert_eq!(
2026 config.redis_reader_pool_max_size, 1000,
2027 "Should use default 1000 when not set"
2028 );
2029
2030 env::remove_var("REDIS_URL");
2031 env::remove_var("API_KEY");
2032 }
2033 }
2034}