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 redis_connection_max_age_ms: u64,
84 pub rpc_timeout_ms: u64,
86 pub provider_max_retries: u8,
88 pub provider_retry_base_delay_ms: u64,
90 pub provider_retry_max_delay_ms: u64,
92 pub provider_max_failovers: u8,
94 pub provider_failure_threshold: u32,
96 pub provider_pause_duration_secs: u64,
98 pub provider_failure_expiration_secs: u64,
100 pub repository_storage_type: RepositoryStorageType,
102 pub reset_storage_on_start: bool,
104 pub storage_encryption_key: Option<SecretString>,
106 pub transaction_expiration_hours: f64,
109 pub rpc_allowed_hosts: Vec<String>,
111 pub rpc_block_private_ips: bool,
113 pub relayer_concurrency_limit: usize,
115 pub max_connections: usize,
117 pub connection_backlog: u32,
120 pub request_timeout_seconds: u64,
122 pub stellar_mainnet_fee_forwarder_address: Option<String>,
124 pub stellar_testnet_fee_forwarder_address: Option<String>,
126 pub stellar_mainnet_soroswap_router_address: Option<String>,
128 pub stellar_testnet_soroswap_router_address: Option<String>,
130 pub stellar_mainnet_soroswap_factory_address: Option<String>,
132 pub stellar_testnet_soroswap_factory_address: Option<String>,
134 pub stellar_mainnet_soroswap_native_wrapper_address: Option<String>,
136 pub stellar_testnet_soroswap_native_wrapper_address: Option<String>,
138}
139
140impl ServerConfig {
141 pub fn from_env() -> Self {
168 Self {
169 host: Self::get_host(),
170 port: Self::get_port(),
171 redis_url: Self::get_redis_url(), redis_reader_url: Self::get_redis_reader_url_optional(),
173 redis_reader_pool_max_size: Self::get_redis_reader_pool_max_size(),
174 config_file_path: Self::get_config_file_path(),
175 api_key: Self::get_api_key(), rate_limit_requests_per_second: Self::get_rate_limit_requests_per_second(),
177 rate_limit_burst_size: Self::get_rate_limit_burst_size(),
178 metrics_port: Self::get_metrics_port(),
179 enable_swagger: Self::get_enable_swagger(),
180 redis_connection_timeout_ms: Self::get_redis_connection_timeout_ms(),
181 redis_key_prefix: Self::get_redis_key_prefix(),
182 redis_pool_max_size: Self::get_redis_pool_max_size(),
183 redis_pool_timeout_ms: Self::get_redis_pool_timeout_ms(),
184 redis_connection_max_age_ms: Self::get_redis_connection_max_age_ms(),
185 rpc_timeout_ms: Self::get_rpc_timeout_ms(),
186 provider_max_retries: Self::get_provider_max_retries(),
187 provider_retry_base_delay_ms: Self::get_provider_retry_base_delay_ms(),
188 provider_retry_max_delay_ms: Self::get_provider_retry_max_delay_ms(),
189 provider_max_failovers: Self::get_provider_max_failovers(),
190 provider_failure_threshold: Self::get_provider_failure_threshold(),
191 provider_pause_duration_secs: Self::get_provider_pause_duration_secs(),
192 provider_failure_expiration_secs: Self::get_provider_failure_expiration_secs(),
193 repository_storage_type: Self::get_repository_storage_type(),
194 reset_storage_on_start: Self::get_reset_storage_on_start(),
195 storage_encryption_key: Self::get_storage_encryption_key(),
196 transaction_expiration_hours: Self::get_transaction_expiration_hours(),
197 rpc_allowed_hosts: Self::get_rpc_allowed_hosts(),
198 rpc_block_private_ips: Self::get_rpc_block_private_ips(),
199 relayer_concurrency_limit: Self::get_relayer_concurrency_limit(),
200 max_connections: Self::get_max_connections(),
201 connection_backlog: Self::get_connection_backlog(),
202 request_timeout_seconds: Self::get_request_timeout_seconds(),
203 stellar_mainnet_fee_forwarder_address: Self::get_stellar_mainnet_fee_forwarder_address(
204 ),
205 stellar_testnet_fee_forwarder_address: Self::get_stellar_testnet_fee_forwarder_address(
206 ),
207 stellar_mainnet_soroswap_router_address:
208 Self::get_stellar_mainnet_soroswap_router_address(),
209 stellar_testnet_soroswap_router_address:
210 Self::get_stellar_testnet_soroswap_router_address(),
211 stellar_mainnet_soroswap_factory_address:
212 Self::get_stellar_mainnet_soroswap_factory_address(),
213 stellar_testnet_soroswap_factory_address:
214 Self::get_stellar_testnet_soroswap_factory_address(),
215 stellar_mainnet_soroswap_native_wrapper_address:
216 Self::get_stellar_mainnet_soroswap_native_wrapper_address(),
217 stellar_testnet_soroswap_native_wrapper_address:
218 Self::get_stellar_testnet_soroswap_native_wrapper_address(),
219 }
220 }
221
222 pub fn get_host() -> String {
226 env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string())
227 }
228
229 pub fn get_port() -> u16 {
231 env::var("APP_PORT")
232 .unwrap_or_else(|_| "8080".to_string())
233 .parse()
234 .unwrap_or(8080)
235 }
236
237 pub fn get_redis_url() -> String {
239 env::var("REDIS_URL").expect("REDIS_URL must be set")
240 }
241
242 pub fn get_redis_url_optional() -> Option<String> {
244 env::var("REDIS_URL").ok()
245 }
246
247 pub fn get_redis_reader_url_optional() -> Option<String> {
251 env::var("REDIS_READER_URL").ok()
252 }
253
254 pub fn get_config_file_path() -> String {
256 let conf_dir = if env::var("IN_DOCKER")
257 .map(|val| val == "true")
258 .unwrap_or(false)
259 {
260 "config/".to_string()
261 } else {
262 env::var("CONFIG_DIR").unwrap_or_else(|_| "./config".to_string())
263 };
264
265 let conf_dir = format!("{}/", conf_dir.trim_end_matches('/'));
266 let config_file_name =
267 env::var("CONFIG_FILE_NAME").unwrap_or_else(|_| "config.json".to_string());
268
269 format!("{conf_dir}{config_file_name}")
270 }
271
272 pub fn get_queue_backend() -> String {
277 env::var("QUEUE_BACKEND").unwrap_or_else(|_| "redis".to_string())
278 }
279
280 pub fn get_sqs_queue_type() -> String {
286 env::var("SQS_QUEUE_TYPE").unwrap_or_else(|_| "auto".to_string())
287 }
288
289 pub fn get_aws_region() -> Result<String, String> {
297 env::var("AWS_REGION")
298 .map_err(|_| "AWS_REGION not set. Required for SQS backend.".to_string())
299 }
300
301 pub fn get_aws_account_id() -> Result<String, String> {
309 env::var("AWS_ACCOUNT_ID").map_err(|_| {
310 "AWS_ACCOUNT_ID not set. Required when SQS_QUEUE_URL_PREFIX is not provided."
311 .to_string()
312 })
313 }
314
315 pub fn get_pubsub_project_id() -> Result<String, String> {
323 env::var("PUBSUB_PROJECT_ID")
324 .map_err(|_| "PUBSUB_PROJECT_ID not set. Required for the Pub/Sub backend.".to_string())
325 }
326
327 pub fn get_pubsub_topic_prefix() -> String {
333 env::var("PUBSUB_TOPIC_PREFIX").unwrap_or_else(|_| "relayer".to_string())
334 }
335
336 pub fn get_pubsub_emulator_host() -> Option<String> {
341 env::var("PUBSUB_EMULATOR_HOST")
342 .ok()
343 .filter(|v| !v.is_empty())
344 }
345
346 pub fn get_api_key() -> SecretString {
348 let api_key = SecretString::new(&env::var("API_KEY").expect("API_KEY must be set"));
349
350 if !api_key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH) {
351 panic!(
352 "Security error: API_KEY must be at least {MINIMUM_SECRET_VALUE_LENGTH} characters long"
353 );
354 }
355
356 api_key
357 }
358
359 pub fn get_api_key_optional() -> Option<SecretString> {
361 env::var("API_KEY")
362 .ok()
363 .map(|key| SecretString::new(&key))
364 .filter(|key| key.has_minimum_length(MINIMUM_SECRET_VALUE_LENGTH))
365 }
366
367 pub fn get_rate_limit_requests_per_second() -> u64 {
369 env::var("RATE_LIMIT_REQUESTS_PER_SECOND")
370 .unwrap_or_else(|_| "100".to_string())
371 .parse()
372 .unwrap_or(100)
373 }
374
375 pub fn get_rate_limit_burst_size() -> u32 {
377 env::var("RATE_LIMIT_BURST_SIZE")
378 .unwrap_or_else(|_| "300".to_string())
379 .parse()
380 .unwrap_or(300)
381 }
382
383 pub fn get_metrics_port() -> u16 {
385 env::var("METRICS_PORT")
386 .unwrap_or_else(|_| "8081".to_string())
387 .parse()
388 .unwrap_or(8081)
389 }
390
391 pub fn get_enable_swagger() -> bool {
393 env::var("ENABLE_SWAGGER")
394 .map(|v| v.to_lowercase() == "true")
395 .unwrap_or(false)
396 }
397
398 pub fn get_redis_connection_timeout_ms() -> u64 {
400 env::var("REDIS_CONNECTION_TIMEOUT_MS")
401 .unwrap_or_else(|_| "10000".to_string())
402 .parse()
403 .unwrap_or(10000)
404 }
405
406 pub fn get_redis_key_prefix() -> String {
408 env::var("REDIS_KEY_PREFIX").unwrap_or_else(|_| "oz-relayer".to_string())
409 }
410
411 pub fn get_redis_pool_max_size() -> usize {
414 env::var("REDIS_POOL_MAX_SIZE")
415 .unwrap_or_else(|_| "500".to_string())
416 .parse()
417 .ok()
418 .filter(|&v| v > 0)
419 .unwrap_or(500)
420 }
421
422 pub fn get_redis_reader_pool_max_size() -> usize {
425 env::var("REDIS_READER_POOL_MAX_SIZE")
426 .ok()
427 .and_then(|v| v.parse().ok())
428 .filter(|&v| v > 0)
429 .unwrap_or(1000)
430 }
431
432 pub fn get_redis_pool_timeout_ms() -> u64 {
435 env::var("REDIS_POOL_TIMEOUT_MS")
436 .unwrap_or_else(|_| "10000".to_string())
437 .parse()
438 .ok()
439 .filter(|&v| v > 0)
440 .unwrap_or(10000)
441 }
442
443 pub fn get_redis_connection_max_age_ms() -> u64 {
452 env::var("REDIS_CONNECTION_MAX_AGE_MS")
453 .unwrap_or_else(|_| "60000".to_string())
454 .parse()
455 .unwrap_or(60000)
456 }
457
458 pub fn get_rpc_timeout_ms() -> u64 {
460 env::var("RPC_TIMEOUT_MS")
461 .unwrap_or_else(|_| "10000".to_string())
462 .parse()
463 .unwrap_or(10000)
464 }
465
466 pub fn get_provider_max_retries() -> u8 {
468 env::var("PROVIDER_MAX_RETRIES")
469 .unwrap_or_else(|_| "3".to_string())
470 .parse()
471 .unwrap_or(3)
472 }
473
474 pub fn get_provider_retry_base_delay_ms() -> u64 {
476 env::var("PROVIDER_RETRY_BASE_DELAY_MS")
477 .unwrap_or_else(|_| "100".to_string())
478 .parse()
479 .unwrap_or(100)
480 }
481
482 pub fn get_provider_retry_max_delay_ms() -> u64 {
484 env::var("PROVIDER_RETRY_MAX_DELAY_MS")
485 .unwrap_or_else(|_| "2000".to_string())
486 .parse()
487 .unwrap_or(2000)
488 }
489
490 pub fn get_provider_max_failovers() -> u8 {
492 env::var("PROVIDER_MAX_FAILOVERS")
493 .unwrap_or_else(|_| "3".to_string())
494 .parse()
495 .unwrap_or(3)
496 }
497
498 pub fn get_provider_failure_threshold() -> u32 {
500 env::var("PROVIDER_FAILURE_THRESHOLD")
501 .or_else(|_| env::var("RPC_FAILURE_THRESHOLD")) .unwrap_or_else(|_| DEFAULT_PROVIDER_FAILURE_THRESHOLD.to_string())
503 .parse()
504 .unwrap_or(DEFAULT_PROVIDER_FAILURE_THRESHOLD)
505 }
506
507 pub fn get_provider_pause_duration_secs() -> u64 {
512 env::var("PROVIDER_PAUSE_DURATION_SECS")
513 .or_else(|_| env::var("RPC_PAUSE_DURATION_SECS")) .unwrap_or_else(|_| DEFAULT_PROVIDER_PAUSE_DURATION_SECS.to_string())
515 .parse()
516 .unwrap_or(DEFAULT_PROVIDER_PAUSE_DURATION_SECS)
517 }
518
519 pub fn get_provider_failure_expiration_secs() -> u64 {
524 env::var("PROVIDER_FAILURE_EXPIRATION_SECS")
525 .unwrap_or_else(|_| DEFAULT_PROVIDER_FAILURE_EXPIRATION_SECS.to_string())
526 .parse()
527 .unwrap_or(DEFAULT_PROVIDER_FAILURE_EXPIRATION_SECS)
528 }
529
530 pub fn get_repository_storage_type() -> RepositoryStorageType {
532 env::var("REPOSITORY_STORAGE_TYPE")
533 .unwrap_or_else(|_| "in_memory".to_string())
534 .parse()
535 .unwrap_or(RepositoryStorageType::InMemory)
536 }
537
538 pub fn get_reset_storage_on_start() -> bool {
540 env::var("RESET_STORAGE_ON_START")
541 .map(|v| v.to_lowercase() == "true")
542 .unwrap_or(false)
543 }
544
545 pub fn get_storage_encryption_key() -> Option<SecretString> {
547 env::var("STORAGE_ENCRYPTION_KEY")
548 .map(|v| SecretString::new(&v))
549 .ok()
550 }
551
552 pub fn get_transaction_expiration_hours() -> f64 {
555 env::var("TRANSACTION_EXPIRATION_HOURS")
556 .unwrap_or_else(|_| "4".to_string())
557 .parse()
558 .unwrap_or(4.0)
559 }
560
561 pub fn get_rpc_allowed_hosts() -> Vec<String> {
563 env::var("RPC_ALLOWED_HOSTS")
564 .ok()
565 .map(|s| {
566 s.split(',')
567 .map(|host| host.trim().to_string())
568 .filter(|host| !host.is_empty())
569 .collect()
570 })
571 .unwrap_or_default()
572 }
573
574 pub fn get_rpc_block_private_ips() -> bool {
576 env::var("RPC_BLOCK_PRIVATE_IPS")
577 .map(|v| v.to_lowercase() == "true")
578 .unwrap_or(false)
579 }
580
581 pub fn get_relayer_concurrency_limit() -> usize {
583 env::var("RELAYER_CONCURRENCY_LIMIT")
584 .unwrap_or_else(|_| "100".to_string())
585 .parse()
586 .unwrap_or(100)
587 }
588
589 pub fn get_max_connections() -> usize {
591 env::var("MAX_CONNECTIONS")
592 .unwrap_or_else(|_| "256".to_string())
593 .parse()
594 .unwrap_or(256)
595 }
596
597 pub fn get_connection_backlog() -> u32 {
603 env::var("CONNECTION_BACKLOG")
604 .unwrap_or_else(|_| "511".to_string())
605 .parse()
606 .unwrap_or(511)
607 }
608
609 pub fn get_request_timeout_seconds() -> u64 {
615 env::var("REQUEST_TIMEOUT_SECONDS")
616 .unwrap_or_else(|_| "30".to_string())
617 .parse()
618 .unwrap_or(30)
619 }
620
621 pub fn get_distributed_mode() -> bool {
629 env::var("DISTRIBUTED_MODE")
630 .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
631 .unwrap_or(false)
632 }
633
634 pub fn get_stellar_mainnet_fee_forwarder_address() -> Option<String> {
639 env::var("STELLAR_MAINNET_FEE_FORWARDER_ADDRESS").ok()
640 }
641
642 pub fn get_stellar_testnet_fee_forwarder_address() -> Option<String> {
643 env::var("STELLAR_TESTNET_FEE_FORWARDER_ADDRESS").ok()
644 }
645
646 pub fn get_stellar_mainnet_soroswap_router_address() -> Option<String> {
647 env::var("STELLAR_MAINNET_SOROSWAP_ROUTER_ADDRESS").ok()
648 }
649
650 pub fn get_stellar_testnet_soroswap_router_address() -> Option<String> {
651 env::var("STELLAR_TESTNET_SOROSWAP_ROUTER_ADDRESS").ok()
652 }
653
654 pub fn get_stellar_mainnet_soroswap_factory_address() -> Option<String> {
655 env::var("STELLAR_MAINNET_SOROSWAP_FACTORY_ADDRESS").ok()
656 }
657
658 pub fn get_stellar_testnet_soroswap_factory_address() -> Option<String> {
659 env::var("STELLAR_TESTNET_SOROSWAP_FACTORY_ADDRESS").ok()
660 }
661
662 pub fn get_stellar_mainnet_soroswap_native_wrapper_address() -> Option<String> {
663 env::var("STELLAR_MAINNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS").ok()
664 }
665
666 pub fn get_stellar_testnet_soroswap_native_wrapper_address() -> Option<String> {
667 env::var("STELLAR_TESTNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS").ok()
668 }
669
670 pub fn resolve_stellar_fee_forwarder_address(is_testnet: bool) -> Option<String> {
678 if is_testnet {
679 Self::get_stellar_testnet_fee_forwarder_address()
680 } else {
681 Self::get_stellar_mainnet_fee_forwarder_address()
682 .or_else(|| non_empty_const(STELLAR_FEE_FORWARDER_MAINNET))
683 }
684 }
685
686 pub fn resolve_stellar_soroswap_router_address(is_testnet: bool) -> Option<String> {
688 if is_testnet {
689 Self::get_stellar_testnet_soroswap_router_address()
690 } else {
691 Self::get_stellar_mainnet_soroswap_router_address()
692 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_ROUTER.to_string()))
693 }
694 }
695
696 pub fn resolve_stellar_soroswap_factory_address(is_testnet: bool) -> Option<String> {
698 if is_testnet {
699 Self::get_stellar_testnet_soroswap_factory_address()
700 } else {
701 Self::get_stellar_mainnet_soroswap_factory_address()
702 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_FACTORY.to_string()))
703 }
704 }
705
706 pub fn resolve_stellar_soroswap_native_wrapper_address(is_testnet: bool) -> Option<String> {
708 if is_testnet {
709 Self::get_stellar_testnet_soroswap_native_wrapper_address()
710 } else {
711 Self::get_stellar_mainnet_soroswap_native_wrapper_address()
712 .or_else(|| Some(STELLAR_SOROSWAP_MAINNET_NATIVE_WRAPPER.to_string()))
713 }
714 }
715
716 pub fn get_worker_concurrency(worker_name: &str, default: usize) -> usize {
721 let env_var = format!(
722 "BACKGROUND_WORKER_{}_CONCURRENCY",
723 worker_name.to_uppercase()
724 );
725 env::var(&env_var)
726 .ok()
727 .and_then(|v| v.parse().ok())
728 .unwrap_or(default)
729 }
730
731 pub fn get_sqs_wait_time(queue_key: &str, default: u64) -> u64 {
738 let env_var = format!("SQS_{queue_key}_WAIT_TIME_SECONDS");
739 env::var(&env_var)
740 .ok()
741 .and_then(|v| v.parse().ok())
742 .unwrap_or(default)
743 .min(20)
744 }
745
746 pub fn get_sqs_poller_count(queue_key: &str, default: usize) -> usize {
755 let env_var = format!("SQS_{queue_key}_POLLER_COUNT");
756 env::var(&env_var)
757 .ok()
758 .and_then(|v| v.parse().ok())
759 .unwrap_or(default)
760 .max(1)
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767 use lazy_static::lazy_static;
768 use std::env;
769 use std::sync::Mutex;
770
771 lazy_static! {
773 static ref ENV_MUTEX: Mutex<()> = Mutex::new(());
774 }
775
776 #[test]
777 fn test_pubsub_config_getters() {
778 let _lock = match ENV_MUTEX.lock() {
779 Ok(guard) => guard,
780 Err(poisoned) => poisoned.into_inner(),
781 };
782
783 env::remove_var("PUBSUB_PROJECT_ID");
784 env::remove_var("PUBSUB_TOPIC_PREFIX");
785 env::remove_var("PUBSUB_EMULATOR_HOST");
786
787 assert!(
789 ServerConfig::get_pubsub_project_id().is_err(),
790 "project id must be required (error) when unset"
791 );
792 assert_eq!(ServerConfig::get_pubsub_topic_prefix(), "relayer");
793 assert_eq!(ServerConfig::get_pubsub_emulator_host(), None);
794
795 env::set_var("PUBSUB_PROJECT_ID", "my-project");
797 env::set_var("PUBSUB_TOPIC_PREFIX", "test");
798 env::set_var("PUBSUB_EMULATOR_HOST", "localhost:8085");
799
800 assert_eq!(ServerConfig::get_pubsub_project_id().unwrap(), "my-project");
801 assert_eq!(ServerConfig::get_pubsub_topic_prefix(), "test");
802 assert_eq!(
803 ServerConfig::get_pubsub_emulator_host(),
804 Some("localhost:8085".to_string())
805 );
806
807 env::set_var("PUBSUB_EMULATOR_HOST", "");
809 assert_eq!(ServerConfig::get_pubsub_emulator_host(), None);
810
811 env::remove_var("PUBSUB_PROJECT_ID");
812 env::remove_var("PUBSUB_TOPIC_PREFIX");
813 env::remove_var("PUBSUB_EMULATOR_HOST");
814 }
815
816 fn setup() {
817 env::remove_var("HOST");
819 env::remove_var("APP_PORT");
820 env::remove_var("REDIS_URL");
821 env::remove_var("CONFIG_DIR");
822 env::remove_var("CONFIG_FILE_NAME");
823 env::remove_var("CONFIG_FILE_PATH");
824 env::remove_var("API_KEY");
825 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
826 env::remove_var("RATE_LIMIT_BURST_SIZE");
827 env::remove_var("METRICS_PORT");
828 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
829 env::remove_var("RPC_TIMEOUT_MS");
830 env::remove_var("PROVIDER_MAX_RETRIES");
831 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
832 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
833 env::remove_var("PROVIDER_MAX_FAILOVERS");
834 env::remove_var("REPOSITORY_STORAGE_TYPE");
835 env::remove_var("RESET_STORAGE_ON_START");
836 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
837 env::remove_var("REDIS_READER_URL");
838 env::set_var("REDIS_URL", "redis://localhost:6379");
840 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
841 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
842 }
843
844 #[test]
845 fn test_default_values() {
846 let _lock = match ENV_MUTEX.lock() {
847 Ok(guard) => guard,
848 Err(poisoned) => poisoned.into_inner(),
849 };
850 setup();
851
852 let config = ServerConfig::from_env();
853
854 assert_eq!(config.host, "0.0.0.0");
855 assert_eq!(config.port, 8080);
856 assert_eq!(config.redis_url, "redis://localhost:6379");
857 assert_eq!(config.config_file_path, "./config/config.json");
858 assert_eq!(
859 config.api_key,
860 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
861 );
862 assert_eq!(config.rate_limit_requests_per_second, 100);
863 assert_eq!(config.rate_limit_burst_size, 300);
864 assert_eq!(config.metrics_port, 8081);
865 assert_eq!(config.redis_connection_timeout_ms, 5000);
866 assert_eq!(config.rpc_timeout_ms, 10000);
867 assert_eq!(config.provider_max_retries, 3);
868 assert_eq!(config.provider_retry_base_delay_ms, 100);
869 assert_eq!(config.provider_retry_max_delay_ms, 2000);
870 assert_eq!(config.provider_max_failovers, 3);
871 assert_eq!(config.provider_failure_threshold, 3);
872 assert_eq!(config.provider_pause_duration_secs, 60);
873 assert_eq!(
874 config.repository_storage_type,
875 RepositoryStorageType::InMemory
876 );
877 assert!(!config.reset_storage_on_start);
878 assert_eq!(config.transaction_expiration_hours, 4.0);
879 }
880
881 #[test]
882 fn test_invalid_port_values() {
883 let _lock = match ENV_MUTEX.lock() {
884 Ok(guard) => guard,
885 Err(poisoned) => poisoned.into_inner(),
886 };
887 setup();
888 env::set_var("REDIS_URL", "redis://localhost:6379");
889 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
890 env::set_var("APP_PORT", "not_a_number");
891 env::set_var("METRICS_PORT", "also_not_a_number");
892 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "invalid");
893 env::set_var("RATE_LIMIT_BURST_SIZE", "invalid");
894 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "invalid");
895 env::set_var("RPC_TIMEOUT_MS", "invalid");
896 env::set_var("PROVIDER_MAX_RETRIES", "invalid");
897 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "invalid");
898 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "invalid");
899 env::set_var("PROVIDER_MAX_FAILOVERS", "invalid");
900 env::set_var("REPOSITORY_STORAGE_TYPE", "invalid");
901 env::set_var("RESET_STORAGE_ON_START", "invalid");
902 env::set_var("TRANSACTION_EXPIRATION_HOURS", "invalid");
903 let config = ServerConfig::from_env();
904
905 assert_eq!(config.port, 8080);
907 assert_eq!(config.metrics_port, 8081);
908 assert_eq!(config.rate_limit_requests_per_second, 100);
909 assert_eq!(config.rate_limit_burst_size, 300);
910 assert_eq!(config.redis_connection_timeout_ms, 10000);
911 assert_eq!(config.rpc_timeout_ms, 10000);
912 assert_eq!(config.provider_max_retries, 3);
913 assert_eq!(config.provider_retry_base_delay_ms, 100);
914 assert_eq!(config.provider_retry_max_delay_ms, 2000);
915 assert_eq!(config.provider_max_failovers, 3);
916 assert_eq!(
917 config.repository_storage_type,
918 RepositoryStorageType::InMemory
919 );
920 assert!(!config.reset_storage_on_start);
921 assert_eq!(config.transaction_expiration_hours, 4.0);
922 }
923
924 #[test]
925 fn test_custom_values() {
926 let _lock = match ENV_MUTEX.lock() {
927 Ok(guard) => guard,
928 Err(poisoned) => poisoned.into_inner(),
929 };
930 setup();
931
932 env::set_var("HOST", "127.0.0.1");
933 env::set_var("APP_PORT", "9090");
934 env::set_var("REDIS_URL", "redis://custom:6379");
935 env::set_var("CONFIG_DIR", "custom");
936 env::set_var("CONFIG_FILE_NAME", "path.json");
937 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
938 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "200");
939 env::set_var("RATE_LIMIT_BURST_SIZE", "500");
940 env::set_var("METRICS_PORT", "9091");
941 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "10000");
942 env::set_var("RPC_TIMEOUT_MS", "33333");
943 env::set_var("PROVIDER_MAX_RETRIES", "5");
944 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
945 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "3000");
946 env::set_var("PROVIDER_MAX_FAILOVERS", "4");
947 env::set_var("REPOSITORY_STORAGE_TYPE", "in_memory");
948 env::set_var("RESET_STORAGE_ON_START", "true");
949 env::set_var("TRANSACTION_EXPIRATION_HOURS", "6");
950 let config = ServerConfig::from_env();
951
952 assert_eq!(config.host, "127.0.0.1");
953 assert_eq!(config.port, 9090);
954 assert_eq!(config.redis_url, "redis://custom:6379");
955 assert_eq!(config.config_file_path, "custom/path.json");
956 assert_eq!(
957 config.api_key,
958 SecretString::new("7EF1CB7C-5003-4696-B384-C72AF8C3E15D")
959 );
960 assert_eq!(config.rate_limit_requests_per_second, 200);
961 assert_eq!(config.rate_limit_burst_size, 500);
962 assert_eq!(config.metrics_port, 9091);
963 assert_eq!(config.redis_connection_timeout_ms, 10000);
964 assert_eq!(config.rpc_timeout_ms, 33333);
965 assert_eq!(config.provider_max_retries, 5);
966 assert_eq!(config.provider_retry_base_delay_ms, 200);
967 assert_eq!(config.provider_retry_max_delay_ms, 3000);
968 assert_eq!(config.provider_max_failovers, 4);
969 assert_eq!(
970 config.repository_storage_type,
971 RepositoryStorageType::InMemory
972 );
973 assert!(config.reset_storage_on_start);
974 assert_eq!(config.transaction_expiration_hours, 6.0);
975 }
976
977 #[test]
978 #[should_panic(expected = "Security error: API_KEY must be at least 32 characters long")]
979 fn test_invalid_api_key_length() {
980 let _lock = match ENV_MUTEX.lock() {
981 Ok(guard) => guard,
982 Err(poisoned) => poisoned.into_inner(),
983 };
984 setup();
985 env::set_var("REDIS_URL", "redis://localhost:6379");
986 env::set_var("API_KEY", "insufficient_length");
987 env::set_var("APP_PORT", "8080");
988 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "100");
989 env::set_var("RATE_LIMIT_BURST_SIZE", "300");
990 env::set_var("METRICS_PORT", "9091");
991 env::set_var("TRANSACTION_EXPIRATION_HOURS", "4");
992
993 let _ = ServerConfig::from_env();
994
995 panic!("Test should have panicked before reaching here");
996 }
997
998 #[test]
1000 fn test_individual_getters_with_defaults() {
1001 let _lock = match ENV_MUTEX.lock() {
1002 Ok(guard) => guard,
1003 Err(poisoned) => poisoned.into_inner(),
1004 };
1005
1006 env::remove_var("HOST");
1008 env::remove_var("APP_PORT");
1009 env::remove_var("REDIS_URL");
1010 env::remove_var("CONFIG_DIR");
1011 env::remove_var("CONFIG_FILE_NAME");
1012 env::remove_var("API_KEY");
1013 env::remove_var("RATE_LIMIT_REQUESTS_PER_SECOND");
1014 env::remove_var("RATE_LIMIT_BURST_SIZE");
1015 env::remove_var("METRICS_PORT");
1016 env::remove_var("ENABLE_SWAGGER");
1017 env::remove_var("REDIS_CONNECTION_TIMEOUT_MS");
1018 env::remove_var("REDIS_KEY_PREFIX");
1019 env::remove_var("REDIS_READER_URL");
1020 env::remove_var("RPC_TIMEOUT_MS");
1021 env::remove_var("PROVIDER_MAX_RETRIES");
1022 env::remove_var("PROVIDER_RETRY_BASE_DELAY_MS");
1023 env::remove_var("PROVIDER_RETRY_MAX_DELAY_MS");
1024 env::remove_var("PROVIDER_MAX_FAILOVERS");
1025 env::remove_var("REPOSITORY_STORAGE_TYPE");
1026 env::remove_var("RESET_STORAGE_ON_START");
1027 env::remove_var("STORAGE_ENCRYPTION_KEY");
1028 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
1029 env::remove_var("REDIS_POOL_MAX_SIZE");
1030 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1031
1032 assert_eq!(ServerConfig::get_host(), "0.0.0.0");
1034 assert_eq!(ServerConfig::get_port(), 8080);
1035 assert_eq!(ServerConfig::get_redis_url_optional(), None);
1036 assert_eq!(ServerConfig::get_config_file_path(), "./config/config.json");
1037 assert_eq!(ServerConfig::get_api_key_optional(), None);
1038 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 100);
1039 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 300);
1040 assert_eq!(ServerConfig::get_metrics_port(), 8081);
1041 assert!(!ServerConfig::get_enable_swagger());
1042 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 10000);
1043 assert_eq!(ServerConfig::get_redis_key_prefix(), "oz-relayer");
1044 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 10000);
1045 assert_eq!(ServerConfig::get_provider_max_retries(), 3);
1046 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 100);
1047 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 2000);
1048 assert_eq!(ServerConfig::get_provider_max_failovers(), 3);
1049 assert_eq!(
1050 ServerConfig::get_repository_storage_type(),
1051 RepositoryStorageType::InMemory
1052 );
1053 assert!(!ServerConfig::get_reset_storage_on_start());
1054 assert!(ServerConfig::get_storage_encryption_key().is_none());
1055 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 4.0);
1056 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1057 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1058 }
1059
1060 #[test]
1061 fn test_individual_getters_with_custom_values() {
1062 let _lock = match ENV_MUTEX.lock() {
1063 Ok(guard) => guard,
1064 Err(poisoned) => poisoned.into_inner(),
1065 };
1066
1067 env::set_var("HOST", "192.168.1.1");
1069 env::set_var("APP_PORT", "9999");
1070 env::set_var("REDIS_URL", "redis://custom:6379");
1071 env::set_var("CONFIG_DIR", "/custom/config");
1072 env::set_var("CONFIG_FILE_NAME", "custom.json");
1073 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1074 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "500");
1075 env::set_var("RATE_LIMIT_BURST_SIZE", "1000");
1076 env::set_var("METRICS_PORT", "9999");
1077 env::set_var("ENABLE_SWAGGER", "true");
1078 env::set_var("REDIS_CONNECTION_TIMEOUT_MS", "5000");
1079 env::set_var("REDIS_KEY_PREFIX", "custom-prefix");
1080 env::set_var("RPC_TIMEOUT_MS", "15000");
1081 env::set_var("PROVIDER_MAX_RETRIES", "5");
1082 env::set_var("PROVIDER_RETRY_BASE_DELAY_MS", "200");
1083 env::set_var("PROVIDER_RETRY_MAX_DELAY_MS", "5000");
1084 env::set_var("PROVIDER_MAX_FAILOVERS", "10");
1085 env::set_var("REPOSITORY_STORAGE_TYPE", "redis");
1086 env::set_var("RESET_STORAGE_ON_START", "true");
1087 env::set_var("STORAGE_ENCRYPTION_KEY", "my-encryption-key");
1088 env::set_var("TRANSACTION_EXPIRATION_HOURS", "12");
1089 env::set_var("REDIS_POOL_MAX_SIZE", "200");
1090 env::set_var("REDIS_POOL_TIMEOUT_MS", "20000");
1091
1092 assert_eq!(ServerConfig::get_host(), "192.168.1.1");
1094 assert_eq!(ServerConfig::get_port(), 9999);
1095 assert_eq!(
1096 ServerConfig::get_redis_url_optional(),
1097 Some("redis://custom:6379".to_string())
1098 );
1099 assert_eq!(
1100 ServerConfig::get_config_file_path(),
1101 "/custom/config/custom.json"
1102 );
1103 assert!(ServerConfig::get_api_key_optional().is_some());
1104 assert_eq!(ServerConfig::get_rate_limit_requests_per_second(), 500);
1105 assert_eq!(ServerConfig::get_rate_limit_burst_size(), 1000);
1106 assert_eq!(ServerConfig::get_metrics_port(), 9999);
1107 assert!(ServerConfig::get_enable_swagger());
1108 assert_eq!(ServerConfig::get_redis_connection_timeout_ms(), 5000);
1109 assert_eq!(ServerConfig::get_redis_key_prefix(), "custom-prefix");
1110 assert_eq!(ServerConfig::get_rpc_timeout_ms(), 15000);
1111 assert_eq!(ServerConfig::get_provider_max_retries(), 5);
1112 assert_eq!(ServerConfig::get_provider_retry_base_delay_ms(), 200);
1113 assert_eq!(ServerConfig::get_provider_retry_max_delay_ms(), 5000);
1114 assert_eq!(ServerConfig::get_provider_max_failovers(), 10);
1115 assert_eq!(
1116 ServerConfig::get_repository_storage_type(),
1117 RepositoryStorageType::Redis
1118 );
1119 assert!(ServerConfig::get_reset_storage_on_start());
1120 assert!(ServerConfig::get_storage_encryption_key().is_some());
1121 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 12.0);
1122 assert_eq!(ServerConfig::get_redis_pool_max_size(), 200);
1123 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 20000);
1124 }
1125
1126 #[test]
1127 fn test_get_redis_pool_max_size() {
1128 let _lock = match ENV_MUTEX.lock() {
1129 Ok(guard) => guard,
1130 Err(poisoned) => poisoned.into_inner(),
1131 };
1132 env::remove_var("REDIS_POOL_MAX_SIZE");
1134 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1135
1136 env::set_var("REDIS_POOL_MAX_SIZE", "100");
1138 assert_eq!(ServerConfig::get_redis_pool_max_size(), 100);
1139
1140 env::set_var("REDIS_POOL_MAX_SIZE", "not_a_number");
1142 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1143
1144 env::set_var("REDIS_POOL_MAX_SIZE", "0");
1146 assert_eq!(ServerConfig::get_redis_pool_max_size(), 500);
1147
1148 env::set_var("REDIS_POOL_MAX_SIZE", "10000");
1150 assert_eq!(ServerConfig::get_redis_pool_max_size(), 10000);
1151
1152 env::remove_var("REDIS_POOL_MAX_SIZE");
1154 }
1155
1156 #[test]
1157 fn test_get_redis_pool_timeout_ms() {
1158 let _lock = match ENV_MUTEX.lock() {
1159 Ok(guard) => guard,
1160 Err(poisoned) => poisoned.into_inner(),
1161 };
1162
1163 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1165 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1166
1167 env::set_var("REDIS_POOL_TIMEOUT_MS", "15000");
1169 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 15000);
1170
1171 env::set_var("REDIS_POOL_TIMEOUT_MS", "not_a_number");
1173 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1174
1175 env::set_var("REDIS_POOL_TIMEOUT_MS", "0");
1177 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 10000);
1178
1179 env::set_var("REDIS_POOL_TIMEOUT_MS", "60000");
1181 assert_eq!(ServerConfig::get_redis_pool_timeout_ms(), 60000);
1182
1183 env::remove_var("REDIS_POOL_TIMEOUT_MS");
1185 }
1186
1187 #[test]
1188 fn test_fractional_transaction_expiration_hours() {
1189 let _lock = match ENV_MUTEX.lock() {
1190 Ok(guard) => guard,
1191 Err(poisoned) => poisoned.into_inner(),
1192 };
1193 setup();
1194
1195 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.1");
1197 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.1);
1198
1199 env::set_var("TRANSACTION_EXPIRATION_HOURS", "0.5");
1201 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 0.5);
1202
1203 env::set_var("TRANSACTION_EXPIRATION_HOURS", "24");
1205 assert_eq!(ServerConfig::get_transaction_expiration_hours(), 24.0);
1206
1207 env::remove_var("TRANSACTION_EXPIRATION_HOURS");
1209 }
1210
1211 #[test]
1212 #[should_panic(expected = "REDIS_URL must be set")]
1213 fn test_get_redis_url_panics_when_not_set() {
1214 let _lock = match ENV_MUTEX.lock() {
1215 Ok(guard) => guard,
1216 Err(poisoned) => poisoned.into_inner(),
1217 };
1218
1219 env::remove_var("REDIS_URL");
1220 let _ = ServerConfig::get_redis_url();
1221 }
1222
1223 #[test]
1224 #[should_panic(expected = "API_KEY must be set")]
1225 fn test_get_api_key_panics_when_not_set() {
1226 let _lock = match ENV_MUTEX.lock() {
1227 Ok(guard) => guard,
1228 Err(poisoned) => poisoned.into_inner(),
1229 };
1230
1231 env::remove_var("API_KEY");
1232 let _ = ServerConfig::get_api_key();
1233 }
1234
1235 #[test]
1236 fn test_optional_getters_return_none_safely() {
1237 let _lock = match ENV_MUTEX.lock() {
1238 Ok(guard) => guard,
1239 Err(poisoned) => poisoned.into_inner(),
1240 };
1241
1242 env::remove_var("REDIS_URL");
1243 env::remove_var("API_KEY");
1244 env::remove_var("STORAGE_ENCRYPTION_KEY");
1245
1246 assert!(ServerConfig::get_redis_url_optional().is_none());
1247 assert!(ServerConfig::get_api_key_optional().is_none());
1248 assert!(ServerConfig::get_storage_encryption_key().is_none());
1249 }
1250
1251 #[test]
1252 fn test_refactored_from_env_equivalence() {
1253 let _lock = match ENV_MUTEX.lock() {
1254 Ok(guard) => guard,
1255 Err(poisoned) => poisoned.into_inner(),
1256 };
1257 setup();
1258
1259 env::set_var("HOST", "custom-host");
1261 env::set_var("APP_PORT", "7777");
1262 env::set_var("RATE_LIMIT_REQUESTS_PER_SECOND", "250");
1263 env::set_var("METRICS_PORT", "7778");
1264 env::set_var("ENABLE_SWAGGER", "true");
1265 env::set_var("PROVIDER_MAX_RETRIES", "7");
1266 env::set_var("TRANSACTION_EXPIRATION_HOURS", "8");
1267
1268 let config = ServerConfig::from_env();
1269
1270 assert_eq!(config.host, ServerConfig::get_host());
1272 assert_eq!(config.port, ServerConfig::get_port());
1273 assert_eq!(config.redis_url, ServerConfig::get_redis_url());
1274 assert_eq!(
1275 config.config_file_path,
1276 ServerConfig::get_config_file_path()
1277 );
1278 assert_eq!(config.api_key, ServerConfig::get_api_key());
1279 assert_eq!(
1280 config.rate_limit_requests_per_second,
1281 ServerConfig::get_rate_limit_requests_per_second()
1282 );
1283 assert_eq!(
1284 config.rate_limit_burst_size,
1285 ServerConfig::get_rate_limit_burst_size()
1286 );
1287 assert_eq!(config.metrics_port, ServerConfig::get_metrics_port());
1288 assert_eq!(config.enable_swagger, ServerConfig::get_enable_swagger());
1289 assert_eq!(
1290 config.redis_connection_timeout_ms,
1291 ServerConfig::get_redis_connection_timeout_ms()
1292 );
1293 assert_eq!(
1294 config.redis_key_prefix,
1295 ServerConfig::get_redis_key_prefix()
1296 );
1297 assert_eq!(config.rpc_timeout_ms, ServerConfig::get_rpc_timeout_ms());
1298 assert_eq!(
1299 config.provider_max_retries,
1300 ServerConfig::get_provider_max_retries()
1301 );
1302 assert_eq!(
1303 config.provider_retry_base_delay_ms,
1304 ServerConfig::get_provider_retry_base_delay_ms()
1305 );
1306 assert_eq!(
1307 config.provider_retry_max_delay_ms,
1308 ServerConfig::get_provider_retry_max_delay_ms()
1309 );
1310 assert_eq!(
1311 config.provider_max_failovers,
1312 ServerConfig::get_provider_max_failovers()
1313 );
1314 assert_eq!(
1315 config.repository_storage_type,
1316 ServerConfig::get_repository_storage_type()
1317 );
1318 assert_eq!(
1319 config.reset_storage_on_start,
1320 ServerConfig::get_reset_storage_on_start()
1321 );
1322 assert_eq!(
1323 config.storage_encryption_key,
1324 ServerConfig::get_storage_encryption_key()
1325 );
1326 assert_eq!(
1327 config.transaction_expiration_hours,
1328 ServerConfig::get_transaction_expiration_hours()
1329 );
1330 }
1331
1332 mod get_worker_concurrency_tests {
1333 use super::*;
1334 use serial_test::serial;
1335
1336 #[test]
1337 #[serial]
1338 fn test_returns_default_when_env_not_set() {
1339 let worker_name = "test_worker";
1340 let env_var = format!(
1341 "BACKGROUND_WORKER_{}_CONCURRENCY",
1342 worker_name.to_uppercase()
1343 );
1344
1345 env::remove_var(&env_var);
1347
1348 let default_value = 42;
1349 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1350
1351 assert_eq!(
1352 result, default_value,
1353 "Should return default value when env var is not set"
1354 );
1355 }
1356
1357 #[test]
1358 #[serial]
1359 fn test_returns_env_value_when_set() {
1360 let worker_name = "status_checker";
1361 let env_var = format!(
1362 "BACKGROUND_WORKER_{}_CONCURRENCY",
1363 worker_name.to_uppercase()
1364 );
1365
1366 env::set_var(&env_var, "100");
1368
1369 let default_value = 10;
1370 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1371
1372 assert_eq!(result, 100, "Should return env var value when set");
1373
1374 env::remove_var(&env_var);
1376 }
1377
1378 #[test]
1379 #[serial]
1380 fn test_returns_default_when_env_invalid() {
1381 let worker_name = "invalid_worker";
1382 let env_var = format!(
1383 "BACKGROUND_WORKER_{}_CONCURRENCY",
1384 worker_name.to_uppercase()
1385 );
1386
1387 env::set_var(&env_var, "not_a_number");
1389
1390 let default_value = 25;
1391 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1392
1393 assert_eq!(
1394 result, default_value,
1395 "Should return default value when env var is invalid"
1396 );
1397
1398 env::remove_var(&env_var);
1400 }
1401
1402 #[test]
1403 #[serial]
1404 fn test_returns_default_when_env_empty() {
1405 let worker_name = "empty_worker";
1406 let env_var = format!(
1407 "BACKGROUND_WORKER_{}_CONCURRENCY",
1408 worker_name.to_uppercase()
1409 );
1410
1411 env::set_var(&env_var, "");
1413
1414 let default_value = 15;
1415 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1416
1417 assert_eq!(
1418 result, default_value,
1419 "Should return default value when env var is empty"
1420 );
1421
1422 env::remove_var(&env_var);
1424 }
1425
1426 #[test]
1427 #[serial]
1428 fn test_returns_default_when_env_negative() {
1429 let worker_name = "negative_worker";
1430 let env_var = format!(
1431 "BACKGROUND_WORKER_{}_CONCURRENCY",
1432 worker_name.to_uppercase()
1433 );
1434
1435 env::set_var(&env_var, "-5");
1437
1438 let default_value = 20;
1439 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1440
1441 assert_eq!(
1442 result, default_value,
1443 "Should return default value when env var is negative"
1444 );
1445
1446 env::remove_var(&env_var);
1448 }
1449
1450 #[test]
1451 #[serial]
1452 fn test_env_var_name_formatting() {
1453 let worker_names = vec![
1455 (
1456 "transaction_sender",
1457 "BACKGROUND_WORKER_TRANSACTION_SENDER_CONCURRENCY",
1458 ),
1459 (
1460 "status_checker_evm",
1461 "BACKGROUND_WORKER_STATUS_CHECKER_EVM_CONCURRENCY",
1462 ),
1463 (
1464 "notification_sender",
1465 "BACKGROUND_WORKER_NOTIFICATION_SENDER_CONCURRENCY",
1466 ),
1467 ];
1468
1469 for (worker_name, expected_env_var) in worker_names {
1470 let actual_env_var = format!(
1471 "BACKGROUND_WORKER_{}_CONCURRENCY",
1472 worker_name.to_uppercase()
1473 );
1474 assert_eq!(
1475 actual_env_var, expected_env_var,
1476 "Env var name should be correctly formatted for worker: {worker_name}"
1477 );
1478 }
1479 }
1480
1481 #[test]
1482 #[serial]
1483 fn test_zero_value() {
1484 let worker_name = "zero_worker";
1485 let env_var = format!(
1486 "BACKGROUND_WORKER_{}_CONCURRENCY",
1487 worker_name.to_uppercase()
1488 );
1489
1490 env::set_var(&env_var, "0");
1492
1493 let default_value = 30;
1494 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1495
1496 assert_eq!(result, 0, "Should accept zero as a valid value");
1497
1498 env::remove_var(&env_var);
1500 }
1501
1502 #[test]
1503 #[serial]
1504 fn test_large_value() {
1505 let worker_name = "large_worker";
1506 let env_var = format!(
1507 "BACKGROUND_WORKER_{}_CONCURRENCY",
1508 worker_name.to_uppercase()
1509 );
1510
1511 env::set_var(&env_var, "10000");
1513
1514 let default_value = 50;
1515 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1516
1517 assert_eq!(result, 10000, "Should accept large values");
1518
1519 env::remove_var(&env_var);
1521 }
1522
1523 #[test]
1524 #[serial]
1525 fn test_whitespace_in_value() {
1526 let worker_name = "whitespace_worker";
1527 let env_var = format!(
1528 "BACKGROUND_WORKER_{}_CONCURRENCY",
1529 worker_name.to_uppercase()
1530 );
1531
1532 env::set_var(&env_var, " 75 ");
1534
1535 let default_value = 35;
1536 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1537
1538 assert_eq!(
1541 result, default_value,
1542 "Should return default value when value has whitespace"
1543 );
1544
1545 env::remove_var(&env_var);
1547 }
1548
1549 #[test]
1550 #[serial]
1551 fn test_float_value_returns_default() {
1552 let worker_name = "float_worker";
1553 let env_var = format!(
1554 "BACKGROUND_WORKER_{}_CONCURRENCY",
1555 worker_name.to_uppercase()
1556 );
1557
1558 env::set_var(&env_var, "12.5");
1560
1561 let default_value = 40;
1562 let result = ServerConfig::get_worker_concurrency(worker_name, default_value);
1563
1564 assert_eq!(
1565 result, default_value,
1566 "Should return default value for float input"
1567 );
1568
1569 env::remove_var(&env_var);
1571 }
1572 }
1573
1574 mod get_relayer_concurrency_limit_tests {
1575 use super::*;
1576 use serial_test::serial;
1577
1578 #[test]
1579 #[serial]
1580 fn test_returns_default_when_env_not_set() {
1581 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1582 let result = ServerConfig::get_relayer_concurrency_limit();
1583 assert_eq!(result, 100, "Should return default value of 100");
1584 }
1585
1586 #[test]
1587 #[serial]
1588 fn test_returns_env_value_when_set() {
1589 env::set_var("RELAYER_CONCURRENCY_LIMIT", "250");
1590 let result = ServerConfig::get_relayer_concurrency_limit();
1591 assert_eq!(result, 250, "Should return env var value");
1592 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1593 }
1594
1595 #[test]
1596 #[serial]
1597 fn test_returns_default_when_env_invalid() {
1598 env::set_var("RELAYER_CONCURRENCY_LIMIT", "not_a_number");
1599 let result = ServerConfig::get_relayer_concurrency_limit();
1600 assert_eq!(result, 100, "Should return default value when invalid");
1601 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1602 }
1603
1604 #[test]
1605 #[serial]
1606 fn test_returns_default_when_env_empty() {
1607 env::set_var("RELAYER_CONCURRENCY_LIMIT", "");
1608 let result = ServerConfig::get_relayer_concurrency_limit();
1609 assert_eq!(result, 100, "Should return default value when empty");
1610 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1611 }
1612
1613 #[test]
1614 #[serial]
1615 fn test_zero_value() {
1616 env::set_var("RELAYER_CONCURRENCY_LIMIT", "0");
1617 let result = ServerConfig::get_relayer_concurrency_limit();
1618 assert_eq!(result, 0, "Should accept zero as valid value");
1619 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1620 }
1621
1622 #[test]
1623 #[serial]
1624 fn test_large_value() {
1625 env::set_var("RELAYER_CONCURRENCY_LIMIT", "5000");
1626 let result = ServerConfig::get_relayer_concurrency_limit();
1627 assert_eq!(result, 5000, "Should accept large values");
1628 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1629 }
1630
1631 #[test]
1632 #[serial]
1633 fn test_negative_value_returns_default() {
1634 env::set_var("RELAYER_CONCURRENCY_LIMIT", "-10");
1635 let result = ServerConfig::get_relayer_concurrency_limit();
1636 assert_eq!(result, 100, "Should return default for negative value");
1637 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1638 }
1639
1640 #[test]
1641 #[serial]
1642 fn test_float_value_returns_default() {
1643 env::set_var("RELAYER_CONCURRENCY_LIMIT", "100.5");
1644 let result = ServerConfig::get_relayer_concurrency_limit();
1645 assert_eq!(result, 100, "Should return default for float value");
1646 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1647 }
1648
1649 #[test]
1650 #[serial]
1651 fn test_whitespace_value_returns_default() {
1652 env::set_var("RELAYER_CONCURRENCY_LIMIT", " 150 ");
1653 let result = ServerConfig::get_relayer_concurrency_limit();
1654 assert_eq!(
1655 result, 100,
1656 "Should return default when value has whitespace"
1657 );
1658 env::remove_var("RELAYER_CONCURRENCY_LIMIT");
1659 }
1660 }
1661
1662 mod get_max_connections_tests {
1663 use super::*;
1664 use serial_test::serial;
1665
1666 #[test]
1667 #[serial]
1668 fn test_returns_default_when_env_not_set() {
1669 env::remove_var("MAX_CONNECTIONS");
1670 let result = ServerConfig::get_max_connections();
1671 assert_eq!(result, 256, "Should return default value of 256");
1672 }
1673
1674 #[test]
1675 #[serial]
1676 fn test_returns_env_value_when_set() {
1677 env::set_var("MAX_CONNECTIONS", "512");
1678 let result = ServerConfig::get_max_connections();
1679 assert_eq!(result, 512, "Should return env var value");
1680 env::remove_var("MAX_CONNECTIONS");
1681 }
1682
1683 #[test]
1684 #[serial]
1685 fn test_returns_default_when_env_invalid() {
1686 env::set_var("MAX_CONNECTIONS", "invalid");
1687 let result = ServerConfig::get_max_connections();
1688 assert_eq!(result, 256, "Should return default value when invalid");
1689 env::remove_var("MAX_CONNECTIONS");
1690 }
1691
1692 #[test]
1693 #[serial]
1694 fn test_returns_default_when_env_empty() {
1695 env::set_var("MAX_CONNECTIONS", "");
1696 let result = ServerConfig::get_max_connections();
1697 assert_eq!(result, 256, "Should return default value when empty");
1698 env::remove_var("MAX_CONNECTIONS");
1699 }
1700
1701 #[test]
1702 #[serial]
1703 fn test_zero_value() {
1704 env::set_var("MAX_CONNECTIONS", "0");
1705 let result = ServerConfig::get_max_connections();
1706 assert_eq!(result, 0, "Should accept zero as valid value");
1707 env::remove_var("MAX_CONNECTIONS");
1708 }
1709
1710 #[test]
1711 #[serial]
1712 fn test_large_value() {
1713 env::set_var("MAX_CONNECTIONS", "10000");
1714 let result = ServerConfig::get_max_connections();
1715 assert_eq!(result, 10000, "Should accept large values");
1716 env::remove_var("MAX_CONNECTIONS");
1717 }
1718
1719 #[test]
1720 #[serial]
1721 fn test_negative_value_returns_default() {
1722 env::set_var("MAX_CONNECTIONS", "-100");
1723 let result = ServerConfig::get_max_connections();
1724 assert_eq!(result, 256, "Should return default for negative value");
1725 env::remove_var("MAX_CONNECTIONS");
1726 }
1727
1728 #[test]
1729 #[serial]
1730 fn test_float_value_returns_default() {
1731 env::set_var("MAX_CONNECTIONS", "256.5");
1732 let result = ServerConfig::get_max_connections();
1733 assert_eq!(result, 256, "Should return default for float value");
1734 env::remove_var("MAX_CONNECTIONS");
1735 }
1736 }
1737
1738 mod get_connection_backlog_tests {
1739 use super::*;
1740 use serial_test::serial;
1741
1742 #[test]
1743 #[serial]
1744 fn test_returns_default_when_env_not_set() {
1745 env::remove_var("CONNECTION_BACKLOG");
1746 let result = ServerConfig::get_connection_backlog();
1747 assert_eq!(result, 511, "Should return default value of 511");
1748 }
1749
1750 #[test]
1751 #[serial]
1752 fn test_returns_env_value_when_set() {
1753 env::set_var("CONNECTION_BACKLOG", "1024");
1754 let result = ServerConfig::get_connection_backlog();
1755 assert_eq!(result, 1024, "Should return env var value");
1756 env::remove_var("CONNECTION_BACKLOG");
1757 }
1758
1759 #[test]
1760 #[serial]
1761 fn test_returns_default_when_env_invalid() {
1762 env::set_var("CONNECTION_BACKLOG", "not_a_number");
1763 let result = ServerConfig::get_connection_backlog();
1764 assert_eq!(result, 511, "Should return default value when invalid");
1765 env::remove_var("CONNECTION_BACKLOG");
1766 }
1767
1768 #[test]
1769 #[serial]
1770 fn test_returns_default_when_env_empty() {
1771 env::set_var("CONNECTION_BACKLOG", "");
1772 let result = ServerConfig::get_connection_backlog();
1773 assert_eq!(result, 511, "Should return default value when empty");
1774 env::remove_var("CONNECTION_BACKLOG");
1775 }
1776
1777 #[test]
1778 #[serial]
1779 fn test_zero_value() {
1780 env::set_var("CONNECTION_BACKLOG", "0");
1781 let result = ServerConfig::get_connection_backlog();
1782 assert_eq!(result, 0, "Should accept zero as valid value");
1783 env::remove_var("CONNECTION_BACKLOG");
1784 }
1785
1786 #[test]
1787 #[serial]
1788 fn test_large_value() {
1789 env::set_var("CONNECTION_BACKLOG", "65535");
1790 let result = ServerConfig::get_connection_backlog();
1791 assert_eq!(result, 65535, "Should accept large values");
1792 env::remove_var("CONNECTION_BACKLOG");
1793 }
1794
1795 #[test]
1796 #[serial]
1797 fn test_negative_value_returns_default() {
1798 env::set_var("CONNECTION_BACKLOG", "-50");
1799 let result = ServerConfig::get_connection_backlog();
1800 assert_eq!(result, 511, "Should return default for negative value");
1801 env::remove_var("CONNECTION_BACKLOG");
1802 }
1803
1804 #[test]
1805 #[serial]
1806 fn test_float_value_returns_default() {
1807 env::set_var("CONNECTION_BACKLOG", "511.5");
1808 let result = ServerConfig::get_connection_backlog();
1809 assert_eq!(result, 511, "Should return default for float value");
1810 env::remove_var("CONNECTION_BACKLOG");
1811 }
1812
1813 #[test]
1814 #[serial]
1815 fn test_common_production_values() {
1816 let test_cases = vec![
1818 (128, "Small server"),
1819 (511, "Default"),
1820 (1024, "Medium server"),
1821 (2048, "Large server"),
1822 ];
1823
1824 for (value, description) in test_cases {
1825 env::set_var("CONNECTION_BACKLOG", value.to_string());
1826 let result = ServerConfig::get_connection_backlog();
1827 assert_eq!(result, value, "Should accept {description}: {value}");
1828 }
1829
1830 env::remove_var("CONNECTION_BACKLOG");
1831 }
1832 }
1833
1834 mod get_request_timeout_seconds_tests {
1835 use super::*;
1836 use serial_test::serial;
1837
1838 #[test]
1839 #[serial]
1840 fn test_returns_default_when_env_not_set() {
1841 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1842 let result = ServerConfig::get_request_timeout_seconds();
1843 assert_eq!(result, 30, "Should return default value of 30");
1844 }
1845
1846 #[test]
1847 #[serial]
1848 fn test_returns_env_value_when_set() {
1849 env::set_var("REQUEST_TIMEOUT_SECONDS", "60");
1850 let result = ServerConfig::get_request_timeout_seconds();
1851 assert_eq!(result, 60, "Should return env var value");
1852 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1853 }
1854
1855 #[test]
1856 #[serial]
1857 fn test_returns_default_when_env_invalid() {
1858 env::set_var("REQUEST_TIMEOUT_SECONDS", "invalid");
1859 let result = ServerConfig::get_request_timeout_seconds();
1860 assert_eq!(result, 30, "Should return default value when invalid");
1861 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1862 }
1863
1864 #[test]
1865 #[serial]
1866 fn test_returns_default_when_env_empty() {
1867 env::set_var("REQUEST_TIMEOUT_SECONDS", "");
1868 let result = ServerConfig::get_request_timeout_seconds();
1869 assert_eq!(result, 30, "Should return default value when empty");
1870 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1871 }
1872
1873 #[test]
1874 #[serial]
1875 fn test_zero_value() {
1876 env::set_var("REQUEST_TIMEOUT_SECONDS", "0");
1877 let result = ServerConfig::get_request_timeout_seconds();
1878 assert_eq!(result, 0, "Should accept zero as valid value");
1879 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1880 }
1881
1882 #[test]
1883 #[serial]
1884 fn test_large_value() {
1885 env::set_var("REQUEST_TIMEOUT_SECONDS", "300");
1886 let result = ServerConfig::get_request_timeout_seconds();
1887 assert_eq!(result, 300, "Should accept large values");
1888 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1889 }
1890
1891 #[test]
1892 #[serial]
1893 fn test_negative_value_returns_default() {
1894 env::set_var("REQUEST_TIMEOUT_SECONDS", "-10");
1895 let result = ServerConfig::get_request_timeout_seconds();
1896 assert_eq!(result, 30, "Should return default for negative value");
1897 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1898 }
1899
1900 #[test]
1901 #[serial]
1902 fn test_float_value_returns_default() {
1903 env::set_var("REQUEST_TIMEOUT_SECONDS", "30.5");
1904 let result = ServerConfig::get_request_timeout_seconds();
1905 assert_eq!(result, 30, "Should return default for float value");
1906 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1907 }
1908
1909 #[test]
1910 #[serial]
1911 fn test_common_timeout_values() {
1912 let test_cases = vec![
1914 (10, "Short timeout"),
1915 (30, "Default timeout"),
1916 (60, "Moderate timeout"),
1917 (120, "Long timeout"),
1918 ];
1919
1920 for (value, description) in test_cases {
1921 env::set_var("REQUEST_TIMEOUT_SECONDS", value.to_string());
1922 let result = ServerConfig::get_request_timeout_seconds();
1923 assert_eq!(result, value, "Should accept {description}: {value}");
1924 }
1925
1926 env::remove_var("REQUEST_TIMEOUT_SECONDS");
1927 }
1928 }
1929
1930 mod get_redis_reader_url_tests {
1931 use super::*;
1932 use serial_test::serial;
1933
1934 #[test]
1935 #[serial]
1936 fn test_returns_none_when_env_not_set() {
1937 env::remove_var("REDIS_READER_URL");
1938 let result = ServerConfig::get_redis_reader_url_optional();
1939 assert!(
1940 result.is_none(),
1941 "Should return None when env var is not set"
1942 );
1943 }
1944
1945 #[test]
1946 #[serial]
1947 fn test_returns_value_when_set() {
1948 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1949 let result = ServerConfig::get_redis_reader_url_optional();
1950 assert_eq!(
1951 result,
1952 Some("redis://reader:6379".to_string()),
1953 "Should return the env var value"
1954 );
1955 env::remove_var("REDIS_READER_URL");
1956 }
1957
1958 #[test]
1959 #[serial]
1960 fn test_returns_empty_string_when_set_to_empty() {
1961 env::set_var("REDIS_READER_URL", "");
1962 let result = ServerConfig::get_redis_reader_url_optional();
1963 assert_eq!(
1964 result,
1965 Some("".to_string()),
1966 "Should return empty string when set to empty"
1967 );
1968 env::remove_var("REDIS_READER_URL");
1969 }
1970
1971 #[test]
1972 #[serial]
1973 fn test_aws_elasticache_reader_url() {
1974 let reader_url = "redis://my-cluster-ro.xxx.cache.amazonaws.com:6379";
1976 env::set_var("REDIS_READER_URL", reader_url);
1977 let result = ServerConfig::get_redis_reader_url_optional();
1978 assert_eq!(
1979 result,
1980 Some(reader_url.to_string()),
1981 "Should accept AWS ElastiCache reader endpoint"
1982 );
1983 env::remove_var("REDIS_READER_URL");
1984 }
1985
1986 #[test]
1987 #[serial]
1988 fn test_config_includes_redis_reader_url() {
1989 env::set_var("REDIS_URL", "redis://primary:6379");
1990 env::set_var("REDIS_READER_URL", "redis://reader:6379");
1991 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
1992
1993 let config = ServerConfig::from_env();
1994
1995 assert_eq!(config.redis_url, "redis://primary:6379");
1996 assert_eq!(
1997 config.redis_reader_url,
1998 Some("redis://reader:6379".to_string())
1999 );
2000
2001 env::remove_var("REDIS_URL");
2002 env::remove_var("REDIS_READER_URL");
2003 env::remove_var("API_KEY");
2004 }
2005
2006 #[test]
2007 #[serial]
2008 fn test_config_without_redis_reader_url() {
2009 env::set_var("REDIS_URL", "redis://primary:6379");
2010 env::remove_var("REDIS_READER_URL");
2011 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2012
2013 let config = ServerConfig::from_env();
2014
2015 assert_eq!(config.redis_url, "redis://primary:6379");
2016 assert!(
2017 config.redis_reader_url.is_none(),
2018 "redis_reader_url should be None when not set"
2019 );
2020
2021 env::remove_var("REDIS_URL");
2022 env::remove_var("API_KEY");
2023 }
2024 }
2025
2026 mod get_sqs_queue_type_tests {
2027 use super::*;
2028 use serial_test::serial;
2029
2030 #[test]
2031 #[serial]
2032 fn test_returns_auto_when_env_not_set() {
2033 env::remove_var("SQS_QUEUE_TYPE");
2034 let result = ServerConfig::get_sqs_queue_type();
2035 assert_eq!(result, "auto", "Should default to 'auto'");
2036 }
2037
2038 #[test]
2039 #[serial]
2040 fn test_returns_fifo_when_set() {
2041 env::set_var("SQS_QUEUE_TYPE", "fifo");
2042 let result = ServerConfig::get_sqs_queue_type();
2043 assert_eq!(result, "fifo");
2044 env::remove_var("SQS_QUEUE_TYPE");
2045 }
2046
2047 #[test]
2048 #[serial]
2049 fn test_returns_standard_when_set() {
2050 env::set_var("SQS_QUEUE_TYPE", "standard");
2051 let result = ServerConfig::get_sqs_queue_type();
2052 assert_eq!(result, "standard");
2053 env::remove_var("SQS_QUEUE_TYPE");
2054 }
2055
2056 #[test]
2057 #[serial]
2058 fn test_returns_raw_value_for_unknown() {
2059 env::set_var("SQS_QUEUE_TYPE", "unknown");
2060 let result = ServerConfig::get_sqs_queue_type();
2061 assert_eq!(result, "unknown");
2062 env::remove_var("SQS_QUEUE_TYPE");
2063 }
2064 }
2065
2066 mod get_redis_reader_pool_max_size_tests {
2067 use super::*;
2068 use serial_test::serial;
2069
2070 #[test]
2071 #[serial]
2072 fn test_returns_default_when_env_not_set() {
2073 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2074 let result = ServerConfig::get_redis_reader_pool_max_size();
2075 assert_eq!(
2076 result, 1000,
2077 "Should return default 1000 when env var is not set"
2078 );
2079 }
2080
2081 #[test]
2082 #[serial]
2083 fn test_returns_value_when_set() {
2084 env::set_var("REDIS_READER_POOL_MAX_SIZE", "2000");
2085 let result = ServerConfig::get_redis_reader_pool_max_size();
2086 assert_eq!(result, 2000, "Should return the parsed value");
2087 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2088 }
2089
2090 #[test]
2091 #[serial]
2092 fn test_returns_default_when_invalid() {
2093 env::set_var("REDIS_READER_POOL_MAX_SIZE", "not_a_number");
2094 let result = ServerConfig::get_redis_reader_pool_max_size();
2095 assert_eq!(
2096 result, 1000,
2097 "Should return default 1000 for invalid values"
2098 );
2099 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2100 }
2101
2102 #[test]
2103 #[serial]
2104 fn test_returns_default_when_zero() {
2105 env::set_var("REDIS_READER_POOL_MAX_SIZE", "0");
2106 let result = ServerConfig::get_redis_reader_pool_max_size();
2107 assert_eq!(result, 1000, "Should return default 1000 when value is 0");
2108 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2109 }
2110
2111 #[test]
2112 #[serial]
2113 fn test_returns_default_when_negative() {
2114 env::set_var("REDIS_READER_POOL_MAX_SIZE", "-100");
2115 let result = ServerConfig::get_redis_reader_pool_max_size();
2116 assert_eq!(
2117 result, 1000,
2118 "Should return default 1000 for negative values"
2119 );
2120 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2121 }
2122
2123 #[test]
2124 #[serial]
2125 fn test_config_includes_reader_pool_max_size() {
2126 env::set_var("REDIS_URL", "redis://primary:6379");
2127 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2128 env::set_var("REDIS_READER_POOL_MAX_SIZE", "750");
2129
2130 let config = ServerConfig::from_env();
2131
2132 assert_eq!(
2133 config.redis_reader_pool_max_size, 750,
2134 "Should include reader pool max size in config"
2135 );
2136
2137 env::remove_var("REDIS_URL");
2138 env::remove_var("API_KEY");
2139 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2140 }
2141
2142 #[test]
2143 #[serial]
2144 fn test_config_uses_default_when_not_set() {
2145 env::set_var("REDIS_URL", "redis://primary:6379");
2146 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D");
2147 env::remove_var("REDIS_READER_POOL_MAX_SIZE");
2148
2149 let config = ServerConfig::from_env();
2150
2151 assert_eq!(
2152 config.redis_reader_pool_max_size, 1000,
2153 "Should use default 1000 when not set"
2154 );
2155
2156 env::remove_var("REDIS_URL");
2157 env::remove_var("API_KEY");
2158 }
2159 }
2160
2161 mod get_redis_connection_max_age_ms_tests {
2162 use super::*;
2163 use serial_test::serial;
2164
2165 #[test]
2166 #[serial]
2167 fn test_returns_default_when_env_not_set() {
2168 env::remove_var("REDIS_CONNECTION_MAX_AGE_MS");
2169 let result = ServerConfig::get_redis_connection_max_age_ms();
2170 assert_eq!(result, 60000, "Should return default value of 60000");
2171 }
2172
2173 #[test]
2174 #[serial]
2175 fn test_returns_env_value_when_set() {
2176 env::set_var("REDIS_CONNECTION_MAX_AGE_MS", "120000");
2177 let result = ServerConfig::get_redis_connection_max_age_ms();
2178 assert_eq!(result, 120000, "Should return env var value");
2179 env::remove_var("REDIS_CONNECTION_MAX_AGE_MS");
2180 }
2181
2182 #[test]
2183 #[serial]
2184 fn test_returns_default_when_env_invalid() {
2185 env::set_var("REDIS_CONNECTION_MAX_AGE_MS", "not_a_number");
2186 let result = ServerConfig::get_redis_connection_max_age_ms();
2187 assert_eq!(result, 60000, "Should return default value when invalid");
2188 env::remove_var("REDIS_CONNECTION_MAX_AGE_MS");
2189 }
2190
2191 #[test]
2192 #[serial]
2193 fn test_zero_disables_recycling() {
2194 env::set_var("REDIS_CONNECTION_MAX_AGE_MS", "0");
2195 let result = ServerConfig::get_redis_connection_max_age_ms();
2196 assert_eq!(result, 0, "Should accept zero to disable recycling");
2197 env::remove_var("REDIS_CONNECTION_MAX_AGE_MS");
2198 }
2199 }
2200
2201 mod get_sqs_wait_time_tests {
2202 use super::*;
2203 use serial_test::serial;
2204
2205 #[test]
2206 #[serial]
2207 fn test_returns_default_when_env_not_set() {
2208 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2209 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2210 assert_eq!(result, 5, "Should return default when env var is not set");
2211 }
2212
2213 #[test]
2214 #[serial]
2215 fn test_returns_parsed_value() {
2216 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "10");
2217 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2218 assert_eq!(result, 10, "Should return parsed value");
2219 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2220 }
2221
2222 #[test]
2223 #[serial]
2224 fn test_returns_default_when_invalid() {
2225 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "not_a_number");
2226 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2227 assert_eq!(result, 5, "Should return default for non-numeric input");
2228 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2229 }
2230
2231 #[test]
2232 #[serial]
2233 fn test_clamps_to_sqs_maximum_of_20() {
2234 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "30");
2235 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2236 assert_eq!(result, 20, "Should clamp to SQS maximum of 20 seconds");
2237 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2238 }
2239
2240 #[test]
2241 #[serial]
2242 fn test_allows_zero() {
2243 env::set_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS", "0");
2244 let result = ServerConfig::get_sqs_wait_time("TEST_QUEUE", 5);
2245 assert_eq!(result, 0, "Should allow zero (short polling)");
2246 env::remove_var("SQS_TEST_QUEUE_WAIT_TIME_SECONDS");
2247 }
2248 }
2249
2250 mod get_sqs_poller_count_tests {
2251 use super::*;
2252 use serial_test::serial;
2253
2254 #[test]
2255 #[serial]
2256 fn test_returns_default_when_env_not_set() {
2257 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2258 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2259 assert_eq!(result, 2, "Should return default when env var is not set");
2260 }
2261
2262 #[test]
2263 #[serial]
2264 fn test_returns_parsed_value() {
2265 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "4");
2266 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2267 assert_eq!(result, 4, "Should return parsed value");
2268 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2269 }
2270
2271 #[test]
2272 #[serial]
2273 fn test_returns_default_when_invalid() {
2274 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "not_a_number");
2275 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2276 assert_eq!(result, 2, "Should return default for non-numeric input");
2277 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2278 }
2279
2280 #[test]
2281 #[serial]
2282 fn test_clamps_zero_to_minimum_of_1() {
2283 env::set_var("SQS_TEST_QUEUE_POLLER_COUNT", "0");
2284 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 2);
2285 assert_eq!(result, 1, "Should clamp zero to minimum of 1");
2286 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2287 }
2288
2289 #[test]
2290 #[serial]
2291 fn test_default_also_clamped_to_minimum_of_1() {
2292 env::remove_var("SQS_TEST_QUEUE_POLLER_COUNT");
2293 let result = ServerConfig::get_sqs_poller_count("TEST_QUEUE", 0);
2294 assert_eq!(
2295 result, 1,
2296 "Default of 0 should also be clamped to minimum of 1"
2297 );
2298 }
2299 }
2300}