//! 正常业务场景测试 //! //! 端到端测试正常业务流程 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; 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_pending(&mut self) { self.status = TransactionStatus::Pending; } fn set_bank_submitted(&mut self) { self.status = TransactionStatus::BankSubmitted; } 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_scenario_withdrawal_e2e() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); let mut txn = MockTransaction::new("WD_E2E_001", dec!(2000.00)); let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); client.create_account("FAMILY001", "家属账户", dec!(0.00)).unwrap(); // 验证初始状态 assert!(balance.validate_invariant().is_ok()); assert_eq!(txn.status, TransactionStatus::Created); // ========== 2. 创建交易 (Created → Pending) ========== // 校验余额 let withdrawal_amount = txn.amount; assert!(balance.available_balance() >= withdrawal_amount); // 扣款并冻结/建立在途 let deduction = balance.deduct_with_priority(withdrawal_amount).unwrap(); balance.add_transit(withdrawal_amount); txn.set_pending(); assert_eq!(txn.status, TransactionStatus::Pending); assert_eq!(deduction.from_personal, dec!(2000.00)); assert_eq!(balance.personal_balance, dec!(3000.00)); assert_eq!(balance.transit_amount, dec!(2000.00)); assert!(balance.validate_invariant().is_ok()); // ========== 3. 提交银行 (Pending → BankSubmitted) ========== 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: Some("罪犯提现".to_string()), business_no: txn.txn_no.clone(), }; let response = client.transfer(request).await.unwrap(); // ========== 4. 银行成功 (BankSubmitted → Success) ========== assert!(response.success); let bank_ref_no = response.bank_ref_no.unwrap(); txn.set_success(bank_ref_no.clone()); // 结转在途 balance.settle_transit(withdrawal_amount).unwrap(); // ========== 5. 验证最终状态 ========== assert_eq!(txn.status, TransactionStatus::Success); assert!(txn.bank_ref_no.is_some()); assert_eq!(balance.personal_balance, dec!(3000.00)); assert_eq!(balance.labor_balance, dec!(3000.00)); assert_eq!(balance.bank_balance, dec!(6000.00)); assert_eq!(balance.transit_amount, dec!(0.00)); assert!(balance.validate_invariant().is_ok()); // 验证银行余额 let prison_balance = client.query_balance("PRISON001").await.unwrap(); assert_eq!(prison_balance.balance, dec!(98000.00)); let family_balance = client.query_balance("FAMILY001").await.unwrap(); assert_eq!(family_balance.balance, dec!(2000.00)); // 验证交易可查询 let status = client.query_transaction_status(&txn.txn_no).await.unwrap(); assert!(status.success); } // ==================== 场景2:完整充值流程 ==================== #[tokio::test] async fn test_scenario_deposit_e2e() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(1000.00), dec!(500.00)); let deposit_amount = dec!(2000.00); let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); // ========== 2. 模拟外部入账 ========== let bank_ref = client.simulate_external_deposit( "PRISON001", "FAMILY001", "家属张三", deposit_amount, Some("给罪犯XXX充值".to_string()), ).unwrap(); // ========== 3. 系统接收到入账通知,更新余额 ========== balance.add_personal(deposit_amount); // ========== 4. 验证最终状态 ========== assert_eq!(balance.personal_balance, dec!(3000.00)); assert_eq!(balance.labor_balance, dec!(500.00)); assert_eq!(balance.bank_balance, dec!(3500.00)); assert!(balance.validate_invariant().is_ok()); // 银行流水可查 let today = Utc::now().date_naive(); let statements = client.query_statements("PRISON001", today, today).await.unwrap(); let deposit_record = statements.iter().find(|s| s.bank_ref_no == bank_ref); assert!(deposit_record.is_some()); } // ==================== 场景3:劳动报酬发放 ==================== #[tokio::test] async fn test_scenario_labor_payment_e2e() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(1000.00), dec!(0.00)); let labor_amount = dec!(500.00); // ========== 2. 发放劳动报酬 ========== balance.add_labor(labor_amount); // ========== 3. 验证 ========== assert_eq!(balance.personal_balance, dec!(1000.00)); assert_eq!(balance.labor_balance, dec!(500.00)); assert_eq!(balance.bank_balance, dec!(1500.00)); assert!(balance.validate_invariant().is_ok()); // 可用余额增加 assert_eq!(balance.available_balance(), dec!(1500.00)); } // ==================== 场景4:冻结后提现 ==================== #[tokio::test] async fn test_scenario_freeze_then_withdraw() { // ========== 1. 初始化 ========== let mut balance = create_balance(dec!(3000.00), dec!(2000.00)); // ========== 2. 冻结部分余额 ========== balance.freeze(dec!(1500.00)); assert_eq!(balance.personal_balance, dec!(1500.00)); assert_eq!(balance.frozen_balance, dec!(1500.00)); assert_eq!(balance.available_balance(), dec!(3500.00)); // 1500 + 2000 // ========== 3. 尝试提现超过可用余额 ========== let result = balance.deduct_with_priority(dec!(4000.00)); assert!(result.is_err()); // 失败,因为可用只有 3500 // ========== 4. 提现可用余额范围内 ========== let result = balance.deduct_with_priority(dec!(3000.00)); assert!(result.is_ok()); // ========== 5. 验证 ========== assert_eq!(balance.personal_balance, dec!(0.00)); assert_eq!(balance.labor_balance, dec!(500.00)); assert_eq!(balance.frozen_balance, dec!(1500.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景5:多笔交易混合 ==================== #[tokio::test] async fn test_scenario_mixed_transactions() { 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 req1 = 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: "MIX001".to_string(), }; let resp1 = client.transfer(req1).await.unwrap(); assert!(resp1.success); balance.settle_transit(dec!(3000.00)).unwrap(); // ========== 2. 收到充值 ========== client.simulate_external_deposit( "PRISON001", "EXT001", "外部", dec!(2000.00), None, ).unwrap(); balance.add_personal(dec!(2000.00)); // ========== 3. 发放劳动报酬 ========== balance.add_labor(dec!(1000.00)); // ========== 4. 第二笔提现 ========== balance.deduct_with_priority(dec!(5000.00)).unwrap(); balance.add_transit(dec!(5000.00)); let req2 = 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!(5000.00), remark: None, business_no: "MIX002".to_string(), }; let resp2 = client.transfer(req2).await.unwrap(); assert!(resp2.success); balance.settle_transit(dec!(5000.00)).unwrap(); // ========== 5. 验证最终状态 ========== // 初始: 10000 + 5000 = 15000 // -3000 +2000 +1000 -5000 = 10000 assert_eq!(balance.bank_balance, dec!(10000.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景6:日终对账 ==================== #[tokio::test] async fn test_scenario_daily_reconciliation() { let client = MockBankClient::new(); client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap(); client.create_account("EXT001", "外部账户", dec!(50000.00)).unwrap(); // 模拟一天的交易 // 出账 5000 let req = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "EXT001".to_string(), to_account_name: "外部".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(5000.00), remark: None, business_no: "DAY001".to_string(), }; client.transfer(req).await.unwrap(); // 入账 3000 client.simulate_external_deposit( "PRISON001", "FAM001", "家属", dec!(3000.00), None, ).unwrap(); // 出账 2000 let req2 = BankTransferRequest { from_account: "PRISON001".to_string(), to_account: "EXT001".to_string(), to_account_name: "外部".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(2000.00), remark: None, business_no: "DAY002".to_string(), }; client.transfer(req2).await.unwrap(); // 日终对账 let today = Utc::now().date_naive(); let statements = client.query_statements("PRISON001", today, today).await.unwrap(); // 统计 let total_out: Decimal = statements.iter() .filter(|s| s.direction == "out") .map(|s| s.amount) .sum(); let total_in: Decimal = statements.iter() .filter(|s| s.direction == "in") .map(|s| s.amount) .sum(); assert_eq!(total_out, dec!(7000.00)); // 5000 + 2000 assert_eq!(total_in, dec!(3000.00)); // 验证余额 let balance = client.query_balance("PRISON001").await.unwrap(); assert_eq!(balance.balance, dec!(96000.00)); // 100000 - 7000 + 3000 }