//! 失败场景测试 //! //! 测试各种失败情况及恢复 use chrono::Utc; use rust_decimal::Decimal; use rust_decimal_macros::dec; use rustjr::domain::ledger::entity::AccountBalance; use rustjr::domain::account::AccountType; use rustjr::domain::transaction::entity::TransactionStatus; use rustjr::infrastructure::bank_integration::mock_bank::{MockBankClient, FailureConfig}; use rustjr::infrastructure::bank_integration::{BankClient, BankTransferRequest}; // ==================== 测试辅助 ==================== fn create_balance(personal: Decimal, labor: Decimal) -> AccountBalance { AccountBalance { id: 1, account_id: 1001, account_type: AccountType::Virtual, personal_balance: personal, labor_balance: labor, frozen_balance: Decimal::ZERO, bank_balance: personal + labor, transit_amount: Decimal::ZERO, system_balance: personal + labor, available_balance: personal + labor, frozen_amount: Decimal::ZERO, version: 1, updated_at: Utc::now(), } } // ==================== 场景1:银行拒绝交易 ==================== #[tokio::test] async fn test_bank_rejection() { let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); let original_bank = balance.bank_balance; let client = MockBankClient::with_failure_config(FailureConfig::force_failure()); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); // 扣款并建立在途 let withdrawal = dec!(2000.00); balance.deduct_with_priority(withdrawal).unwrap(); balance.add_transit(withdrawal); // 银行拒绝 let request = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "FAMILY001".to_string(), to_account_name: "家属".to_string(), to_bank_code: "MOCK".to_string(), amount: withdrawal, remark: None, business_no: "REJECT001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(!response.success); assert!(response.error_code.is_some()); // 回退在途 balance.rollback_transit(withdrawal); // 验证余额恢复 assert_eq!(balance.bank_balance, original_bank); assert_eq!(balance.transit_amount, dec!(0.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景2:余额不足 ==================== #[tokio::test] async fn test_insufficient_bank_balance() { let client = MockBankClient::new(); client.create_account("FROM001", "转出账户", dec!(100.00)).unwrap(); let request = BankTransferRequest { from_account: "FROM001".to_string(), to_account: "TO001".to_string(), to_account_name: "转入".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(1000.00), // 超过余额 remark: None, business_no: "INSUF001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(!response.success); assert_eq!(response.error_code, Some("INSUFFICIENT_BALANCE".to_string())); } #[test] fn test_insufficient_system_balance() { let mut balance = create_balance(dec!(500.00), dec!(300.00)); // 尝试扣款超过可用余额 let result = balance.deduct_with_priority(dec!(1000.00)); assert!(result.is_err()); // 余额不变 assert_eq!(balance.personal_balance, dec!(500.00)); assert_eq!(balance.labor_balance, dec!(300.00)); } // ==================== 场景3:账户不存在 ==================== #[tokio::test] async fn test_account_not_found() { let client = MockBankClient::new(); let request = BankTransferRequest { from_account: "NONEXISTENT".to_string(), to_account: "TO001".to_string(), to_account_name: "转入".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(100.00), remark: None, business_no: "NOTFOUND001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(!response.success); assert_eq!(response.error_code, Some("ACCOUNT_NOT_FOUND".to_string())); } // ==================== 场景4:部分失败回滚 ==================== #[tokio::test] async fn test_partial_failure_rollback() { // 多笔交易中部分失败的处理 // 使用余额不足来触发第三笔失败 let mut balances = vec![ create_balance(dec!(5000.00), dec!(3000.00)), create_balance(dec!(4000.00), dec!(2000.00)), create_balance(dec!(3000.00), dec!(1000.00)), ]; let client = MockBankClient::new(); // 监狱账户余额只够前两笔 client.create_account("PRISON001", "监狱账户", dec!(2000.00)).unwrap(); client.create_account("FAMILY001", "家属1", dec!(0.00)).unwrap(); client.create_account("FAMILY002", "家属2", dec!(0.00)).unwrap(); client.create_account("FAMILY003", "家属3", dec!(0.00)).unwrap(); let amounts = vec![dec!(1000.00), dec!(1000.00), dec!(1000.00)]; let families = vec!["FAMILY001", "FAMILY002", "FAMILY003"]; let mut results = vec![]; for i in 0..3 { balances[i].deduct_with_priority(amounts[i]).unwrap(); balances[i].add_transit(amounts[i]); let request = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: families[i].to_string(), to_account_name: format!("家属{}", i + 1), to_bank_code: "MOCK".to_string(), amount: amounts[i], remark: None, business_no: format!("PARTIAL_{}", i), }; let response = client.transfer(request).await.unwrap(); results.push(response.success); if response.success { balances[i].settle_transit(amounts[i]).unwrap(); } else { balances[i].rollback_transit(amounts[i]); } } // 验证:前两笔成功,第三笔失败(余额不足) assert!(results[0]); assert!(results[1]); assert!(!results[2]); // 余额不足 // 验证余额 // 第一笔成功:在途清零 assert_eq!(balances[0].transit_amount, dec!(0.00)); // 第二笔成功:在途清零 assert_eq!(balances[1].transit_amount, dec!(0.00)); // 第三笔失败回滚:余额恢复 // 初始: personal=3000, labor=1000, bank=4000 // deduct_with_priority(1000): personal=2000, bank=3000 // rollback_transit(1000): personal=3000, bank=4000 assert_eq!(balances[2].transit_amount, dec!(0.00)); assert_eq!(balances[2].personal_balance, dec!(3000.00)); // 回滚后恢复到 deduct 前的状态 assert_eq!(balances[2].bank_balance, dec!(4000.00)); // bank_balance 也恢复 for balance in &balances { assert!(balance.validate_invariant().is_ok()); } } // ==================== 场景5:重复交易处理 ==================== #[tokio::test] async fn test_duplicate_transaction_handling() { let client = MockBankClient::new(); client.create_account("FROM001", "转出账户", dec!(10000.00)).unwrap(); client.create_account("TO001", "转入账户", dec!(0.00)).unwrap(); // 第一次提交 let request = BankTransferRequest { from_account: "FROM001".to_string(), to_account: "TO001".to_string(), to_account_name: "转入".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(1000.00), remark: None, business_no: "DUP001".to_string(), }; let response1 = client.transfer(request.clone()).await.unwrap(); assert!(response1.success); // 检查幂等性:通过 business_no 查询 let status = client.query_transaction_status("DUP001").await.unwrap(); assert!(status.success); assert_eq!(status.bank_ref_no, response1.bank_ref_no); // 验证只扣款一次 let balance = client.query_balance("FROM001").await.unwrap(); assert_eq!(balance.balance, dec!(9000.00)); } // ==================== 场景6:连续失败后成功 ==================== #[tokio::test] async fn test_retry_after_failures() { let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); // 第一次尝试(失败) let client_fail = MockBankClient::with_failure_config(FailureConfig::force_failure()); client_fail.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); balance.deduct_with_priority(dec!(1000.00)).unwrap(); balance.add_transit(dec!(1000.00)); let request = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "FAMILY001".to_string(), to_account_name: "家属".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(1000.00), remark: None, business_no: "RETRY001".to_string(), }; let response1 = client_fail.transfer(request.clone()).await.unwrap(); assert!(!response1.success); // 回滚 balance.rollback_transit(dec!(1000.00)); assert_eq!(balance.bank_balance, dec!(8000.00)); // 第二次尝试(成功) let client_success = MockBankClient::new(); client_success.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); client_success.create_account("FAMILY001", "家属", dec!(0.00)).unwrap(); balance.deduct_with_priority(dec!(1000.00)).unwrap(); balance.add_transit(dec!(1000.00)); let request2 = BankTransferRequest { business_no: "RETRY002".to_string(), // 新的业务号 ..request }; let response2 = client_success.transfer(request2).await.unwrap(); assert!(response2.success); balance.settle_transit(dec!(1000.00)).unwrap(); // 验证最终状态 assert_eq!(balance.bank_balance, dec!(7000.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景7:冻结余额保护 ==================== #[test] fn test_frozen_balance_protection() { let mut balance = create_balance(dec!(3000.00), dec!(2000.00)); // 冻结 2500 balance.freeze(dec!(2500.00)); // 可用余额现在只有 2500 assert_eq!(balance.available_balance(), dec!(2500.00)); // 尝试扣款 3000(超过可用) let result = balance.deduct_with_priority(dec!(3000.00)); assert!(result.is_err()); // 冻结金额不受影响 assert_eq!(balance.frozen_balance, dec!(2500.00)); // 可以扣款 2000 let result = balance.deduct_with_priority(dec!(2000.00)); assert!(result.is_ok()); assert!(balance.validate_invariant().is_ok()); }