librsigstopup 0.1.0

Super safe library untuk simulasi perhitungan top-up dengan JSON API, verbose logging, dan full trace
Documentation
use librsigstopup::{CalculatorConfig, LoanInput, TopUpHandler};
use rust_decimal_macros::dec;
#[tokio::test]
async fn test_full_calculation_flow_realistic() {
    librsigstopup::init_logging();
    let handler = TopUpHandler::new();
    let input = LoanInput::builder()
        .pinjaman(15_000_000.0)
        .angsuran(750_000.0)
        .sisa_tenor(6)
        .diskon(250_000.0)
        .pajak(180_000.0)
        .build()
        .unwrap();
    let result = handler.calculate(input.clone()).await.unwrap();
    assert_eq!(result.calculations.pinjaman, dec!(15_000_000));
    assert_eq!(result.calculations.angsuran, dec!(750_000));
    assert_eq!(result.calculations.sisa_tenor, 6);
    assert_eq!(result.calculations.sisa_pokok, dec!(10_500_000));
    assert_eq!(result.calculations.asuransi, dec!(225_000));
    assert!(result.total_didapat > dec!(0));
    let json = result.to_json().unwrap();
    assert!(json.contains("request_id"));
    assert!(json.contains("total_didapat"));
    println!("✅ Full calculation flow passed");
}
#[tokio::test]
async fn test_json_api_endpoint() {
    let handler = TopUpHandler::new();
    let json_input = r#"{
        "pinjaman": 8000000,
        "angsuran": 400000,
        "sisa_tenor": 4,
        "diskon": 150000,
        "pajak": 130000
    }"#;
    let json_output = handler.calculate_from_json(json_input).await.unwrap();
    let parsed: serde_json::Value = serde_json::from_str(&json_output).unwrap();
    assert_eq!(parsed["success"], true);
    assert!(parsed["request_id"].as_str().unwrap().len() > 0);
    assert_eq!(parsed["data"]["total_didapat"], "6095000.00");
    println!("✅ JSON API endpoint passed");
}
#[tokio::test]
async fn test_custom_config() {
    let config = CalculatorConfig::builder()
        .insurance_percentage(2.5)
        .admin_fee(200_000.0)
        .build();
    let handler = TopUpHandler::new().with_config(config);
    let input = LoanInput::builder()
        .pinjaman(10_000_000.0)
        .angsuran(500_000.0)
        .sisa_tenor(5)
        .build()
        .unwrap();
    let result = handler.calculate(input).await.unwrap();
    assert_eq!(result.calculations.asuransi, dec!(250_000));
    println!("✅ Custom config passed");
}
#[tokio::test]
async fn test_error_handling_invalid_input() {
    let handler = TopUpHandler::new();
    let invalid_input = LoanInput::builder()
        .pinjaman(-1000.0)
        .angsuran(500_000.0)
        .sisa_tenor(5)
        .build();
    assert!(invalid_input.is_err());
    let invalid_json = r#"{"pinjaman": "not_a_number"}"#;
    let result = handler.calculate_from_json(invalid_json).await;
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("JSON parse error"));
    println!("✅ Error handling passed");
}
#[tokio::test]
async fn test_health_check() {
    let handler = TopUpHandler::new();
    let health = handler.health_check().await;
    assert_eq!(health["status"], "healthy");
    assert!(health["timestamp"].as_str().unwrap().len() > 0);
    assert_eq!(health["version"], env!("CARGO_PKG_VERSION"));
    println!("✅ Health check passed");
}
#[tokio::test]
async fn test_multiple_calculations_sequential() {
    let handler = TopUpHandler::new();
    let test_cases = vec![
        (
            10_000_000.0,
            500_000.0,
            5,
            200_000.0,
            150_000.0,
            7_400_000.0,
        ),
        (5_000_000.0, 250_000.0, 4, 100_000.0, 125_000.0, 3_400_000.0),
        (
            20_000_000.0,
            1_000_000.0,
            3,
            500_000.0,
            300_000.0,
            14_675_000.0,
        ),
    ];
    for (pinjaman, angsuran, tenor, diskon, pajak, expected) in test_cases {
        let input = LoanInput::builder()
            .pinjaman(pinjaman)
            .angsuran(angsuran)
            .sisa_tenor(tenor)
            .diskon(diskon)
            .pajak(pajak)
            .build()
            .unwrap();
        let result = handler.calculate(input).await.unwrap();
        let diff = (result.total_didapat
            - rust_decimal::Decimal::from_f64_retain(expected).unwrap())
        .abs();
        assert!(
            diff < rust_decimal::Decimal::from(1),
            "Expected {}, got {}",
            expected,
            result.total_didapat
        );
    }
    println!("✅ Multiple calculations passed");
}
#[tokio::test]
async fn test_concurrent_calculations() {
    use tokio::task;
    let handler = std::sync::Arc::new(TopUpHandler::new());
    let mut handles = vec![];
    for i in 0..10 {
        let h = handler.clone();
        let pinjaman = 10_000_000.0 + (i as f64 * 1_000_000.0);
        handles.push(task::spawn(async move {
            let input = LoanInput::builder()
                .pinjaman(pinjaman)
                .angsuran(500_000.0)
                .sisa_tenor(5)
                .build()
                .unwrap();
            h.calculate(input).await
        }));
    }
    for handle in handles {
        let result = handle.await.unwrap().unwrap();
        assert!(result.total_didapat > rust_decimal::Decimal::ZERO);
    }
    println!("✅ Concurrent calculations passed");
}
#[test]
fn test_sync_wrapper() {
    let handler = TopUpHandler::new();
    let input = LoanInput::builder()
        .pinjaman(10_000_000.0)
        .angsuran(500_000.0)
        .sisa_tenor(5)
        .build()
        .unwrap();
    println!("⚠️ Sync wrapper test skipped (implement calculate_sync first)");
}