//! 超时场景测试 //! //! 测试交易超时及恢复流程 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(), } } /// 模拟交易 struct MockTransaction { txn_no: String, amount: Decimal, status: TransactionStatus, bank_ref_no: Option, } impl MockTransaction { fn new(txn_no: &str, amount: Decimal) -> Self { Self { txn_no: txn_no.to_string(), amount, status: TransactionStatus::Created, bank_ref_no: None, } } fn set_bank_submitted(&mut self) { self.status = TransactionStatus::BankSubmitted; } fn set_timeout(&mut self) { self.status = TransactionStatus::Timeout; } fn set_success(&mut self, bank_ref_no: String) { self.status = TransactionStatus::Success; self.bank_ref_no = Some(bank_ref_no); } fn set_failed(&mut self) { self.status = TransactionStatus::Failed; } } // ==================== 场景1:超时后对账发现成功 ==================== #[tokio::test] async fn test_timeout_then_reconcile_success() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); let mut txn = MockTransaction::new("TIMEOUT001", dec!(2000.00)); let withdrawal_amount = txn.amount; // 使用正常银行(模拟银行实际成功但响应超时) let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); client.create_account("FAMILY001", "家属账户", dec!(0.00)).unwrap(); // ========== 2. 创建交易并提交 ========== balance.deduct_with_priority(withdrawal_amount).unwrap(); balance.add_transit(withdrawal_amount); txn.set_bank_submitted(); // 实际提交银行(成功) 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_amount, remark: None, business_no: txn.txn_no.clone(), }; let response = client.transfer(request).await.unwrap(); let actual_bank_ref = response.bank_ref_no.clone(); // ========== 3. 模拟系统超时(未收到响应) ========== // 实际上银行成功了,但系统认为超时 txn.set_timeout(); assert_eq!(txn.status, TransactionStatus::Timeout); assert_eq!(balance.transit_amount, dec!(2000.00)); // ========== 4. 对账发现银行已成功 ========== // 查询银行流水或交易状态 let bank_status = client.query_transaction_status(&txn.txn_no).await.unwrap(); assert!(bank_status.success); // ========== 5. 根据对账结果更新状态 ========== // 对账确认:银行已成功,更新交易状态 txn.set_success(bank_status.bank_ref_no.unwrap()); // 结转在途 balance.settle_transit(withdrawal_amount).unwrap(); // ========== 6. 验证最终状态 ========== assert_eq!(txn.status, TransactionStatus::Success); assert_eq!(balance.transit_amount, dec!(0.00)); assert_eq!(balance.bank_balance, dec!(6000.00)); assert!(balance.validate_invariant().is_ok()); // 银行余额正确 let prison_balance = client.query_balance("PRISON001").await.unwrap(); assert_eq!(prison_balance.balance, dec!(98000.00)); } // ==================== 场景2:超时后对账发现失败 ==================== #[tokio::test] async fn test_timeout_then_reconcile_failed() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); let original_bank = balance.bank_balance; let mut txn = MockTransaction::new("TIMEOUT002", dec!(2000.00)); let withdrawal_amount = txn.amount; // 银行会失败 let client = MockBankClient::with_failure_config(FailureConfig::force_failure()); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); // ========== 2. 创建交易并提交 ========== balance.deduct_with_priority(withdrawal_amount).unwrap(); balance.add_transit(withdrawal_amount); txn.set_bank_submitted(); // 提交银行(失败) 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_amount, remark: None, business_no: txn.txn_no.clone(), }; let _response = client.transfer(request).await.unwrap(); // ========== 3. 模拟系统超时 ========== txn.set_timeout(); // ========== 4. 对账发现银行未成功 ========== let bank_status = client.query_transaction_status(&txn.txn_no).await.unwrap(); // 交易不存在(因为失败了没记录)或标记失败 assert!(!bank_status.success || bank_status.error_code == Some("TXN_NOT_FOUND".to_string())); // ========== 5. 回退在途 ========== txn.set_failed(); balance.rollback_transit(withdrawal_amount); // ========== 6. 验证:余额恢复 ========== assert_eq!(txn.status, TransactionStatus::Failed); assert_eq!(balance.transit_amount, dec!(0.00)); assert_eq!(balance.bank_balance, original_bank); assert!(balance.validate_invariant().is_ok()); // 银行余额未变 let prison_balance = client.query_balance("PRISON001").await.unwrap(); assert_eq!(prison_balance.balance, dec!(100000.00)); } // ==================== 场景3:多笔超时交易批量处理 ==================== #[tokio::test] async fn test_batch_timeout_processing() { // 模拟多笔超时交易的批量处理 let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(1000000.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 mut balances = vec![ create_balance(dec!(10000.00), dec!(5000.00)), create_balance(dec!(8000.00), dec!(4000.00)), create_balance(dec!(6000.00), dec!(3000.00)), ]; let amounts = vec![dec!(3000.00), dec!(2000.00), dec!(1500.00)]; let family_accounts = vec!["FAMILY001", "FAMILY002", "FAMILY003"]; // 所有交易都成功但模拟超时 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: family_accounts[i].to_string(), to_account_name: format!("家属{}", i + 1), to_bank_code: "MOCK".to_string(), amount: amounts[i], remark: None, business_no: format!("BATCH_{}", i), }; let response = client.transfer(request).await.unwrap(); assert!(response.success); } // 批量对账确认 for i in 0..3 { let status = client.query_transaction_status(&format!("BATCH_{}", i)).await.unwrap(); assert!(status.success); // 结转在途 balances[i].settle_transit(amounts[i]).unwrap(); assert!(balances[i].validate_invariant().is_ok()); } // 验证所有余额正确 for i in 0..3 { assert_eq!(balances[i].transit_amount, dec!(0.00)); } } // ==================== 场景4:超时期间新交易处理 ==================== #[tokio::test] async fn test_new_transaction_during_timeout() { // 当有超时交易时,新交易应该正常处理 let mut balance = create_balance(dec!(10000.00), dec!(5000.00)); let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(1000000.00)).unwrap(); client.create_account("FAMILY001", "家属1", dec!(0.00)).unwrap(); client.create_account("FAMILY002", "家属2", dec!(0.00)).unwrap(); // ========== 1. 第一笔交易超时 ========== balance.deduct_with_priority(dec!(3000.00)).unwrap(); balance.add_transit(dec!(3000.00)); let request1 = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "FAMILY001".to_string(), to_account_name: "家属1".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(3000.00), remark: None, business_no: "TIMEOUT_TXN".to_string(), }; client.transfer(request1).await.unwrap(); // 假设这笔超时了,在途还在 // ========== 2. 新交易正常处理 ========== // 可用余额:(10000 - 3000) + 5000 = 12000 assert_eq!(balance.available_balance(), dec!(12000.00)); balance.deduct_with_priority(dec!(2000.00)).unwrap(); balance.add_transit(dec!(2000.00)); let request2 = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "FAMILY002".to_string(), to_account_name: "家属2".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(2000.00), remark: None, business_no: "NEW_TXN".to_string(), }; let response2 = client.transfer(request2).await.unwrap(); assert!(response2.success); // 新交易成功,结转 balance.settle_transit(dec!(2000.00)).unwrap(); // ========== 3. 超时交易后续确认 ========== let status1 = client.query_transaction_status("TIMEOUT_TXN").await.unwrap(); assert!(status1.success); balance.settle_transit(dec!(3000.00)).unwrap(); // ========== 4. 验证最终状态 ========== assert_eq!(balance.transit_amount, dec!(0.00)); // 15000 - 3000 - 2000 = 10000 assert_eq!(balance.bank_balance, dec!(10000.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景5:超时阈值边界 ==================== #[test] fn test_timeout_threshold_boundary() { // 测试超时阈值判断逻辑 use chrono::Duration; let submitted_at = Utc::now() - Duration::seconds(300); // 5分钟前 let timeout_threshold = Duration::seconds(300); // 5分钟阈值 // 刚好超时 let is_timeout = Utc::now() - submitted_at >= timeout_threshold; assert!(is_timeout); // 未超时 let submitted_recent = Utc::now() - Duration::seconds(200); let is_timeout_recent = Utc::now() - submitted_recent >= timeout_threshold; assert!(!is_timeout_recent); }