html2pdf_api/config.rs
1//! Configuration for browser pool behavior and limits.
2//!
3//! This module provides [`BrowserPoolConfig`] and [`BrowserPoolConfigBuilder`]
4//! for configuring pool size, browser lifecycle, and health monitoring parameters.
5//!
6//! # Example
7//!
8//! ```rust
9//! use std::time::Duration;
10//! use html2pdf_api::BrowserPoolConfigBuilder;
11//!
12//! let config = BrowserPoolConfigBuilder::new()
13//! .max_pool_size(10)
14//! .warmup_count(5)
15//! .browser_ttl(Duration::from_secs(7200))
16//! .build()
17//! .expect("Invalid configuration");
18//!
19//! assert_eq!(config.max_pool_size, 10);
20//! assert_eq!(config.warmup_count, 5);
21//! ```
22//!
23//! # Environment Configuration
24//!
25//! When the `env-config` feature is enabled, you can load configuration
26//! from environment variables and an optional `app.env` file:
27//!
28//! ```rust,ignore
29//! use html2pdf_api::config::env::from_env;
30//!
31//! let config = from_env()?;
32//! ```
33//!
34//! See [`mod@env`] module for available environment variables.
35
36use std::time::Duration;
37
38/// Configuration for browser pool behavior and limits.
39///
40/// Controls pool size, browser lifecycle, and health monitoring parameters.
41/// Use [`BrowserPoolConfigBuilder`] for validation and convenience.
42///
43/// # Fields Overview
44///
45/// | Field | Default | Description |
46/// |-------|---------|-------------|
47/// | `max_pool_size` | 5 | Maximum browsers in pool |
48/// | `warmup_count` | 3 | Browsers to pre-create |
49/// | `ping_interval` | 15s | Health check frequency |
50/// | `browser_ttl` | 1 hour | Browser lifetime |
51/// | `max_ping_failures` | 3 | Failures before removal |
52/// | `warmup_timeout` | 60s | Warmup time limit |
53///
54/// # Example
55///
56/// ```rust
57/// use html2pdf_api::BrowserPoolConfig;
58///
59/// // Use defaults
60/// let config = BrowserPoolConfig::default();
61/// assert_eq!(config.max_pool_size, 5);
62/// ```
63#[derive(Debug, Clone)]
64pub struct BrowserPoolConfig {
65 /// Maximum number of browsers to keep in the pool (idle + active).
66 ///
67 /// This is a soft limit - active browsers may temporarily exceed this during high load.
68 ///
69 /// # Default
70 ///
71 /// 5 browsers
72 ///
73 /// # Considerations
74 ///
75 /// - Higher values = more memory usage, better concurrency
76 /// - Lower values = less memory, potential queuing under load
77 pub max_pool_size: usize,
78
79 /// Number of browsers to pre-create during warmup phase.
80 ///
81 /// Must be d `max_pool_size`. Reduces first-request latency.
82 ///
83 /// # Default
84 ///
85 /// 3 browsers
86 ///
87 /// # Considerations
88 ///
89 /// - Set to `max_pool_size` for fastest first requests
90 /// - Set to 0 for lazy initialization (browsers created on demand)
91 pub warmup_count: usize,
92
93 /// Interval between health check pings for active browsers.
94 ///
95 /// Shorter intervals = faster failure detection, higher overhead.
96 ///
97 /// # Default
98 ///
99 /// 15 seconds
100 ///
101 /// # Considerations
102 ///
103 /// - Too short: Unnecessary CPU/memory overhead
104 /// - Too long: Slow detection of crashed browsers
105 pub ping_interval: Duration,
106
107 /// Time-to-live for each browser instance before forced retirement.
108 ///
109 /// Prevents memory leaks from long-running browser processes.
110 ///
111 /// # Default
112 ///
113 /// 1 hour (3600 seconds)
114 ///
115 /// # Considerations
116 ///
117 /// - Chrome can accumulate memory over time
118 /// - Shorter TTL = more browser restarts, fresher instances
119 /// - Longer TTL = fewer restarts, potential memory growth
120 pub browser_ttl: Duration,
121
122 /// Maximum consecutive ping failures before removing a browser.
123 ///
124 /// Higher values = more tolerance for transient failures.
125 ///
126 /// # Default
127 ///
128 /// 3 consecutive failures
129 ///
130 /// # Considerations
131 ///
132 /// - Set to 1 for aggressive failure detection
133 /// - Set higher if experiencing transient network issues
134 pub max_ping_failures: u32,
135
136 /// Maximum time allowed for warmup process to complete.
137 ///
138 /// If warmup doesn't complete in this time, it fails with timeout error.
139 ///
140 /// # Default
141 ///
142 /// 60 seconds
143 ///
144 /// # Considerations
145 ///
146 /// - Should be at least `warmup_count * ~5 seconds` per browser
147 /// - Increase if running on slow hardware or with many warmup browsers
148 pub warmup_timeout: Duration,
149}
150
151impl Default for BrowserPoolConfig {
152 /// Production-ready default configuration.
153 ///
154 /// - Pool size: 5 browsers
155 /// - Warmup: 3 browsers
156 /// - Health checks: Every 15 seconds
157 /// - TTL: 1 hour
158 /// - Failure tolerance: 3 consecutive failures
159 /// - Warmup timeout: 60 seconds
160 ///
161 /// # Example
162 ///
163 /// ```rust
164 /// use html2pdf_api::BrowserPoolConfig;
165 /// use std::time::Duration;
166 ///
167 /// let config = BrowserPoolConfig::default();
168 ///
169 /// assert_eq!(config.max_pool_size, 5);
170 /// assert_eq!(config.warmup_count, 3);
171 /// assert_eq!(config.ping_interval, Duration::from_secs(15));
172 /// assert_eq!(config.browser_ttl, Duration::from_secs(3600));
173 /// assert_eq!(config.max_ping_failures, 3);
174 /// assert_eq!(config.warmup_timeout, Duration::from_secs(60));
175 /// ```
176 fn default() -> Self {
177 Self {
178 max_pool_size: 5,
179 warmup_count: 3,
180 ping_interval: Duration::from_secs(15),
181 browser_ttl: Duration::from_secs(3600), // 1 hour
182 max_ping_failures: 3,
183 warmup_timeout: Duration::from_secs(60),
184 }
185 }
186}
187
188/// Builder for [`BrowserPoolConfig`] with validation.
189///
190/// Provides a fluent API for constructing validated configurations.
191/// All setter methods can be chained together.
192///
193/// # Example
194///
195/// ```rust
196/// use std::time::Duration;
197/// use html2pdf_api::BrowserPoolConfigBuilder;
198///
199/// let config = BrowserPoolConfigBuilder::new()
200/// .max_pool_size(10)
201/// .warmup_count(5)
202/// .browser_ttl(Duration::from_secs(7200))
203/// .build()
204/// .expect("Invalid configuration");
205/// ```
206///
207/// # Validation
208///
209/// The [`build()`](Self::build) method validates:
210/// - `max_pool_size` must be greater than 0
211/// - `warmup_count` must be d `max_pool_size`
212pub struct BrowserPoolConfigBuilder {
213 config: BrowserPoolConfig,
214}
215
216impl BrowserPoolConfigBuilder {
217 /// Create a new builder with default values.
218 ///
219 /// # Example
220 ///
221 /// ```rust
222 /// use html2pdf_api::BrowserPoolConfigBuilder;
223 ///
224 /// let builder = BrowserPoolConfigBuilder::new();
225 /// let config = builder.build().unwrap();
226 ///
227 /// // Has default values
228 /// assert_eq!(config.max_pool_size, 5);
229 /// ```
230 pub fn new() -> Self {
231 Self {
232 config: BrowserPoolConfig::default(),
233 }
234 }
235
236 /// Set maximum pool size (must be > 0).
237 ///
238 /// # Parameters
239 ///
240 /// * `size` - Maximum number of browsers in the pool.
241 ///
242 /// # Example
243 ///
244 /// ```rust
245 /// use html2pdf_api::BrowserPoolConfigBuilder;
246 ///
247 /// let config = BrowserPoolConfigBuilder::new()
248 /// .max_pool_size(10)
249 /// .build()
250 /// .unwrap();
251 ///
252 /// assert_eq!(config.max_pool_size, 10);
253 /// ```
254 pub fn max_pool_size(mut self, size: usize) -> Self {
255 self.config.max_pool_size = size;
256 self
257 }
258
259 /// Set warmup count (must be d max_pool_size).
260 ///
261 /// # Parameters
262 ///
263 /// * `count` - Number of browsers to pre-create during warmup.
264 ///
265 /// # Example
266 ///
267 /// ```rust
268 /// use html2pdf_api::BrowserPoolConfigBuilder;
269 ///
270 /// let config = BrowserPoolConfigBuilder::new()
271 /// .max_pool_size(10)
272 /// .warmup_count(5)
273 /// .build()
274 /// .unwrap();
275 ///
276 /// assert_eq!(config.warmup_count, 5);
277 /// ```
278 pub fn warmup_count(mut self, count: usize) -> Self {
279 self.config.warmup_count = count;
280 self
281 }
282
283 /// Set health check interval.
284 ///
285 /// # Parameters
286 ///
287 /// * `interval` - Duration between health check pings.
288 ///
289 /// # Example
290 ///
291 /// ```rust
292 /// use std::time::Duration;
293 /// use html2pdf_api::BrowserPoolConfigBuilder;
294 ///
295 /// let config = BrowserPoolConfigBuilder::new()
296 /// .ping_interval(Duration::from_secs(30))
297 /// .build()
298 /// .unwrap();
299 ///
300 /// assert_eq!(config.ping_interval, Duration::from_secs(30));
301 /// ```
302 pub fn ping_interval(mut self, interval: Duration) -> Self {
303 self.config.ping_interval = interval;
304 self
305 }
306
307 /// Set browser time-to-live before forced retirement.
308 ///
309 /// # Parameters
310 ///
311 /// * `ttl` - Maximum lifetime for each browser instance.
312 ///
313 /// # Example
314 ///
315 /// ```rust
316 /// use std::time::Duration;
317 /// use html2pdf_api::BrowserPoolConfigBuilder;
318 ///
319 /// let config = BrowserPoolConfigBuilder::new()
320 /// .browser_ttl(Duration::from_secs(7200)) // 2 hours
321 /// .build()
322 /// .unwrap();
323 ///
324 /// assert_eq!(config.browser_ttl, Duration::from_secs(7200));
325 /// ```
326 pub fn browser_ttl(mut self, ttl: Duration) -> Self {
327 self.config.browser_ttl = ttl;
328 self
329 }
330
331 /// Set maximum consecutive ping failures before removal.
332 ///
333 /// # Parameters
334 ///
335 /// * `failures` - Number of consecutive failures tolerated.
336 ///
337 /// # Example
338 ///
339 /// ```rust
340 /// use html2pdf_api::BrowserPoolConfigBuilder;
341 ///
342 /// let config = BrowserPoolConfigBuilder::new()
343 /// .max_ping_failures(5)
344 /// .build()
345 /// .unwrap();
346 ///
347 /// assert_eq!(config.max_ping_failures, 5);
348 /// ```
349 pub fn max_ping_failures(mut self, failures: u32) -> Self {
350 self.config.max_ping_failures = failures;
351 self
352 }
353
354 /// Set warmup timeout.
355 ///
356 /// # Parameters
357 ///
358 /// * `timeout` - Maximum time allowed for warmup to complete.
359 ///
360 /// # Example
361 ///
362 /// ```rust
363 /// use std::time::Duration;
364 /// use html2pdf_api::BrowserPoolConfigBuilder;
365 ///
366 /// let config = BrowserPoolConfigBuilder::new()
367 /// .warmup_timeout(Duration::from_secs(120))
368 /// .build()
369 /// .unwrap();
370 ///
371 /// assert_eq!(config.warmup_timeout, Duration::from_secs(120));
372 /// ```
373 pub fn warmup_timeout(mut self, timeout: Duration) -> Self {
374 self.config.warmup_timeout = timeout;
375 self
376 }
377
378 /// Build and validate the configuration.
379 ///
380 /// # Errors
381 ///
382 /// - Returns error if `max_pool_size` is 0
383 /// - Returns error if `warmup_count` > `max_pool_size`
384 ///
385 /// # Example
386 ///
387 /// ```rust
388 /// use html2pdf_api::BrowserPoolConfigBuilder;
389 ///
390 /// // Valid configuration
391 /// let config = BrowserPoolConfigBuilder::new()
392 /// .max_pool_size(10)
393 /// .warmup_count(5)
394 /// .build();
395 /// assert!(config.is_ok());
396 ///
397 /// // Invalid: pool size is 0
398 /// let config = BrowserPoolConfigBuilder::new()
399 /// .max_pool_size(0)
400 /// .build();
401 /// assert!(config.is_err());
402 ///
403 /// // Invalid: warmup exceeds pool size
404 /// let config = BrowserPoolConfigBuilder::new()
405 /// .max_pool_size(5)
406 /// .warmup_count(10)
407 /// .build();
408 /// assert!(config.is_err());
409 /// ```
410 pub fn build(self) -> std::result::Result<BrowserPoolConfig, String> {
411 // Validation: Pool size must be positive
412 if self.config.max_pool_size == 0 {
413 return Err("max_pool_size must be greater than 0".to_string());
414 }
415
416 // Validation: Can't warmup more browsers than pool can hold
417 if self.config.warmup_count > self.config.max_pool_size {
418 return Err("warmup_count cannot exceed max_pool_size".to_string());
419 }
420
421 Ok(self.config)
422 }
423}
424
425impl Default for BrowserPoolConfigBuilder {
426 fn default() -> Self {
427 Self::new()
428 }
429}
430
431// ============================================================================
432// Environment Configuration (feature-gated)
433// ============================================================================
434
435/// Environment-based configuration loading.
436///
437/// This module is only available when the `env-config` feature is enabled.
438///
439/// # Environment File
440///
441/// This module uses `dotenvy` to load environment variables from an `app.env`
442/// file in the current directory. The file is optional - if not found,
443/// environment variables and defaults are used.
444///
445/// # Environment Variables
446///
447/// | Variable | Type | Default | Description |
448/// |----------|------|---------|-------------|
449/// | `BROWSER_POOL_SIZE` | usize | 5 | Maximum pool size |
450/// | `BROWSER_WARMUP_COUNT` | usize | 3 | Warmup browser count |
451/// | `BROWSER_TTL_SECONDS` | u64 | 3600 | Browser TTL in seconds |
452/// | `BROWSER_WARMUP_TIMEOUT_SECONDS` | u64 | 60 | Warmup timeout |
453/// | `BROWSER_PING_INTERVAL_SECONDS` | u64 | 15 | Health check interval |
454/// | `BROWSER_MAX_PING_FAILURES` | u32 | 3 | Max ping failures |
455/// | `CHROME_PATH` | String | auto | Custom Chrome binary path |
456///
457/// # Example `app.env` File
458///
459/// ```text
460/// # Browser Pool Configuration
461/// BROWSER_POOL_SIZE=5
462/// BROWSER_WARMUP_COUNT=3
463/// BROWSER_TTL_SECONDS=3600
464/// BROWSER_WARMUP_TIMEOUT_SECONDS=60
465/// BROWSER_PING_INTERVAL_SECONDS=15
466/// BROWSER_MAX_PING_FAILURES=3
467///
468/// # Chrome Configuration (optional)
469/// # CHROME_PATH=/usr/bin/google-chrome
470/// ```
471#[cfg(feature = "env-config")]
472pub mod env {
473 use super::*;
474 use crate::error::BrowserPoolError;
475
476 /// Default environment file name.
477 pub const ENV_FILE_NAME: &str = "app.env";
478
479 /// Load environment variables from `app.env` file.
480 ///
481 /// Call this early in your application startup to ensure environment
482 /// variables are loaded before any configuration functions are called.
483 ///
484 /// This function is automatically called by [`from_env`], but you can
485 /// call it explicitly if you need to load the file earlier or check
486 /// for errors.
487 ///
488 /// # Returns
489 ///
490 /// - `Ok(PathBuf)` if the file was found and loaded successfully
491 /// - `Err(dotenvy::Error)` if the file was not found or couldn't be parsed
492 ///
493 /// # Example
494 ///
495 /// ```rust,ignore
496 /// use html2pdf_api::config::env::load_env_file;
497 ///
498 /// // Load at application startup
499 /// match load_env_file() {
500 /// Ok(path) => println!("Loaded environment from: {:?}", path),
501 /// Err(e) => println!("No app.env file found: {}", e),
502 /// }
503 /// ```
504 pub fn load_env_file() -> Result<std::path::PathBuf, dotenvy::Error> {
505 dotenvy::from_filename(ENV_FILE_NAME)
506 }
507
508 /// Load configuration from environment variables.
509 ///
510 /// Reads configuration from environment variables with sensible defaults.
511 /// Also loads `app.env` file if present (via `dotenvy`).
512 ///
513 /// # Environment File
514 ///
515 /// This function looks for an `app.env` file in the current directory
516 /// and loads it if present. The file is optional - if not found,
517 /// environment variables and defaults are used.
518 ///
519 /// # Environment Variables
520 ///
521 /// - `BROWSER_POOL_SIZE`: Maximum pool size (default: 5)
522 /// - `BROWSER_WARMUP_COUNT`: Warmup browser count (default: 3)
523 /// - `BROWSER_TTL_SECONDS`: Browser TTL in seconds (default: 3600)
524 /// - `BROWSER_WARMUP_TIMEOUT_SECONDS`: Warmup timeout (default: 60)
525 /// - `BROWSER_PING_INTERVAL_SECONDS`: Health check interval (default: 15)
526 /// - `BROWSER_MAX_PING_FAILURES`: Max ping failures (default: 3)
527 ///
528 /// # Errors
529 ///
530 /// Returns [`BrowserPoolError::Configuration`] if configuration values are invalid.
531 ///
532 /// # Example
533 ///
534 /// ```rust,ignore
535 /// use html2pdf_api::config::env::from_env;
536 ///
537 /// // Set environment variables before calling
538 /// std::env::set_var("BROWSER_POOL_SIZE", "10");
539 ///
540 /// let config = from_env()?;
541 /// assert_eq!(config.max_pool_size, 10);
542 /// ```
543 pub fn from_env() -> Result<BrowserPoolConfig, BrowserPoolError> {
544 // Load app.env file if present (ignore errors if not found)
545 match load_env_file() {
546 Ok(path) => {
547 log::info!("� Loaded configuration from: {:?}", path);
548 }
549 Err(e) => {
550 log::debug!(
551 "� No {} file found or failed to load: {} (using environment variables and defaults)",
552 ENV_FILE_NAME,
553 e
554 );
555 }
556 }
557
558 let max_pool_size = std::env::var("BROWSER_POOL_SIZE")
559 .ok()
560 .and_then(|s| s.parse().ok())
561 .unwrap_or(5);
562
563 let warmup_count = std::env::var("BROWSER_WARMUP_COUNT")
564 .ok()
565 .and_then(|s| s.parse().ok())
566 .unwrap_or(3);
567
568 let ttl_seconds = std::env::var("BROWSER_TTL_SECONDS")
569 .ok()
570 .and_then(|s| s.parse().ok())
571 .unwrap_or(3600u64);
572
573 let warmup_timeout_seconds = std::env::var("BROWSER_WARMUP_TIMEOUT_SECONDS")
574 .ok()
575 .and_then(|s| s.parse().ok())
576 .unwrap_or(60u64);
577
578 let ping_interval_seconds = std::env::var("BROWSER_PING_INTERVAL_SECONDS")
579 .ok()
580 .and_then(|s| s.parse().ok())
581 .unwrap_or(15u64);
582
583 let max_ping_failures = std::env::var("BROWSER_MAX_PING_FAILURES")
584 .ok()
585 .and_then(|s| s.parse().ok())
586 .unwrap_or(3);
587
588 log::info!("' Loading pool configuration from environment:");
589 log::info!(" - Max pool size: {}", max_pool_size);
590 log::info!(" - Warmup count: {}", warmup_count);
591 log::info!(
592 " - Browser TTL: {}s ({}min)",
593 ttl_seconds,
594 ttl_seconds / 60
595 );
596 log::info!(" - Warmup timeout: {}s", warmup_timeout_seconds);
597 log::info!(" - Ping interval: {}s", ping_interval_seconds);
598 log::info!(" - Max ping failures: {}", max_ping_failures);
599
600 BrowserPoolConfigBuilder::new()
601 .max_pool_size(max_pool_size)
602 .warmup_count(warmup_count)
603 .browser_ttl(Duration::from_secs(ttl_seconds))
604 .warmup_timeout(Duration::from_secs(warmup_timeout_seconds))
605 .ping_interval(Duration::from_secs(ping_interval_seconds))
606 .max_ping_failures(max_ping_failures)
607 .build()
608 .map_err(BrowserPoolError::Configuration)
609 }
610
611 /// Get Chrome path from environment.
612 ///
613 /// Reads `CHROME_PATH` environment variable.
614 ///
615 /// **Note:** Call [`from_env`] or [`load_env_file`] first to ensure
616 /// `app.env` is loaded if you're using a configuration file.
617 ///
618 /// # Returns
619 ///
620 /// - `Some(path)` if `CHROME_PATH` is set
621 /// - `None` if not set (will use auto-detection)
622 ///
623 /// # Example
624 ///
625 /// ```rust,ignore
626 /// use html2pdf_api::config::env::{load_env_file, chrome_path_from_env};
627 ///
628 /// // Ensure app.env is loaded first
629 /// let _ = load_env_file();
630 ///
631 /// let path = chrome_path_from_env();
632 /// if let Some(p) = path {
633 /// println!("Using Chrome at: {}", p);
634 /// }
635 /// ```
636 pub fn chrome_path_from_env() -> Option<String> {
637 std::env::var("CHROME_PATH").ok()
638 }
639}
640
641// ============================================================================
642// Unit Tests
643// ============================================================================
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648
649 /// Verifies that BrowserPoolConfigBuilder correctly sets all configuration values.
650 ///
651 /// Tests the happy path where all values are valid and within constraints.
652 #[test]
653 fn test_config_builder() {
654 let config = BrowserPoolConfigBuilder::new()
655 .max_pool_size(10)
656 .warmup_count(5)
657 .browser_ttl(Duration::from_secs(7200))
658 .warmup_timeout(Duration::from_secs(120))
659 .build()
660 .unwrap();
661
662 assert_eq!(config.max_pool_size, 10);
663 assert_eq!(config.warmup_count, 5);
664 assert_eq!(config.browser_ttl.as_secs(), 7200);
665 assert_eq!(config.warmup_timeout.as_secs(), 120);
666 }
667
668 /// Verifies that config builder rejects invalid pool size (zero).
669 ///
670 /// Pool size must be at least 1 to be useful. This test ensures
671 /// the validation catches this error at build time.
672 #[test]
673 fn test_config_validation() {
674 let result = BrowserPoolConfigBuilder::new().max_pool_size(0).build();
675
676 assert!(result.is_err());
677 let err_msg = result.unwrap_err();
678 assert!(
679 err_msg.contains("max_pool_size must be greater than 0"),
680 "Expected validation error message, got: {}",
681 err_msg
682 );
683 }
684
685 /// Verifies that warmup count cannot exceed pool size.
686 ///
687 /// It's illogical to warmup more browsers than the pool can hold.
688 /// This test ensures the configuration builder catches this mistake.
689 #[test]
690 fn test_config_warmup_exceeds_pool() {
691 let result = BrowserPoolConfigBuilder::new()
692 .max_pool_size(5)
693 .warmup_count(10)
694 .build();
695
696 assert!(result.is_err());
697 let err_msg = result.unwrap_err();
698 assert!(
699 err_msg.contains("warmup_count cannot exceed max_pool_size"),
700 "Expected validation error message, got: {}",
701 err_msg
702 );
703 }
704
705 /// Verifies that default configuration values are production-ready.
706 ///
707 /// These defaults are used when no explicit configuration is provided.
708 /// They should be safe and reasonable for most use cases.
709 #[test]
710 fn test_config_defaults() {
711 let config = BrowserPoolConfig::default();
712
713 // Verify production-ready defaults
714 assert_eq!(config.max_pool_size, 5, "Default pool size should be 5");
715 assert_eq!(config.warmup_count, 3, "Default warmup should be 3");
716 assert_eq!(
717 config.ping_interval,
718 Duration::from_secs(15),
719 "Default ping interval should be 15s"
720 );
721 assert_eq!(
722 config.browser_ttl,
723 Duration::from_secs(3600),
724 "Default TTL should be 1 hour"
725 );
726 assert_eq!(
727 config.max_ping_failures, 3,
728 "Default max failures should be 3"
729 );
730 assert_eq!(
731 config.warmup_timeout,
732 Duration::from_secs(60),
733 "Default warmup timeout should be 60s"
734 );
735 }
736
737 /// Verifies that config builder supports method chaining.
738 ///
739 /// The builder pattern should allow fluent API usage where all
740 /// setters can be chained together.
741 #[test]
742 fn test_config_builder_chaining() {
743 let config = BrowserPoolConfigBuilder::new()
744 .max_pool_size(8)
745 .warmup_count(4)
746 .ping_interval(Duration::from_secs(30))
747 .browser_ttl(Duration::from_secs(1800))
748 .max_ping_failures(5)
749 .warmup_timeout(Duration::from_secs(90))
750 .build()
751 .unwrap();
752
753 // Verify all chained values were set correctly
754 assert_eq!(config.max_pool_size, 8);
755 assert_eq!(config.warmup_count, 4);
756 assert_eq!(config.ping_interval.as_secs(), 30);
757 assert_eq!(config.browser_ttl.as_secs(), 1800);
758 assert_eq!(config.max_ping_failures, 5);
759 assert_eq!(config.warmup_timeout.as_secs(), 90);
760 }
761
762 /// Verifies that BrowserPoolConfigBuilder implements Default.
763 #[test]
764 fn test_builder_default() {
765 let builder: BrowserPoolConfigBuilder = Default::default();
766 let config = builder.build().unwrap();
767
768 // Should have same values as BrowserPoolConfig::default()
769 assert_eq!(config.max_pool_size, 5);
770 assert_eq!(config.warmup_count, 3);
771 }
772}