rate-guard 0.1.0

Thread-safe rate limiting library with multiple algorithms and Duration-based configuration
Documentation
//! Tests for BuildError validation and error handling in all rate limiter builders.
//!
//! This module tests that all builders properly validate their parameters and return
//! appropriate BuildError variants when invalid configurations are provided.

use rate_guard::{Nanos, Millis, MockTimeSource};
use rate_guard::limits::{
    TokenBucketBuilder, FixedWindowCounterBuilder, 
    SlidingWindowCounterBuilder, ApproximateSlidingWindowBuilder
};
use rate_guard::error::BuildError;
use std::time::Duration;

#[cfg(test)]
mod token_bucket_build_errors {
    use super::*;

    #[test]
    fn test_missing_capacity() {
        let result = TokenBucketBuilder::builder()
            .refill_amount(10)
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("capacity"))));
    }

    #[test]
    fn test_missing_refill_amount() {
        let result = TokenBucketBuilder::builder()
            .capacity(100)
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("refill_amount"))));
    }

    #[test]
    fn test_missing_refill_every() {
        let result = TokenBucketBuilder::builder()
            .capacity(100)
            .refill_amount(10)
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("refill_every"))));
    }

    #[test]
    fn test_zero_capacity() {
        let result = TokenBucketBuilder::builder()
            .capacity(0)
            .refill_amount(10)
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "capacity", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_zero_refill_amount() {
        let result = TokenBucketBuilder::builder()
            .capacity(100)
            .refill_amount(0)
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "refill_amount", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_zero_refill_every() {
        let result = TokenBucketBuilder::builder()
            .capacity(100)
            .refill_amount(10)
            .refill_every(Duration::ZERO)
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "refill_every", reason: "must be greater than zero" })
        ));
    }

    #[test]
    fn test_refill_amount_exceeds_capacity() {
        let result = TokenBucketBuilder::builder()
            .capacity(10)
            .refill_amount(20)  // More than capacity
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { 
                field: "refill_amount", 
                reason: "should not exceed capacity for optimal rate limiting behavior" 
            })
        ));
    }

    #[test]
    fn test_valid_configuration() {
        let result = TokenBucketBuilder::builder()
            .capacity(100)
            .refill_amount(10)
            .refill_every(Duration::from_millis(100))
            .with_time(MockTimeSource::new())
            .with_precision::<Nanos>()
            .build();

        assert!(result.is_ok());
    }
}

#[cfg(test)]
mod fixed_window_counter_build_errors {
    use super::*;

    #[test]
    fn test_missing_capacity() {
        let result = FixedWindowCounterBuilder::builder()
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("capacity"))));
    }

    #[test]
    fn test_missing_window_duration() {
        let result = FixedWindowCounterBuilder::builder()
            .capacity(100)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("window_duration"))));
    }

    #[test]
    fn test_zero_capacity() {
        let result = FixedWindowCounterBuilder::builder()
            .capacity(0)
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "capacity", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_zero_window_duration() {
        let result = FixedWindowCounterBuilder::builder()
            .capacity(100)
            .window_duration(Duration::ZERO)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "window_duration", reason: "must be greater than zero" })
        ));
    }

    #[test]
    fn test_valid_configuration() {
        let result = FixedWindowCounterBuilder::builder()
            .capacity(100)
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(result.is_ok());
    }
}

#[cfg(test)]
mod sliding_window_counter_build_errors {
    use super::*;

    #[test]
    fn test_missing_capacity() {
        let result = SlidingWindowCounterBuilder::builder()
            .bucket_duration(Duration::from_secs(10))
            .bucket_count(6)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("capacity"))));
    }

    #[test]
    fn test_missing_bucket_duration() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(100)
            .bucket_count(6)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("bucket_duration"))));
    }

    #[test]
    fn test_missing_bucket_count() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(100)
            .bucket_duration(Duration::from_secs(10))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("bucket_count"))));
    }

    #[test]
    fn test_zero_capacity() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(0)
            .bucket_duration(Duration::from_secs(10))
            .bucket_count(6)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "capacity", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_zero_bucket_duration() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(100)
            .bucket_duration(Duration::ZERO)
            .bucket_count(6)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "bucket_duration", reason: "must be greater than zero" })
        ));
    }

    #[test]
    fn test_zero_bucket_count() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(100)
            .bucket_duration(Duration::from_secs(10))
            .bucket_count(0)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "bucket_count", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_valid_configuration() {
        let result = SlidingWindowCounterBuilder::builder()
            .capacity(100)
            .bucket_duration(Duration::from_secs(10))
            .bucket_count(6)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(result.is_ok());
    }
}

#[cfg(test)]
mod approximate_sliding_window_build_errors {
    use super::*;

    #[test]
    fn test_missing_capacity() {
        let result = ApproximateSlidingWindowBuilder::builder()
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("capacity"))));
    }

    #[test]
    fn test_missing_window_duration() {
        let result = ApproximateSlidingWindowBuilder::builder()
            .capacity(100)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(result, Err(BuildError::MissingArgument("window_duration"))));
    }

    #[test]
    fn test_zero_capacity() {
        let result = ApproximateSlidingWindowBuilder::builder()
            .capacity(0)
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "capacity", reason: "must be greater than 0" })
        ));
    }

    #[test]
    fn test_zero_window_duration() {
        let result = ApproximateSlidingWindowBuilder::builder()
            .capacity(100)
            .window_duration(Duration::ZERO)
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(matches!(
            result, 
            Err(BuildError::InvalidArgument { field: "window_duration", reason: "must be greater than zero" })
        ));
    }

    #[test]
    fn test_valid_configuration() {
        let result = ApproximateSlidingWindowBuilder::builder()
            .capacity(100)
            .window_duration(Duration::from_secs(60))
            .with_time(MockTimeSource::new())
            .with_precision::<Millis>()
            .build();

        assert!(result.is_ok());
    }
}

#[cfg(test)]
mod build_error_display_tests {
    use super::*;

    #[test]
    fn test_missing_argument_display() {
        let error = BuildError::MissingArgument("capacity");
        assert_eq!(error.to_string(), "Missing required argument: capacity");
    }

    #[test]
    fn test_invalid_argument_display() {
        let error = BuildError::InvalidArgument {
            field: "capacity",
            reason: "must be greater than 0"
        };
        assert_eq!(error.to_string(), "Invalid argument 'capacity': must be greater than 0");
    }
}