nonce_auth/nonce/
config.rs

1use std::time::Duration;
2
3/// Comprehensive configuration for nonce authentication system.
4///
5/// This struct provides a centralized way to configure all aspects of the
6/// nonce authentication system, including database settings, performance
7/// tuning, and security parameters.
8///
9/// # Environment Variables
10///
11/// All configuration options can be set via environment variables:
12///
13/// ## Database Configuration
14/// - `NONCE_AUTH_DB_PATH`: Database file path (default: "nonce_auth.db")
15/// - `NONCE_AUTH_CACHE_SIZE`: SQLite cache size in KB (default: 2048)
16/// - `NONCE_AUTH_WAL_MODE`: Enable WAL mode (default: true)
17/// - `NONCE_AUTH_SYNC_MODE`: Synchronous mode NORMAL/FULL/OFF (default: NORMAL)
18/// - `NONCE_AUTH_TEMP_STORE`: Temp storage MEMORY/FILE (default: MEMORY)
19///
20/// ## Performance Configuration
21/// - `NONCE_AUTH_CLEANUP_BATCH_SIZE`: Cleanup batch size (default: 1000)
22/// - `NONCE_AUTH_CLEANUP_THRESHOLD`: Auto-optimize threshold (default: 100)
23///
24/// ## Security Configuration
25/// - `NONCE_AUTH_DEFAULT_TTL`: Default TTL in seconds (default: 300)
26/// - `NONCE_AUTH_DEFAULT_TIME_WINDOW`: Time window in seconds (default: 60)
27///
28/// # Example
29///
30/// ```rust
31/// use nonce_auth::nonce::NonceConfig;
32/// use std::time::Duration;
33///
34/// // Use default configuration
35/// let config = NonceConfig::default();
36///
37/// // Create custom configuration
38/// let config = NonceConfig {
39///     db_path: "custom_nonce.db".to_string(),
40///     cache_size_kb: 4096, // 4MB cache
41///     default_ttl: Duration::from_secs(600), // 10 minutes
42///     time_window: Duration::from_secs(120), // 2 minutes
43///     ..Default::default()
44/// };
45///
46/// // Use config directly with Database::new(config)
47/// ```
48#[derive(Debug, Clone)]
49pub struct NonceConfig {
50    // Database configuration
51    /// SQLite database file path
52    pub db_path: String,
53    /// SQLite cache size in KB
54    pub cache_size_kb: i32,
55    /// Enable WAL (Write-Ahead Logging) mode
56    pub wal_mode: bool,
57    /// Synchronous mode: OFF, NORMAL, FULL
58    pub sync_mode: String,
59    /// Temporary storage: MEMORY, FILE
60    pub temp_store: String,
61
62    // Performance configuration
63    /// Batch size for cleanup operations
64    pub cleanup_batch_size: usize,
65    /// Threshold for triggering database optimization
66    pub cleanup_optimize_threshold: usize,
67
68    // Security configuration
69    /// Default time-to-live for nonce records
70    pub default_ttl: Duration,
71    /// Time window for timestamp validation
72    pub time_window: Duration,
73}
74
75impl Default for NonceConfig {
76    fn default() -> Self {
77        Self {
78            // Database defaults
79            db_path: std::env::var("NONCE_AUTH_DB_PATH")
80                .unwrap_or_else(|_| "nonce_auth.db".to_string()),
81            cache_size_kb: std::env::var("NONCE_AUTH_CACHE_SIZE")
82                .ok()
83                .and_then(|s| s.parse().ok())
84                .unwrap_or(2048),
85            wal_mode: std::env::var("NONCE_AUTH_WAL_MODE")
86                .map(|s| s.to_lowercase() != "false")
87                .unwrap_or(true),
88            sync_mode: std::env::var("NONCE_AUTH_SYNC_MODE")
89                .unwrap_or_else(|_| "NORMAL".to_string()),
90            temp_store: std::env::var("NONCE_AUTH_TEMP_STORE")
91                .unwrap_or_else(|_| "MEMORY".to_string()),
92
93            // Performance defaults
94            cleanup_batch_size: std::env::var("NONCE_AUTH_CLEANUP_BATCH_SIZE")
95                .ok()
96                .and_then(|s| s.parse().ok())
97                .unwrap_or(1000),
98            cleanup_optimize_threshold: std::env::var("NONCE_AUTH_CLEANUP_THRESHOLD")
99                .ok()
100                .and_then(|s| s.parse().ok())
101                .unwrap_or(100),
102
103            // Security defaults
104            default_ttl: Duration::from_secs(
105                std::env::var("NONCE_AUTH_DEFAULT_TTL")
106                    .ok()
107                    .and_then(|s| s.parse().ok())
108                    .unwrap_or(300),
109            ),
110            time_window: Duration::from_secs(
111                std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
112                    .ok()
113                    .and_then(|s| s.parse().ok())
114                    .unwrap_or(60),
115            ),
116        }
117    }
118}
119
120impl NonceConfig {
121    /// Creates a new configuration from environment variables.
122    ///
123    /// This method first determines the preset configuration based on the
124    /// `NONCE_AUTH_PRESET` environment variable, then applies individual
125    /// environment variable overrides.
126    ///
127    /// # Environment Variables
128    ///
129    /// - `NONCE_AUTH_PRESET`: Preset configuration (`production`, `development`, `high_performance`)
130    /// - Individual configuration variables override preset values
131    ///
132    /// # Returns
133    ///
134    /// A `NonceConfig` instance with preset and environment variable values.
135    ///
136    /// # Example
137    ///
138    /// ```bash
139    /// # Use production preset with custom cache size
140    /// export NONCE_AUTH_PRESET=production
141    /// export NONCE_AUTH_CACHE_SIZE=16384
142    /// ```
143    pub fn from_env() -> Self {
144        // Start with preset configuration based on NONCE_AUTH_PRESET
145        let preset =
146            std::env::var("NONCE_AUTH_PRESET").unwrap_or_else(|_| "production".to_string());
147
148        let mut config = match preset.to_lowercase().as_str() {
149            "development" => Self::development(),
150            "high_performance" => Self::high_performance(),
151            _ => Self::production(), // Default to production
152        };
153
154        // Apply individual environment variable overrides
155        if let Ok(db_path) = std::env::var("NONCE_AUTH_DB_PATH") {
156            config.db_path = db_path;
157        }
158
159        if let Some(size) = std::env::var("NONCE_AUTH_CACHE_SIZE")
160            .ok()
161            .and_then(|s| s.parse().ok())
162        {
163            config.cache_size_kb = size;
164        }
165
166        if let Ok(wal_mode) = std::env::var("NONCE_AUTH_WAL_MODE") {
167            config.wal_mode = wal_mode.to_lowercase() != "false";
168        }
169
170        if let Ok(sync_mode) = std::env::var("NONCE_AUTH_SYNC_MODE") {
171            config.sync_mode = sync_mode;
172        }
173
174        if let Ok(temp_store) = std::env::var("NONCE_AUTH_TEMP_STORE") {
175            config.temp_store = temp_store;
176        }
177
178        if let Some(size) = std::env::var("NONCE_AUTH_CLEANUP_BATCH_SIZE")
179            .ok()
180            .and_then(|s| s.parse().ok())
181        {
182            config.cleanup_batch_size = size;
183        }
184
185        if let Some(thresh) = std::env::var("NONCE_AUTH_CLEANUP_THRESHOLD")
186            .ok()
187            .and_then(|s| s.parse().ok())
188        {
189            config.cleanup_optimize_threshold = thresh;
190        }
191
192        if let Some(secs) = std::env::var("NONCE_AUTH_DEFAULT_TTL")
193            .ok()
194            .and_then(|s| s.parse().ok())
195        {
196            config.default_ttl = Duration::from_secs(secs);
197        }
198
199        if let Some(secs) = std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
200            .ok()
201            .and_then(|s| s.parse().ok())
202        {
203            config.time_window = Duration::from_secs(secs);
204        }
205
206        config
207    }
208
209    /// Updates this configuration with values from environment variables.
210    ///
211    /// Only updates fields where environment variables are set.
212    /// This allows combining preset configurations with environment overrides.
213    ///
214    /// # Returns
215    ///
216    /// A new `NonceConfig` instance with environment variable overrides applied.
217    ///
218    /// # Example
219    ///
220    /// ```rust
221    /// use nonce_auth::nonce::NonceConfig;
222    ///
223    /// // Start with production preset, then apply environment overrides
224    /// let config = NonceConfig::production().update_from_env();
225    /// ```
226    pub fn update_from_env(mut self) -> Self {
227        // Only update if environment variable is set
228        if let Ok(db_path) = std::env::var("NONCE_AUTH_DB_PATH") {
229            self.db_path = db_path;
230        }
231
232        if let Some(size) = std::env::var("NONCE_AUTH_CACHE_SIZE")
233            .ok()
234            .and_then(|s| s.parse().ok())
235        {
236            self.cache_size_kb = size;
237        }
238
239        if let Ok(wal_mode) = std::env::var("NONCE_AUTH_WAL_MODE") {
240            self.wal_mode = wal_mode.to_lowercase() != "false";
241        }
242
243        if let Ok(sync_mode) = std::env::var("NONCE_AUTH_SYNC_MODE") {
244            self.sync_mode = sync_mode;
245        }
246
247        if let Ok(temp_store) = std::env::var("NONCE_AUTH_TEMP_STORE") {
248            self.temp_store = temp_store;
249        }
250
251        if let Some(size) = std::env::var("NONCE_AUTH_CLEANUP_BATCH_SIZE")
252            .ok()
253            .and_then(|s| s.parse().ok())
254        {
255            self.cleanup_batch_size = size;
256        }
257
258        if let Some(thresh) = std::env::var("NONCE_AUTH_CLEANUP_THRESHOLD")
259            .ok()
260            .and_then(|s| s.parse().ok())
261        {
262            self.cleanup_optimize_threshold = thresh;
263        }
264
265        if let Some(secs) = std::env::var("NONCE_AUTH_DEFAULT_TTL")
266            .ok()
267            .and_then(|s| s.parse().ok())
268        {
269            self.default_ttl = Duration::from_secs(secs);
270        }
271
272        if let Some(secs) = std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
273            .ok()
274            .and_then(|s| s.parse().ok())
275        {
276            self.time_window = Duration::from_secs(secs);
277        }
278
279        self
280    }
281
282    /// Creates a new configuration with recommended production settings.
283    ///
284    /// This preset is optimized for production environments with:
285    /// - Larger cache size for better performance
286    /// - WAL mode enabled for concurrency
287    /// - Balanced security settings
288    /// - Optimized cleanup parameters
289    ///
290    /// # Returns
291    ///
292    /// A `NonceConfig` instance with production-optimized settings.
293    ///
294    /// # Example
295    ///
296    /// ```rust
297    /// use nonce_auth::nonce::NonceConfig;
298    ///
299    /// let config = NonceConfig::production();
300    /// // Use config directly with Database::new(config)
301    /// ```
302    pub fn production() -> Self {
303        Self {
304            db_path: "nonce_auth.db".to_string(),
305            cache_size_kb: 8192, // 8MB cache
306            wal_mode: true,
307            sync_mode: "NORMAL".to_string(),
308            temp_store: "MEMORY".to_string(),
309            cleanup_batch_size: 2000,
310            cleanup_optimize_threshold: 500,
311            default_ttl: Duration::from_secs(300), // 5 minutes
312            time_window: Duration::from_secs(60),  // 1 minute
313        }
314    }
315
316    /// Creates a new configuration optimized for development and testing.
317    ///
318    /// This preset uses:
319    /// - In-memory database for faster tests
320    /// - Smaller cache size to save memory
321    /// - Shorter TTL for faster test cycles
322    /// - Relaxed time window for development
323    ///
324    /// # Returns
325    ///
326    /// A `NonceConfig` instance with development-optimized settings.
327    ///
328    /// # Example
329    ///
330    /// ```rust
331    /// use nonce_auth::nonce::NonceConfig;
332    ///
333    /// let config = NonceConfig::development();
334    /// // Use config directly with Database::new(config)
335    /// ```
336    pub fn development() -> Self {
337        Self {
338            db_path: ":memory:".to_string(),
339            cache_size_kb: 512,           // 512KB cache
340            wal_mode: false,              // Not needed for in-memory
341            sync_mode: "OFF".to_string(), // Faster for development
342            temp_store: "MEMORY".to_string(),
343            cleanup_batch_size: 100,
344            cleanup_optimize_threshold: 50,
345            default_ttl: Duration::from_secs(60),  // 1 minute
346            time_window: Duration::from_secs(300), // 5 minutes (relaxed)
347        }
348    }
349
350    /// Creates a new configuration optimized for high-performance scenarios.
351    ///
352    /// This preset maximizes performance with:
353    /// - Large cache size
354    /// - Aggressive optimization settings
355    /// - Larger batch sizes
356    /// - Minimal synchronization overhead
357    ///
358    /// # Returns
359    ///
360    /// A `NonceConfig` instance with high-performance settings.
361    ///
362    /// # Example
363    ///
364    /// ```rust
365    /// use nonce_auth::nonce::NonceConfig;
366    ///
367    /// let config = NonceConfig::high_performance();
368    /// // Use config directly with Database::new(config)
369    /// ```
370    pub fn high_performance() -> Self {
371        Self {
372            db_path: "nonce_auth.db".to_string(),
373            cache_size_kb: 16384, // 16MB cache
374            wal_mode: true,
375            sync_mode: "NORMAL".to_string(),
376            temp_store: "MEMORY".to_string(),
377            cleanup_batch_size: 5000,
378            cleanup_optimize_threshold: 1000,
379            default_ttl: Duration::from_secs(300),
380            time_window: Duration::from_secs(60),
381        }
382    }
383
384    /// Validates the configuration and returns any issues found.
385    ///
386    /// This method checks for common configuration problems and
387    /// returns a list of validation errors or warnings.
388    ///
389    /// # Returns
390    ///
391    /// A vector of validation messages. Empty if configuration is valid.
392    ///
393    /// # Example
394    ///
395    /// ```rust
396    /// use nonce_auth::nonce::NonceConfig;
397    ///
398    /// let config = NonceConfig::default();
399    /// let issues = config.validate();
400    /// if !issues.is_empty() {
401    ///     for issue in issues {
402    ///         println!("Config issue: {}", issue);
403    ///     }
404    /// }
405    /// ```
406    pub fn validate(&self) -> Vec<String> {
407        let mut issues = Vec::new();
408
409        // Validate cache size
410        if self.cache_size_kb < 64 {
411            issues.push(
412                "Cache size is very small, consider increasing for better performance".to_string(),
413            );
414        }
415        if self.cache_size_kb > 32768 {
416            issues.push("Cache size is very large, may consume excessive memory".to_string());
417        }
418
419        // Validate sync mode
420        if !["OFF", "NORMAL", "FULL"].contains(&self.sync_mode.as_str()) {
421            issues.push(format!(
422                "Invalid sync_mode '{}', must be OFF, NORMAL, or FULL",
423                self.sync_mode
424            ));
425        }
426
427        // Validate temp store
428        if !["MEMORY", "FILE"].contains(&self.temp_store.as_str()) {
429            issues.push(format!(
430                "Invalid temp_store '{}', must be MEMORY or FILE",
431                self.temp_store
432            ));
433        }
434
435        // Validate TTL
436        if self.default_ttl.as_secs() < 30 {
437            issues
438                .push("Default TTL is very short, may cause frequent cleanup overhead".to_string());
439        }
440        if self.default_ttl.as_secs() > 86400 {
441            issues.push("Default TTL is very long, may cause database bloat".to_string());
442        }
443
444        // Validate time window
445        if self.time_window.as_secs() < 10 {
446            issues.push(
447                "Time window is very short, may cause legitimate requests to be rejected"
448                    .to_string(),
449            );
450        }
451        if self.time_window.as_secs() > 3600 {
452            issues.push(
453                "Time window is very long, may reduce security against replay attacks".to_string(),
454            );
455        }
456
457        // Validate batch sizes
458        if self.cleanup_batch_size < 10 {
459            issues
460                .push("Cleanup batch size is very small, may cause performance issues".to_string());
461        }
462        if self.cleanup_batch_size > 10000 {
463            issues.push(
464                "Cleanup batch size is very large, may cause long-running transactions".to_string(),
465            );
466        }
467
468        issues
469    }
470
471    /// Returns a summary of the current configuration.
472    ///
473    /// This method provides a human-readable summary of all configuration
474    /// settings, useful for logging and debugging.
475    ///
476    /// # Returns
477    ///
478    /// A formatted string describing the configuration.
479    ///
480    /// # Example
481    ///
482    /// ```rust
483    /// use nonce_auth::nonce::NonceConfig;
484    ///
485    /// let config = NonceConfig::default();
486    /// println!("Configuration:\n{}", config.summary());
487    /// ```
488    pub fn summary(&self) -> String {
489        format!(
490            "Nonce Authentication Configuration:
491Database:
492  Path: {}
493  Cache Size: {} KB
494  WAL Mode: {}
495  Sync Mode: {}
496  Temp Store: {}
497
498Performance:
499  Cleanup Batch Size: {}
500  Optimize Threshold: {}
501
502Security:
503  Default TTL: {} seconds
504  Time Window: {} seconds",
505            self.db_path,
506            self.cache_size_kb,
507            self.wal_mode,
508            self.sync_mode,
509            self.temp_store,
510            self.cleanup_batch_size,
511            self.cleanup_optimize_threshold,
512            self.default_ttl.as_secs(),
513            self.time_window.as_secs()
514        )
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521    use serial_test::serial;
522    use std::env;
523
524    /// Helper function to clear all nonce auth environment variables
525    fn clear_env_vars() {
526        let vars = [
527            "NONCE_AUTH_DB_PATH",
528            "NONCE_AUTH_CACHE_SIZE",
529            "NONCE_AUTH_WAL_MODE",
530            "NONCE_AUTH_SYNC_MODE",
531            "NONCE_AUTH_TEMP_STORE",
532            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
533            "NONCE_AUTH_CLEANUP_THRESHOLD",
534            "NONCE_AUTH_DEFAULT_TTL",
535            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
536        ];
537
538        for var in &vars {
539            unsafe {
540                env::remove_var(var);
541            }
542        }
543    }
544
545    #[test]
546    #[serial]
547    fn test_default_configuration() {
548        // Save current environment
549        let saved_vars: Vec<_> = [
550            "NONCE_AUTH_DB_PATH",
551            "NONCE_AUTH_CACHE_SIZE",
552            "NONCE_AUTH_WAL_MODE",
553            "NONCE_AUTH_SYNC_MODE",
554            "NONCE_AUTH_TEMP_STORE",
555            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
556            "NONCE_AUTH_CLEANUP_THRESHOLD",
557            "NONCE_AUTH_DEFAULT_TTL",
558            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
559        ]
560        .iter()
561        .map(|var| (*var, env::var(var).ok()))
562        .collect();
563
564        clear_env_vars();
565
566        let config = NonceConfig::default();
567
568        // Test default values
569        assert_eq!(config.db_path, "nonce_auth.db");
570        assert_eq!(config.cache_size_kb, 2048);
571        assert!(config.wal_mode);
572        assert_eq!(config.sync_mode, "NORMAL");
573        assert_eq!(config.temp_store, "MEMORY");
574        assert_eq!(config.cleanup_batch_size, 1000);
575        assert_eq!(config.cleanup_optimize_threshold, 100);
576        assert_eq!(config.default_ttl, Duration::from_secs(300));
577        assert_eq!(config.time_window, Duration::from_secs(60));
578
579        // Restore environment
580        for (var, value) in saved_vars {
581            match value {
582                Some(val) => unsafe {
583                    env::set_var(var, val);
584                },
585                None => unsafe {
586                    env::remove_var(var);
587                },
588            }
589        }
590    }
591
592    #[test]
593    #[serial]
594    fn test_environment_variable_override() {
595        // Save current environment
596        let saved_vars: Vec<_> = [
597            "NONCE_AUTH_DB_PATH",
598            "NONCE_AUTH_CACHE_SIZE",
599            "NONCE_AUTH_WAL_MODE",
600            "NONCE_AUTH_SYNC_MODE",
601            "NONCE_AUTH_TEMP_STORE",
602            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
603            "NONCE_AUTH_CLEANUP_THRESHOLD",
604            "NONCE_AUTH_DEFAULT_TTL",
605            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
606        ]
607        .iter()
608        .map(|var| (*var, env::var(var).ok()))
609        .collect();
610
611        clear_env_vars();
612
613        // Set environment variables
614        unsafe {
615            env::set_var("NONCE_AUTH_DB_PATH", "test.db");
616            env::set_var("NONCE_AUTH_CACHE_SIZE", "4096");
617            env::set_var("NONCE_AUTH_WAL_MODE", "false");
618            env::set_var("NONCE_AUTH_SYNC_MODE", "FULL");
619            env::set_var("NONCE_AUTH_TEMP_STORE", "FILE");
620            env::set_var("NONCE_AUTH_CLEANUP_BATCH_SIZE", "2000");
621            env::set_var("NONCE_AUTH_CLEANUP_THRESHOLD", "200");
622            env::set_var("NONCE_AUTH_DEFAULT_TTL", "600");
623            env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "120");
624        }
625
626        let config = NonceConfig::default();
627
628        // Test environment variable overrides
629        assert_eq!(config.db_path, "test.db");
630        assert_eq!(config.cache_size_kb, 4096);
631        assert!(!config.wal_mode);
632        assert_eq!(config.sync_mode, "FULL");
633        assert_eq!(config.temp_store, "FILE");
634        assert_eq!(config.cleanup_batch_size, 2000);
635        assert_eq!(config.cleanup_optimize_threshold, 200);
636        assert_eq!(config.default_ttl, Duration::from_secs(600));
637        assert_eq!(config.time_window, Duration::from_secs(120));
638
639        // Restore environment
640        for (var, value) in saved_vars {
641            match value {
642                Some(val) => unsafe {
643                    env::set_var(var, val);
644                },
645                None => unsafe {
646                    env::remove_var(var);
647                },
648            }
649        }
650    }
651
652    #[test]
653    #[serial]
654    fn test_wal_mode_parsing() {
655        // Save current environment
656        let saved_vars: Vec<_> = [
657            "NONCE_AUTH_DB_PATH",
658            "NONCE_AUTH_CACHE_SIZE",
659            "NONCE_AUTH_WAL_MODE",
660            "NONCE_AUTH_SYNC_MODE",
661            "NONCE_AUTH_TEMP_STORE",
662            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
663            "NONCE_AUTH_CLEANUP_THRESHOLD",
664            "NONCE_AUTH_DEFAULT_TTL",
665            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
666        ]
667        .iter()
668        .map(|var| (*var, env::var(var).ok()))
669        .collect();
670
671        clear_env_vars();
672
673        // Test various WAL mode values
674        let test_cases = [
675            ("true", true),
676            ("TRUE", true),
677            ("True", true),
678            ("false", false),
679            ("FALSE", false),
680            ("False", false),
681            ("0", true), // Non-"false" values should be true
682            ("1", true),
683            ("", true),
684        ];
685
686        for (env_value, expected) in &test_cases {
687            unsafe {
688                env::set_var("NONCE_AUTH_WAL_MODE", env_value);
689            }
690            let config = NonceConfig::default();
691            assert_eq!(
692                config.wal_mode, *expected,
693                "Failed for WAL_MODE='{env_value}'"
694            );
695        }
696
697        // Restore environment
698        for (var, value) in saved_vars {
699            match value {
700                Some(val) => unsafe {
701                    env::set_var(var, val);
702                },
703                None => unsafe {
704                    env::remove_var(var);
705                },
706            }
707        }
708    }
709
710    #[test]
711    #[serial]
712    fn test_invalid_numeric_env_vars() {
713        // Save current environment
714        let saved_vars: Vec<_> = [
715            "NONCE_AUTH_DB_PATH",
716            "NONCE_AUTH_CACHE_SIZE",
717            "NONCE_AUTH_WAL_MODE",
718            "NONCE_AUTH_SYNC_MODE",
719            "NONCE_AUTH_TEMP_STORE",
720            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
721            "NONCE_AUTH_CLEANUP_THRESHOLD",
722            "NONCE_AUTH_DEFAULT_TTL",
723            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
724        ]
725        .iter()
726        .map(|var| (*var, env::var(var).ok()))
727        .collect();
728
729        clear_env_vars();
730
731        // Test invalid numeric values fall back to defaults
732        unsafe {
733            env::set_var("NONCE_AUTH_CACHE_SIZE", "invalid");
734            env::set_var("NONCE_AUTH_CLEANUP_BATCH_SIZE", "not_a_number");
735            env::set_var("NONCE_AUTH_CLEANUP_THRESHOLD", "");
736            env::set_var("NONCE_AUTH_DEFAULT_TTL", "abc");
737            env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "-1");
738        }
739
740        let config = NonceConfig::default();
741
742        // Should fall back to defaults
743        assert_eq!(config.cache_size_kb, 2048);
744        assert_eq!(config.cleanup_batch_size, 1000);
745        assert_eq!(config.cleanup_optimize_threshold, 100);
746        assert_eq!(config.default_ttl, Duration::from_secs(300));
747        assert_eq!(config.time_window, Duration::from_secs(60));
748
749        // Restore environment
750        for (var, value) in saved_vars {
751            match value {
752                Some(val) => unsafe {
753                    env::set_var(var, val);
754                },
755                None => unsafe {
756                    env::remove_var(var);
757                },
758            }
759        }
760    }
761
762    #[test]
763    fn test_production_preset() {
764        let config = NonceConfig::production();
765
766        assert_eq!(config.cache_size_kb, 8192);
767        assert!(config.wal_mode);
768        assert_eq!(config.sync_mode, "NORMAL");
769        assert_eq!(config.temp_store, "MEMORY");
770        assert_eq!(config.cleanup_batch_size, 2000);
771        assert_eq!(config.cleanup_optimize_threshold, 500);
772        assert_eq!(config.default_ttl, Duration::from_secs(300));
773        assert_eq!(config.time_window, Duration::from_secs(60));
774    }
775
776    #[test]
777    fn test_development_preset() {
778        let config = NonceConfig::development();
779
780        assert_eq!(config.db_path, ":memory:");
781        assert_eq!(config.cache_size_kb, 512);
782        assert!(!config.wal_mode);
783        assert_eq!(config.sync_mode, "OFF");
784        assert_eq!(config.temp_store, "MEMORY");
785        assert_eq!(config.cleanup_batch_size, 100);
786        assert_eq!(config.cleanup_optimize_threshold, 50);
787        assert_eq!(config.default_ttl, Duration::from_secs(60));
788        assert_eq!(config.time_window, Duration::from_secs(300));
789    }
790
791    #[test]
792    fn test_high_performance_preset() {
793        let config = NonceConfig::high_performance();
794
795        assert_eq!(config.cache_size_kb, 16384);
796        assert!(config.wal_mode);
797        assert_eq!(config.sync_mode, "NORMAL");
798        assert_eq!(config.temp_store, "MEMORY");
799        assert_eq!(config.cleanup_batch_size, 5000);
800        assert_eq!(config.cleanup_optimize_threshold, 1000);
801        assert_eq!(config.default_ttl, Duration::from_secs(300));
802        assert_eq!(config.time_window, Duration::from_secs(60));
803    }
804
805    #[test]
806    #[serial]
807    fn test_from_env() {
808        // Save current environment
809        let saved_vars: Vec<_> = [
810            "NONCE_AUTH_DB_PATH",
811            "NONCE_AUTH_CACHE_SIZE",
812            "NONCE_AUTH_WAL_MODE",
813            "NONCE_AUTH_SYNC_MODE",
814            "NONCE_AUTH_TEMP_STORE",
815            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
816            "NONCE_AUTH_CLEANUP_THRESHOLD",
817            "NONCE_AUTH_DEFAULT_TTL",
818            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
819        ]
820        .iter()
821        .map(|var| (*var, env::var(var).ok()))
822        .collect();
823
824        clear_env_vars();
825
826        // Set test environment variables
827        unsafe {
828            env::set_var("NONCE_AUTH_DB_PATH", "custom.db");
829            env::set_var("NONCE_AUTH_CACHE_SIZE", "1024");
830            env::set_var("NONCE_AUTH_WAL_MODE", "false");
831            env::set_var("NONCE_AUTH_SYNC_MODE", "OFF");
832            env::set_var("NONCE_AUTH_TEMP_STORE", "FILE");
833            env::set_var("NONCE_AUTH_CLEANUP_BATCH_SIZE", "500");
834            env::set_var("NONCE_AUTH_CLEANUP_THRESHOLD", "250");
835            env::set_var("NONCE_AUTH_DEFAULT_TTL", "120");
836            env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "30");
837        }
838
839        let config = NonceConfig::from_env();
840
841        // Verify configuration was read from environment
842        assert_eq!(config.db_path, "custom.db");
843        assert_eq!(config.cache_size_kb, 1024);
844        assert!(!config.wal_mode);
845        assert_eq!(config.sync_mode, "OFF");
846        assert_eq!(config.temp_store, "FILE");
847        assert_eq!(config.cleanup_batch_size, 500);
848        assert_eq!(config.cleanup_optimize_threshold, 250);
849        assert_eq!(config.default_ttl, Duration::from_secs(120));
850        assert_eq!(config.time_window, Duration::from_secs(30));
851
852        // Restore environment
853        for (var, value) in saved_vars {
854            match value {
855                Some(val) => unsafe {
856                    env::set_var(var, val);
857                },
858                None => unsafe {
859                    env::remove_var(var);
860                },
861            }
862        }
863    }
864
865    #[test]
866    fn test_validation_valid_config() {
867        let config = NonceConfig::production();
868        let issues = config.validate();
869        assert!(issues.is_empty(), "Production config should be valid");
870    }
871
872    #[test]
873    fn test_validation_cache_size_warnings() {
874        // Test very small cache size
875        let config = NonceConfig {
876            cache_size_kb: 32,
877            ..NonceConfig::default()
878        };
879        let issues = config.validate();
880        assert!(
881            issues
882                .iter()
883                .any(|issue| issue.contains("Cache size is very small"))
884        );
885
886        // Test very large cache size
887        let config = NonceConfig {
888            cache_size_kb: 50000,
889            ..NonceConfig::default()
890        };
891        let issues = config.validate();
892        assert!(
893            issues
894                .iter()
895                .any(|issue| issue.contains("Cache size is very large"))
896        );
897    }
898
899    #[test]
900    fn test_validation_invalid_sync_mode() {
901        let config = NonceConfig {
902            sync_mode: "INVALID".to_string(),
903            ..NonceConfig::default()
904        };
905        let issues = config.validate();
906        assert!(
907            issues
908                .iter()
909                .any(|issue| issue.contains("Invalid sync_mode"))
910        );
911    }
912
913    #[test]
914    fn test_validation_invalid_temp_store() {
915        let config = NonceConfig {
916            temp_store: "INVALID".to_string(),
917            ..NonceConfig::default()
918        };
919        let issues = config.validate();
920        assert!(
921            issues
922                .iter()
923                .any(|issue| issue.contains("Invalid temp_store"))
924        );
925    }
926
927    #[test]
928    fn test_validation_ttl_warnings() {
929        // Test very short TTL
930        let config = NonceConfig {
931            default_ttl: Duration::from_secs(10),
932            ..NonceConfig::default()
933        };
934        let issues = config.validate();
935        assert!(
936            issues
937                .iter()
938                .any(|issue| issue.contains("Default TTL is very short"))
939        );
940
941        // Test very long TTL
942        let config = NonceConfig {
943            default_ttl: Duration::from_secs(100000),
944            ..NonceConfig::default()
945        };
946        let issues = config.validate();
947        assert!(
948            issues
949                .iter()
950                .any(|issue| issue.contains("Default TTL is very long"))
951        );
952    }
953
954    #[test]
955    fn test_validation_time_window_warnings() {
956        // Test very short time window
957        let config = NonceConfig {
958            time_window: Duration::from_secs(5),
959            ..NonceConfig::default()
960        };
961        let issues = config.validate();
962        assert!(
963            issues
964                .iter()
965                .any(|issue| issue.contains("Time window is very short"))
966        );
967
968        // Test very long time window
969        let config = NonceConfig {
970            time_window: Duration::from_secs(5000),
971            ..NonceConfig::default()
972        };
973        let issues = config.validate();
974        assert!(
975            issues
976                .iter()
977                .any(|issue| issue.contains("Time window is very long"))
978        );
979    }
980
981    #[test]
982    fn test_validation_batch_size_warnings() {
983        // Test very small batch size
984        let config = NonceConfig {
985            cleanup_batch_size: 5,
986            ..NonceConfig::default()
987        };
988        let issues = config.validate();
989        assert!(
990            issues
991                .iter()
992                .any(|issue| issue.contains("Cleanup batch size is very small"))
993        );
994
995        // Test very large batch size
996        let config = NonceConfig {
997            cleanup_batch_size: 20000,
998            ..NonceConfig::default()
999        };
1000        let issues = config.validate();
1001        assert!(
1002            issues
1003                .iter()
1004                .any(|issue| issue.contains("Cleanup batch size is very large"))
1005        );
1006    }
1007
1008    #[test]
1009    fn test_summary_format() {
1010        let config = NonceConfig::default();
1011        let summary = config.summary();
1012
1013        // Check that summary contains key information
1014        assert!(summary.contains("Nonce Authentication Configuration"));
1015        assert!(summary.contains("Database:"));
1016        assert!(summary.contains("Performance:"));
1017        assert!(summary.contains("Security:"));
1018        assert!(summary.contains(&config.db_path));
1019        assert!(summary.contains(&config.cache_size_kb.to_string()));
1020        assert!(summary.contains(&config.sync_mode));
1021        assert!(summary.contains(&config.cleanup_batch_size.to_string()));
1022        assert!(summary.contains(&config.default_ttl.as_secs().to_string()));
1023    }
1024
1025    #[test]
1026    fn test_config_clone_and_debug() {
1027        let config = NonceConfig::default();
1028
1029        // Test Clone trait
1030        let cloned_config = config.clone();
1031        assert_eq!(config.db_path, cloned_config.db_path);
1032        assert_eq!(config.cache_size_kb, cloned_config.cache_size_kb);
1033
1034        // Test Debug trait
1035        let debug_str = format!("{config:?}");
1036        assert!(debug_str.contains("NonceConfig"));
1037        assert!(debug_str.contains("db_path"));
1038    }
1039
1040    #[test]
1041    fn test_custom_configuration() {
1042        let config = NonceConfig {
1043            db_path: "custom_path.db".to_string(),
1044            cache_size_kb: 4096,
1045            wal_mode: false,
1046            sync_mode: "FULL".to_string(),
1047            temp_store: "FILE".to_string(),
1048            cleanup_batch_size: 1500,
1049            cleanup_optimize_threshold: 300,
1050            default_ttl: Duration::from_secs(600),
1051            time_window: Duration::from_secs(120),
1052        };
1053
1054        assert_eq!(config.db_path, "custom_path.db");
1055        assert_eq!(config.cache_size_kb, 4096);
1056        assert!(!config.wal_mode);
1057        assert_eq!(config.sync_mode, "FULL");
1058        assert_eq!(config.temp_store, "FILE");
1059        assert_eq!(config.cleanup_batch_size, 1500);
1060        assert_eq!(config.cleanup_optimize_threshold, 300);
1061        assert_eq!(config.default_ttl, Duration::from_secs(600));
1062        assert_eq!(config.time_window, Duration::from_secs(120));
1063    }
1064
1065    #[test]
1066    #[serial]
1067    fn test_preset_independence() {
1068        // Save current environment
1069        let saved_vars: Vec<_> = [
1070            "NONCE_AUTH_DB_PATH",
1071            "NONCE_AUTH_CACHE_SIZE",
1072            "NONCE_AUTH_WAL_MODE",
1073            "NONCE_AUTH_SYNC_MODE",
1074            "NONCE_AUTH_TEMP_STORE",
1075            "NONCE_AUTH_CLEANUP_BATCH_SIZE",
1076            "NONCE_AUTH_CLEANUP_THRESHOLD",
1077            "NONCE_AUTH_DEFAULT_TTL",
1078            "NONCE_AUTH_DEFAULT_TIME_WINDOW",
1079        ]
1080        .iter()
1081        .map(|var| (*var, env::var(var).ok()))
1082        .collect();
1083
1084        clear_env_vars();
1085
1086        // Set environment variables that would affect Default::default()
1087        unsafe {
1088            env::set_var("NONCE_AUTH_DB_PATH", "env_override.db");
1089            env::set_var("NONCE_AUTH_CACHE_SIZE", "1024");
1090        }
1091
1092        // Preset configurations should NOT be affected by environment variables
1093        let production_config = NonceConfig::production();
1094        assert_eq!(production_config.db_path, "nonce_auth.db"); // Not env_override.db
1095        assert_eq!(production_config.cache_size_kb, 8192); // Not 1024
1096        assert_eq!(production_config.cleanup_batch_size, 2000);
1097
1098        let dev_config = NonceConfig::development();
1099        assert_eq!(dev_config.db_path, ":memory:"); // Not env_override.db
1100        assert_eq!(dev_config.cache_size_kb, 512); // Not 1024
1101
1102        let perf_config = NonceConfig::high_performance();
1103        assert_eq!(perf_config.db_path, "nonce_auth.db"); // Not env_override.db
1104        assert_eq!(perf_config.cache_size_kb, 16384); // Not 1024
1105
1106        // But from_env() should use environment variables
1107        let env_config = NonceConfig::from_env();
1108        assert_eq!(env_config.db_path, "env_override.db");
1109        assert_eq!(env_config.cache_size_kb, 1024);
1110
1111        // Restore environment
1112        for (var, value) in saved_vars {
1113            match value {
1114                Some(val) => unsafe {
1115                    env::set_var(var, val);
1116                },
1117                None => unsafe {
1118                    env::remove_var(var);
1119                },
1120            }
1121        }
1122    }
1123
1124    #[test]
1125    #[serial]
1126    fn test_env_preset_selection() {
1127        // Save current environment
1128        let saved_env = env::var("NONCE_AUTH_PRESET").ok();
1129
1130        // Test production preset (default)
1131        unsafe {
1132            env::remove_var("NONCE_AUTH_PRESET");
1133        }
1134        let config = NonceConfig::from_env();
1135        let prod_config = NonceConfig::production();
1136        assert_eq!(config.cache_size_kb, prod_config.cache_size_kb);
1137        assert_eq!(config.cleanup_batch_size, prod_config.cleanup_batch_size);
1138
1139        // Test development preset
1140        unsafe {
1141            env::set_var("NONCE_AUTH_PRESET", "development");
1142        }
1143        let config = NonceConfig::from_env();
1144        let dev_config = NonceConfig::development();
1145        assert_eq!(config.cache_size_kb, dev_config.cache_size_kb);
1146        assert_eq!(config.db_path, dev_config.db_path);
1147
1148        // Test high_performance preset
1149        unsafe {
1150            env::set_var("NONCE_AUTH_PRESET", "high_performance");
1151        }
1152        let config = NonceConfig::from_env();
1153        let hp_config = NonceConfig::high_performance();
1154        assert_eq!(config.cache_size_kb, hp_config.cache_size_kb);
1155        assert_eq!(config.cleanup_batch_size, hp_config.cleanup_batch_size);
1156
1157        // Test invalid preset defaults to production
1158        unsafe {
1159            env::set_var("NONCE_AUTH_PRESET", "invalid");
1160        }
1161        let config = NonceConfig::from_env();
1162        let prod_config = NonceConfig::production();
1163        assert_eq!(config.cache_size_kb, prod_config.cache_size_kb);
1164
1165        // Restore environment
1166        match saved_env {
1167            Some(val) => unsafe {
1168                env::set_var("NONCE_AUTH_PRESET", val);
1169            },
1170            None => unsafe {
1171                env::remove_var("NONCE_AUTH_PRESET");
1172            },
1173        }
1174    }
1175
1176    #[test]
1177    #[serial]
1178    fn test_env_override_preset() {
1179        // Save current environment
1180        let saved_vars: Vec<_> = [
1181            "NONCE_AUTH_PRESET",
1182            "NONCE_AUTH_CACHE_SIZE",
1183            "NONCE_AUTH_DB_PATH",
1184        ]
1185        .iter()
1186        .map(|var| (*var, env::var(var).ok()))
1187        .collect();
1188
1189        // Test that individual environment variables override preset values
1190        unsafe {
1191            env::set_var("NONCE_AUTH_PRESET", "production");
1192            env::set_var("NONCE_AUTH_CACHE_SIZE", "12345");
1193            env::set_var("NONCE_AUTH_DB_PATH", "custom_test.db");
1194        }
1195
1196        let config = NonceConfig::from_env();
1197        let prod_config = NonceConfig::production();
1198
1199        // Overridden values should be different from preset
1200        assert_eq!(config.cache_size_kb, 12345);
1201        assert_eq!(config.db_path, "custom_test.db");
1202
1203        // Non-overridden values should match preset
1204        assert_eq!(config.cleanup_batch_size, prod_config.cleanup_batch_size);
1205        assert_eq!(config.wal_mode, prod_config.wal_mode);
1206
1207        // Restore environment
1208        for (var, value) in saved_vars {
1209            match value {
1210                Some(val) => unsafe {
1211                    env::set_var(var, val);
1212                },
1213                None => unsafe {
1214                    env::remove_var(var);
1215                },
1216            }
1217        }
1218    }
1219}