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