rustjr-account-management/tests/integration/withdrawal_flow_tests.rs
tangweijie d7f81893c5 Initial commit: 完整的 Rust 账户管理系统
- 实现账户管理改进设计文档中的所有核心功能
- 三科目余额管理 (个人余额、劳动报酬、冻结余额)
- 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed)
- 三键幂等体系 (JZTxId/BankTxId/SourceKey)
- 优先级扣款规则 (先个人后劳动)
- 在途资金管理 (可用→在途→结转/回退)
- 三账对账闭环 (总账 = 银行账 + 在途净额)
- 补偿服务域 (超时检测、重试、死信队列)
- 虚拟银行模拟器用于业务测试
- 完整的集成测试套件 (133 个测试全部通过)
- Docker 容器化部署配置
- 前端 Vue3 + TypeScript 项目结构
2026-01-05 17:56:01 +08:00

265 lines
9.1 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 提现流程集成测试
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_withdrawal_full_flow_success() {
// 1. 初始化系统余额
let mut balance = create_balance(dec!(5000.00), dec!(3000.00));
assert!(balance.validate_invariant().is_ok());
// 2. 初始化虚拟银行
let client = MockBankClient::new();
client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap();
client.create_account("FAMILY001", "家属账户", dec!(0.00)).unwrap();
let withdrawal_amount = dec!(2000.00);
// 3. 按优先级扣款(先个人后劳动)
let deduction = balance.deduct_with_priority(withdrawal_amount).unwrap();
assert_eq!(deduction.from_personal, dec!(2000.00));
assert_eq!(deduction.from_labor, dec!(0.00));
assert!(balance.validate_invariant().is_ok());
// 4. 建立在途
balance.add_transit(withdrawal_amount);
// 5. 提交银行
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: "WD001".to_string(),
};
let response = client.transfer(request).await.unwrap();
assert!(response.success);
// 6. 银行成功,结转在途
balance.settle_transit(withdrawal_amount).unwrap();
// 7. 验证最终状态
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());
// 8. 验证银行余额
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));
}
#[tokio::test]
async fn test_withdrawal_from_both_balances() {
// 测试跨越个人和劳动余额的提现
let mut balance = create_balance(dec!(1000.00), dec!(2000.00));
let withdrawal_amount = dec!(2500.00);
let deduction = balance.deduct_with_priority(withdrawal_amount).unwrap();
// 应该先扣个人 1000再扣劳动 1500
assert_eq!(deduction.from_personal, dec!(1000.00));
assert_eq!(deduction.from_labor, dec!(1500.00));
assert_eq!(balance.personal_balance, dec!(0.00));
assert_eq!(balance.labor_balance, dec!(500.00));
assert!(balance.validate_invariant().is_ok());
}
// ==================== 提现失败回退测试 ====================
#[tokio::test]
async fn test_withdrawal_bank_failure_rollback() {
// 1. 初始化
let mut balance = create_balance(dec!(5000.00), dec!(3000.00));
let original_personal = balance.personal_balance;
let original_labor = balance.labor_balance;
let original_bank = balance.bank_balance;
// 2. 配置银行强制失败
let client = MockBankClient::with_failure_config(FailureConfig::force_failure());
client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap();
let withdrawal_amount = dec!(2000.00);
// 3. 扣款并建立在途
balance.deduct_with_priority(withdrawal_amount).unwrap();
balance.add_transit(withdrawal_amount);
// 4. 提交银行(会失败)
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: "WD002".to_string(),
};
let response = client.transfer(request).await.unwrap();
assert!(!response.success);
// 5. 回退在途
balance.rollback_transit(withdrawal_amount);
// 6. 验证余额恢复
assert_eq!(balance.personal_balance, original_personal);
assert_eq!(balance.labor_balance, original_labor);
assert_eq!(balance.bank_balance, original_bank);
assert_eq!(balance.transit_amount, dec!(0.00));
assert!(balance.validate_invariant().is_ok());
}
// ==================== 余额不足测试 ====================
#[test]
fn test_withdrawal_insufficient_balance() {
let mut balance = create_balance(dec!(1000.00), dec!(500.00));
let withdrawal_amount = dec!(2000.00);
let result = balance.deduct_with_priority(withdrawal_amount);
assert!(result.is_err());
// 余额不应变化
assert_eq!(balance.personal_balance, dec!(1000.00));
assert_eq!(balance.labor_balance, dec!(500.00));
}
#[test]
fn test_withdrawal_exact_balance() {
let mut balance = create_balance(dec!(1000.00), dec!(500.00));
let withdrawal_amount = dec!(1500.00); // 刚好等于总余额
let result = balance.deduct_with_priority(withdrawal_amount);
assert!(result.is_ok());
assert_eq!(balance.personal_balance, dec!(0.00));
assert_eq!(balance.labor_balance, dec!(0.00));
assert_eq!(balance.bank_balance, dec!(0.00));
assert!(balance.validate_invariant().is_ok());
}
// ==================== 冻结余额不可提现测试 ====================
#[test]
fn test_withdrawal_with_frozen_balance() {
let mut balance = create_balance(dec!(1000.00), dec!(500.00));
// 冻结 800
balance.freeze(dec!(800.00));
assert_eq!(balance.frozen_balance, dec!(800.00));
assert_eq!(balance.available_balance(), dec!(700.00)); // 1000 - 800 + 500
// 尝试提现 1000超过可用余额 700
let result = balance.deduct_with_priority(dec!(1000.00));
assert!(result.is_err());
// 提现 600在可用范围内
let result = balance.deduct_with_priority(dec!(600.00));
assert!(result.is_ok());
assert!(balance.validate_invariant().is_ok());
}
// ==================== 多笔提现测试 ====================
#[tokio::test]
async fn test_multiple_withdrawals() {
let mut balance = create_balance(dec!(10000.00), dec!(5000.00));
let client = MockBankClient::new();
client.create_account("PRISON001", "监狱账户", dec!(100000.00)).unwrap();
client.create_account("FAMILY001", "家属1", dec!(0.00)).unwrap();
client.create_account("FAMILY002", "家属2", dec!(0.00)).unwrap();
// 第一笔提现
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: "WD003".to_string(),
};
let response1 = client.transfer(request1).await.unwrap();
assert!(response1.success);
balance.settle_transit(dec!(3000.00)).unwrap();
assert!(balance.validate_invariant().is_ok());
// 第二笔提现
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: "WD004".to_string(),
};
let response2 = client.transfer(request2).await.unwrap();
assert!(response2.success);
balance.settle_transit(dec!(2000.00)).unwrap();
assert!(balance.validate_invariant().is_ok());
// 验证最终余额
assert_eq!(balance.bank_balance, dec!(10000.00)); // 15000 - 3000 - 2000
}
// ==================== 小数精度测试 ====================
#[tokio::test]
async fn test_withdrawal_decimal_precision() {
let mut balance = create_balance(dec!(100.99), dec!(50.01));
let deduction = balance.deduct_with_priority(dec!(75.50)).unwrap();
assert_eq!(deduction.from_personal, dec!(75.50));
assert_eq!(deduction.from_labor, dec!(0.00));
assert_eq!(balance.personal_balance, dec!(25.49));
assert!(balance.validate_invariant().is_ok());
}