//! 端到端业务测试 //! //! 综合测试所有核心业务流程,使用虚拟银行模拟器 use chrono::Utc; use rust_decimal::Decimal; use rust_decimal_macros::dec; use rustjr::domain::account::AccountType; use rustjr::domain::ledger::AccountBalance; use rustjr::infrastructure::bank_integration::mock_bank::{MockBankClient, MockBankTestEnv, FailureConfig}; use rustjr::infrastructure::bank_integration::{BankClient, BankTransferRequest}; // ==================== 端到端测试入口 ==================== /// 完整的业务流程测试 /// /// 测试覆盖: /// 1. 账户初始化 /// 2. 充值(个人/劳动) /// 3. 提现(正常/失败/超时) /// 4. 冻结解冻 /// 5. 三账对账 /// 6. 补偿处理 #[tokio::test] async fn test_full_business_cycle() { use chrono::Utc; use rust_decimal::Decimal; // ========== 阶段1:初始化 ========== let client = MockBankClient::new(); client.create_account("PRISON_MAIN", "监狱主账户", dec!(1000000.00)).unwrap(); client.create_account("FAMILY_001", "家属账户1", dec!(50000.00)).unwrap(); client.create_account("FAMILY_002", "家属账户2", dec!(30000.00)).unwrap(); let mut inmate_balance = AccountBalance { id: 1, account_id: 1001, account_type: AccountType::Virtual, personal_balance: Decimal::ZERO, labor_balance: Decimal::ZERO, frozen_balance: Decimal::ZERO, bank_balance: Decimal::ZERO, transit_amount: Decimal::ZERO, system_balance: Decimal::ZERO, available_balance: Decimal::ZERO, frozen_amount: Decimal::ZERO, version: 1, updated_at: Utc::now(), }; // ========== 阶段2:充值 ========== // 家属充值 5000 元 client.simulate_external_deposit( "PRISON_MAIN", "FAMILY_001", "家属1", dec!(5000.00), Some("给罪犯1001充值".to_string()), ).unwrap(); inmate_balance.add_personal(dec!(5000.00)); assert!(inmate_balance.validate_invariant().is_ok()); assert_eq!(inmate_balance.personal_balance, dec!(5000.00)); // 发放劳动报酬 2000 元 inmate_balance.add_labor(dec!(2000.00)); assert!(inmate_balance.validate_invariant().is_ok()); assert_eq!(inmate_balance.labor_balance, dec!(2000.00)); // ========== 阶段3:冻结部分资金 ========== inmate_balance.freeze(dec!(1000.00)); assert_eq!(inmate_balance.frozen_balance, dec!(1000.00)); assert_eq!(inmate_balance.available_balance(), dec!(6000.00)); // 5000-1000+2000 assert!(inmate_balance.validate_invariant().is_ok()); // ========== 阶段4:正常提现 ========== let withdrawal_amount = dec!(3000.00); let deduction = inmate_balance.deduct_with_priority(withdrawal_amount).unwrap(); assert_eq!(deduction.from_personal, dec!(3000.00)); assert_eq!(deduction.from_labor, dec!(0.00)); inmate_balance.add_transit(withdrawal_amount); let request = BankTransferRequest { from_account: "PRISON_MAIN".to_string(), to_account: "FAMILY_002".to_string(), to_account_name: "家属2".to_string(), to_bank_code: "MOCK".to_string(), amount: withdrawal_amount, remark: Some("罪犯1001提现".to_string()), business_no: "E2E_WD_001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(response.success); inmate_balance.settle_transit(withdrawal_amount).unwrap(); assert!(inmate_balance.validate_invariant().is_ok()); assert_eq!(inmate_balance.personal_balance, dec!(1000.00)); // 5000-1000-3000 assert_eq!(inmate_balance.bank_balance, dec!(4000.00)); // 7000-3000 // ========== 阶段5:验证银行余额 ========== let prison_balance = client.query_balance("PRISON_MAIN").await.unwrap(); // 初始 1000000 + 5000(入账) - 3000(出账) = 1002000 assert_eq!(prison_balance.balance, dec!(1002000.00)); let family2_balance = client.query_balance("FAMILY_002").await.unwrap(); // 初始 30000 + 3000 = 33000 assert_eq!(family2_balance.balance, dec!(33000.00)); // ========== 阶段6:解冻并提现全部 ========== inmate_balance.unfreeze(dec!(1000.00)); assert_eq!(inmate_balance.frozen_balance, dec!(0.00)); assert_eq!(inmate_balance.available_balance(), dec!(4000.00)); // 提现全部余额 let final_withdrawal = dec!(4000.00); let deduction2 = inmate_balance.deduct_with_priority(final_withdrawal).unwrap(); assert_eq!(deduction2.from_personal, dec!(2000.00)); // 1000+1000(解冻) assert_eq!(deduction2.from_labor, dec!(2000.00)); inmate_balance.add_transit(final_withdrawal); let request2 = BankTransferRequest { from_account: "PRISON_MAIN".to_string(), to_account: "FAMILY_001".to_string(), to_account_name: "家属1".to_string(), to_bank_code: "MOCK".to_string(), amount: final_withdrawal, remark: Some("罪犯1001结清".to_string()), business_no: "E2E_WD_002".to_string(), }; let response2 = client.transfer(request2).await.unwrap(); assert!(response2.success); inmate_balance.settle_transit(final_withdrawal).unwrap(); // ========== 阶段7:验证最终状态 ========== assert_eq!(inmate_balance.personal_balance, dec!(0.00)); assert_eq!(inmate_balance.labor_balance, dec!(0.00)); assert_eq!(inmate_balance.frozen_balance, dec!(0.00)); assert_eq!(inmate_balance.bank_balance, dec!(0.00)); assert_eq!(inmate_balance.transit_amount, dec!(0.00)); assert!(inmate_balance.validate_invariant().is_ok()); // ========== 阶段8:验证银行对账流水 ========== let today = Utc::now().date_naive(); let statements = client.query_statements("PRISON_MAIN", today, today).await.unwrap(); // 应有 3 条流水:1入2出 assert_eq!(statements.len(), 3); let inbound_count = statements.iter().filter(|s| s.direction == "in").count(); let outbound_count = statements.iter().filter(|s| s.direction == "out").count(); assert_eq!(inbound_count, 1); assert_eq!(outbound_count, 2); let total_in: Decimal = statements.iter() .filter(|s| s.direction == "in") .map(|s| s.amount) .sum(); let total_out: Decimal = statements.iter() .filter(|s| s.direction == "out") .map(|s| s.amount) .sum(); assert_eq!(total_in, dec!(5000.00)); assert_eq!(total_out, dec!(7000.00)); } /// 测试失败恢复流程 #[tokio::test] async fn test_failure_recovery_cycle() { use chrono::Utc; use rust_decimal::Decimal; // 配置银行强制失败 let client = MockBankClient::with_failure_config(FailureConfig::force_failure()); client.create_account("PRISON_MAIN", "监狱主账户", dec!(100000.00)).unwrap(); let mut balance = AccountBalance { id: 1, account_id: 1001, account_type: AccountType::Virtual, personal_balance: dec!(5000.00), labor_balance: dec!(3000.00), frozen_balance: Decimal::ZERO, bank_balance: dec!(8000.00), transit_amount: Decimal::ZERO, system_balance: dec!(8000.00), available_balance: dec!(8000.00), frozen_amount: Decimal::ZERO, version: 1, updated_at: Utc::now(), }; let original_bank = balance.bank_balance; // 尝试提现(会失败) balance.deduct_with_priority(dec!(2000.00)).unwrap(); balance.add_transit(dec!(2000.00)); let request = BankTransferRequest { from_account: "PRISON_MAIN".to_string(), to_account: "FAMILY001".to_string(), to_account_name: "家属".to_string(), to_bank_code: "MOCK".to_string(), amount: dec!(2000.00), remark: None, business_no: "FAIL_001".to_string(), }; let response = client.transfer(request).await.unwrap(); assert!(!response.success); // 回退 balance.rollback_transit(dec!(2000.00)); // 验证余额恢复 assert_eq!(balance.bank_balance, original_bank); assert_eq!(balance.transit_amount, dec!(0.00)); assert!(balance.validate_invariant().is_ok()); } /// 测试使用 MockBankTestEnv 快捷环境 #[tokio::test] async fn test_with_standard_env() { let env = MockBankTestEnv::new(); // 验证标准环境已创建 let balance = env.client.query_balance("MAIN001").await.unwrap(); assert_eq!(balance.balance, dec!(100000.00)); let ext_balance = env.client.query_balance("EXT001").await.unwrap(); assert_eq!(ext_balance.balance, dec!(50000.00)); }