- 实现账户管理改进设计文档中的所有核心功能 - 三科目余额管理 (个人余额、劳动报酬、冻结余额) - 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed) - 三键幂等体系 (JZTxId/BankTxId/SourceKey) - 优先级扣款规则 (先个人后劳动) - 在途资金管理 (可用→在途→结转/回退) - 三账对账闭环 (总账 = 银行账 + 在途净额) - 补偿服务域 (超时检测、重试、死信队列) - 虚拟银行模拟器用于业务测试 - 完整的集成测试套件 (133 个测试全部通过) - Docker 容器化部署配置 - 前端 Vue3 + TypeScript 项目结构
265 lines
9.1 KiB
Rust
265 lines
9.1 KiB
Rust
//! 提现流程集成测试
|
||
|
||
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());
|
||
}
|
||
|