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)");
}