librsigstopup 0.1.0

Super safe library untuk simulasi perhitungan top-up dengan JSON API, verbose logging, dan full trace
Documentation
use crate::core::{InsuranceConfig, TaxConfig};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalculatorConfig {
    pub insurance: InsuranceConfig,
    pub tax: TaxConfig,
    pub enable_logging: bool,
    pub log_level: String,
}
impl Default for CalculatorConfig {
    fn default() -> Self {
        Self {
            insurance: InsuranceConfig::default(),
            tax: TaxConfig::default(),
            enable_logging: true,
            log_level: "info".to_string(),
        }
    }
}
impl CalculatorConfig {
    #[must_use]
    pub fn builder() -> ConfigBuilder {
        ConfigBuilder::new()
    }
}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
    insurance_percentage: Option<f64>,
    admin_fee: Option<f64>,
    enable_logging: bool,
    log_level: String,
}
impl ConfigBuilder {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }
    pub fn insurance_percentage(mut self, percent: f64) -> Self {
        self.insurance_percentage = Some(percent);
        self
    }
    pub fn admin_fee(mut self, fee: f64) -> Self {
        self.admin_fee = Some(fee);
        self
    }
    pub fn enable_logging(mut self, enable: bool) -> Self {
        self.enable_logging = enable;
        self
    }
    pub fn log_level(mut self, level: &str) -> Self {
        self.log_level = level.to_string();
        self
    }
    #[must_use]
    pub fn build(self) -> CalculatorConfig {
        let mut config = CalculatorConfig::default();
        if let Some(percent) = self.insurance_percentage {
            config.insurance.percentage = rust_decimal::Decimal::from_f64_retain(percent / 100.0)
                .expect("Failed to convert insurance percentage to Decimal");
        }
        if let Some(fee) = self.admin_fee {
            config.tax.admin_fee = rust_decimal::Decimal::from_f64_retain(fee)
                .expect("Failed to convert admin fee to Decimal");
        }
        config.enable_logging = self.enable_logging;
        config.log_level = if self.log_level.is_empty() {
            "info".to_string()
        } else {
            self.log_level
        };
        config
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal_macros::dec;
    #[test]
    fn test_default_values() {
        let config = CalculatorConfig::default();
        assert_eq!(config.insurance.percentage, dec!(0.015));
        assert_eq!(config.tax.admin_fee, dec!(120_000));
        assert!(config.enable_logging);
        assert_eq!(config.log_level, "info");
    }
    #[test]
    fn test_builder_full_config() {
        let config = CalculatorConfig::builder()
            .insurance_percentage(2.5)
            .admin_fee(200_000.0)
            .enable_logging(false)
            .log_level("error")
            .build();
        assert_eq!(config.insurance.percentage, dec!(0.025));
        assert_eq!(config.tax.admin_fee, dec!(200_000));
        assert!(!config.enable_logging);
        assert_eq!(config.log_level, "error");
    }
    #[test]
    fn test_builder_partial_config() {
        let config = CalculatorConfig::builder().admin_fee(150_000.0).build();
        assert_eq!(config.tax.admin_fee, dec!(150_000));
        assert_eq!(config.insurance.percentage, dec!(0.015));
        assert!(config.enable_logging);
        assert_eq!(config.log_level, "info");
    }
    #[test]
    fn test_percentage_conversion() {
        let test_cases = vec![
            (1.5, dec!(0.015)),
            (2.0, dec!(0.02)),
            (0.5, dec!(0.005)),
            (10.0, dec!(0.1)),
            (0.1, dec!(0.001)),
        ];
        for (input, expected) in test_cases {
            let config = CalculatorConfig::builder()
                .insurance_percentage(input)
                .build();
            assert_eq!(
                config.insurance.percentage, expected,
                "Failed for input: {}",
                input
            );
        }
    }
    #[test]
    fn test_admin_fee_precision() {
        let config = CalculatorConfig::builder().admin_fee(120_000.50).build();
        assert_eq!(config.tax.admin_fee.to_string(), "120000.50");
    }
    #[test]
    fn test_log_level() {
        let config = CalculatorConfig::builder().log_level("debug").build();
        assert_eq!(config.log_level, "debug");
        let config = CalculatorConfig::builder().log_level("").build();
        assert_eq!(config.log_level, "info");
    }
    #[test]
    fn test_enable_logging() {
        let config = CalculatorConfig::builder().enable_logging(false).build();
        assert!(!config.enable_logging);
        let config = CalculatorConfig::builder().enable_logging(true).build();
        assert!(config.enable_logging);
    }
    #[test]
    fn test_serialization() {
        let config = CalculatorConfig::builder()
            .insurance_percentage(2.0)
            .admin_fee(150_000.0)
            .build();
        let json = serde_json::to_string(&config).unwrap();
        assert!(json.contains("insurance"));
        assert!(json.contains("tax"));
        assert!(json.contains("0.02"));
        assert!(json.contains("150000"));
    }
    #[test]
    fn test_deserialization() {
        let json = r#"{
            "insurance": {"percentage": "0.025"},
            "tax": {"base_amount": "0", "admin_fee": "175000"},
            "enable_logging": false,
            "log_level": "warn"
        }"#;
        let config: CalculatorConfig = serde_json::from_str(json).unwrap();
        assert_eq!(config.insurance.percentage, dec!(0.025));
        assert_eq!(config.tax.admin_fee, dec!(175_000));
        assert!(!config.enable_logging);
        assert_eq!(config.log_level, "warn");
    }
    #[test]
    fn test_roundtrip_serialization() {
        let original = CalculatorConfig::builder()
            .insurance_percentage(1.75)
            .admin_fee(135_000.0)
            .enable_logging(true)
            .log_level("trace")
            .build();
        let json = serde_json::to_string(&original).unwrap();
        let restored: CalculatorConfig = serde_json::from_str(&json).unwrap();
        assert_eq!(original.insurance.percentage, restored.insurance.percentage);
        assert_eq!(original.tax.admin_fee, restored.tax.admin_fee);
        assert_eq!(original.enable_logging, restored.enable_logging);
        assert_eq!(original.log_level, restored.log_level);
    }
    #[test]
    fn test_clone() {
        let config1 = CalculatorConfig::builder()
            .insurance_percentage(2.0)
            .build();
        let config2 = config1.clone();
        assert_eq!(config1.insurance.percentage, config2.insurance.percentage);
        assert_eq!(config1.tax.admin_fee, config2.tax.admin_fee);
    }
    #[test]
    fn test_debug_format() {
        let config = CalculatorConfig::default();
        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("CalculatorConfig"));
        assert!(debug_str.contains("insurance"));
        assert!(debug_str.contains("tax"));
    }
}