//! 在途流转集成测试 //! //! 测试场景: //! - 正常在途流转(可用 → 在途 → 结转) //! - 在途回退(银行失败) //! - 在途超时处理 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::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(), } } // ==================== 正常在途流转测试 ==================== #[tokio::test] async fn test_transit_normal_flow_success() { // 1. 初始化余额 let mut balance = create_balance(dec!(1000.00), dec!(500.00)); assert!(balance.validate_invariant().is_ok()); // 2. 按优先级扣款(模拟提现前扣款) let deduction = balance.deduct_with_priority(dec!(300.00)).unwrap(); assert_eq!(deduction.from_personal, dec!(300.00)); assert_eq!(deduction.from_labor, dec!(0.00)); assert_eq!(balance.personal_balance, dec!(700.00)); assert_eq!(balance.bank_balance, dec!(1200.00)); assert!(balance.validate_invariant().is_ok()); // 3. 划转到在途 balance.add_transit(dec!(300.00)); assert_eq!(balance.transit_amount, dec!(300.00)); // 4. 模拟银行成功 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!(300.00), remark: Some("提现".to_string()), business_no: "TXN001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(response.success); // 5. 结转在途 balance.settle_transit(dec!(300.00)).unwrap(); assert_eq!(balance.transit_amount, dec!(0.00)); // 6. 验证最终状态 assert_eq!(balance.personal_balance, dec!(700.00)); assert_eq!(balance.labor_balance, dec!(500.00)); assert_eq!(balance.bank_balance, dec!(1200.00)); assert!(balance.validate_invariant().is_ok()); } #[tokio::test] async fn test_transit_deduction_from_both() { // 测试扣款跨越个人和劳动余额 let mut balance = create_balance(dec!(200.00), dec!(500.00)); // 扣款 400,应该从个人扣 200,从劳动扣 200 let deduction = balance.deduct_with_priority(dec!(400.00)).unwrap(); assert_eq!(deduction.from_personal, dec!(200.00)); assert_eq!(deduction.from_labor, dec!(200.00)); assert_eq!(balance.personal_balance, dec!(0.00)); assert_eq!(balance.labor_balance, dec!(300.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 在途回退测试 ==================== #[tokio::test] async fn test_transit_rollback_on_bank_failure() { // 1. 初始化 let mut balance = create_balance(dec!(1000.00), dec!(500.00)); // 2. 扣款并建立在途 balance.deduct_with_priority(dec!(300.00)).unwrap(); balance.add_transit(dec!(300.00)); // 3. 模拟银行失败 let client = MockBankClient::with_failure_config(FailureConfig::force_failure()); client.create_account("FROM001", "转出账户", dec!(10000.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!(300.00), remark: None, business_no: "TXN002".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(!response.success); // 4. 回退在途 balance.rollback_transit(dec!(300.00)); // 5. 验证:余额恢复 assert_eq!(balance.personal_balance, dec!(1000.00)); assert_eq!(balance.labor_balance, dec!(500.00)); assert_eq!(balance.transit_amount, dec!(0.00)); assert_eq!(balance.bank_balance, dec!(1500.00)); assert!(balance.validate_invariant().is_ok()); } #[tokio::test] async fn test_transit_partial_rollback() { // 测试部分回退 let mut balance = create_balance(dec!(1000.00), dec!(500.00)); // 建立多笔在途 balance.deduct_with_priority(dec!(300.00)).unwrap(); balance.add_transit(dec!(300.00)); balance.deduct_with_priority(dec!(200.00)).unwrap(); balance.add_transit(dec!(200.00)); assert_eq!(balance.transit_amount, dec!(500.00)); // 部分回退 balance.rollback_transit(dec!(200.00)); assert_eq!(balance.transit_amount, dec!(300.00)); assert_eq!(balance.personal_balance, dec!(700.00)); // 500 + 200 回退 assert!(balance.validate_invariant().is_ok()); } // ==================== 在途不足测试 ==================== #[tokio::test] async fn test_settle_transit_insufficient() { let mut balance = create_balance(dec!(1000.00), dec!(500.00)); balance.transit_amount = dec!(100.00); // 尝试结转超过在途金额 let result = balance.settle_transit(dec!(200.00)); assert!(result.is_err()); // 在途金额不变 assert_eq!(balance.transit_amount, dec!(100.00)); } // ==================== 并发场景测试 ==================== #[tokio::test] async fn test_transit_multiple_transactions() { // 模拟多笔交易的在途管理 let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); // 交易1: 扣款并建立在途 balance.deduct_with_priority(dec!(1000.00)).unwrap(); balance.add_transit(dec!(1000.00)); assert!(balance.validate_invariant().is_ok()); // 交易2: 扣款并建立在途 balance.deduct_with_priority(dec!(500.00)).unwrap(); balance.add_transit(dec!(500.00)); assert!(balance.validate_invariant().is_ok()); // 交易1 成功 balance.settle_transit(dec!(1000.00)).unwrap(); assert!(balance.validate_invariant().is_ok()); // 交易2 失败,回退 balance.rollback_transit(dec!(500.00)); assert!(balance.validate_invariant().is_ok()); // 最终验证 assert_eq!(balance.transit_amount, dec!(0.00)); // 初始 8000,扣 1000 成功,扣 500 回退 = 7000 assert_eq!(balance.bank_balance, dec!(7000.00)); } // ==================== 边界条件测试 ==================== #[tokio::test] async fn test_transit_zero_amount() { let mut balance = create_balance(dec!(1000.00), dec!(500.00)); // 零金额在途 balance.add_transit(dec!(0.00)); assert_eq!(balance.transit_amount, dec!(0.00)); // 零金额结转 let result = balance.settle_transit(dec!(0.00)); assert!(result.is_ok()); } #[tokio::test] async fn test_transit_exact_balance() { let mut balance = create_balance(dec!(300.00), dec!(200.00)); // 扣光所有余额 let deduction = balance.deduct_with_priority(dec!(500.00)).unwrap(); assert_eq!(deduction.from_personal, dec!(300.00)); assert_eq!(deduction.from_labor, dec!(200.00)); assert_eq!(balance.available_balance(), dec!(0.00)); assert!(balance.validate_invariant().is_ok()); } #[tokio::test] async fn test_transit_insufficient_balance() { let mut balance = create_balance(dec!(300.00), dec!(200.00)); // 尝试扣款超过余额 let result = balance.deduct_with_priority(dec!(600.00)); assert!(result.is_err()); // 余额不变 assert_eq!(balance.personal_balance, dec!(300.00)); assert_eq!(balance.labor_balance, dec!(200.00)); } // ==================== 与虚拟银行交互测试 ==================== #[tokio::test] async fn test_transit_with_mock_bank_query() { let client = MockBankClient::new(); client.create_account("ACC001", "测试账户", dec!(10000.00)).unwrap(); // 查询初始余额 let balance = client.query_balance("ACC001").await.unwrap(); assert_eq!(balance.balance, dec!(10000.00)); // 执行转账 client.create_account("ACC002", "对手账户", dec!(0.00)).unwrap(); let request = BankTransferRequest { from_account: "ACC001".to_string(), to_account: "ACC002".to_string(), to_account_name: "对手账户".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(3000.00), remark: None, business_no: "TXN003".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(response.success); // 验证余额变化 let balance = client.query_balance("ACC001").await.unwrap(); assert_eq!(balance.balance, dec!(7000.00)); let balance = client.query_balance("ACC002").await.unwrap(); assert_eq!(balance.balance, dec!(3000.00)); } #[tokio::test] async fn test_transit_bank_ref_tracking() { let client = MockBankClient::new(); client.create_account("ACC001", "测试账户", dec!(10000.00)).unwrap(); client.create_account("ACC002", "对手账户", dec!(0.00)).unwrap(); let request = BankTransferRequest { from_account: "ACC001".to_string(), to_account: "ACC002".to_string(), to_account_name: "对手账户".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(1000.00), remark: None, business_no: "TXN004".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(response.success); // 可以通过业务流水号查询交易状态 let status = client.query_transaction_status("TXN004").await.unwrap(); assert!(status.success); assert_eq!(status.bank_ref_no, response.bank_ref_no); }