- 实现账户管理改进设计文档中的所有核心功能 - 三科目余额管理 (个人余额、劳动报酬、冻结余额) - 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed) - 三键幂等体系 (JZTxId/BankTxId/SourceKey) - 优先级扣款规则 (先个人后劳动) - 在途资金管理 (可用→在途→结转/回退) - 三账对账闭环 (总账 = 银行账 + 在途净额) - 补偿服务域 (超时检测、重试、死信队列) - 虚拟银行模拟器用于业务测试 - 完整的集成测试套件 (133 个测试全部通过) - Docker 容器化部署配置 - 前端 Vue3 + TypeScript 项目结构
243 lines
8.6 KiB
Rust
243 lines
8.6 KiB
Rust
//! 综合测试套件
|
||
//!
|
||
//! 整合所有测试模块的综合测试
|
||
|
||
#[cfg(test)]
|
||
mod balance_model_tests {
|
||
use chrono::Utc;
|
||
use rust_decimal::Decimal;
|
||
use rust_decimal_macros::dec;
|
||
|
||
use rustjr::domain::ledger::AccountBalance;
|
||
use rustjr::domain::account::AccountType;
|
||
|
||
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(),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_balance_invariant() {
|
||
let balance = create_balance(dec!(1000.00), dec!(500.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
assert_eq!(balance.total_balance(), dec!(1500.00));
|
||
}
|
||
|
||
#[test]
|
||
fn test_priority_deduction() {
|
||
let mut balance = create_balance(dec!(1000.00), dec!(500.00));
|
||
|
||
let result = balance.deduct_with_priority(dec!(1200.00)).unwrap();
|
||
assert_eq!(result.from_personal, dec!(1000.00));
|
||
assert_eq!(result.from_labor, dec!(200.00));
|
||
assert_eq!(balance.available_balance(), dec!(300.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_transit_flow() {
|
||
let mut balance = create_balance(dec!(1000.00), dec!(500.00));
|
||
|
||
// 扣款并建立在途
|
||
// deduct 从 personal 扣 300,变成 700
|
||
balance.deduct_with_priority(dec!(300.00)).unwrap();
|
||
balance.add_transit(dec!(300.00));
|
||
|
||
assert_eq!(balance.transit_amount, dec!(300.00));
|
||
// available_balance = personal(700) + labor(500) = 1200
|
||
assert_eq!(balance.available_balance(), dec!(1200.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
|
||
// 结转在途
|
||
balance.settle_transit(dec!(300.00)).unwrap();
|
||
assert_eq!(balance.transit_amount, dec!(0.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod bank_integration_tests {
|
||
use rust_decimal_macros::dec;
|
||
|
||
use rustjr::infrastructure::bank_integration::mock_bank::MockBankClient;
|
||
use rustjr::infrastructure::bank_integration::{BankClient, BankTransferRequest};
|
||
|
||
#[tokio::test]
|
||
async fn test_mock_bank_transfer() {
|
||
let client = MockBankClient::new();
|
||
client.create_account("ACC001", "测试账户", dec!(10000.00)).unwrap();
|
||
client.create_account("ACC002", "对手账户", dec!(0.00)).unwrap();
|
||
|
||
let request = BankTransferRequest {
|
||
from_account: "ACC001".to_string(),
|
||
to_account: "ACC002".to_string(),
|
||
to_account_name: "对手账户".to_string(),
|
||
to_bank_code: "MOCK".to_string(),
|
||
amount: dec!(3000.00),
|
||
remark: Some("测试转账".to_string()),
|
||
business_no: "TEST001".to_string(),
|
||
};
|
||
|
||
let response = client.transfer(request).await.unwrap();
|
||
assert!(response.success);
|
||
|
||
let balance1 = client.query_balance("ACC001").await.unwrap();
|
||
let balance2 = client.query_balance("ACC002").await.unwrap();
|
||
assert_eq!(balance1.balance, dec!(7000.00));
|
||
assert_eq!(balance2.balance, dec!(3000.00));
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_external_deposit() {
|
||
let client = MockBankClient::new();
|
||
client.create_account("ACC001", "测试账户", dec!(10000.00)).unwrap();
|
||
|
||
client.simulate_external_deposit(
|
||
"ACC001",
|
||
"EXT001",
|
||
"外部",
|
||
dec!(2000.00),
|
||
Some("外部入账".to_string()),
|
||
).unwrap();
|
||
|
||
let balance = client.query_balance("ACC001").await.unwrap();
|
||
assert_eq!(balance.balance, dec!(12000.00));
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod e2e_workflow_tests {
|
||
use chrono::Utc;
|
||
use rust_decimal::Decimal;
|
||
use rust_decimal_macros::dec;
|
||
|
||
use rustjr::domain::ledger::AccountBalance;
|
||
use rustjr::domain::account::AccountType;
|
||
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(),
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_complete_withdrawal_flow() {
|
||
// 初始化
|
||
let mut balance = create_balance(dec!(5000.00), dec!(3000.00));
|
||
let client = MockBankClient::new();
|
||
client.create_account("BANK001", "银行账户", dec!(100000.00)).unwrap();
|
||
client.create_account("USER001", "用户账户", dec!(0.00)).unwrap();
|
||
|
||
let withdrawal_amount = dec!(2000.00);
|
||
|
||
// 1. 扣款并建立在途
|
||
let deduction = balance.deduct_with_priority(withdrawal_amount).unwrap();
|
||
balance.add_transit(withdrawal_amount);
|
||
assert!(balance.validate_invariant().is_ok());
|
||
|
||
// 2. 银行转账
|
||
let request = BankTransferRequest {
|
||
from_account: "BANK001".to_string(),
|
||
to_account: "USER001".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);
|
||
|
||
// 3. 结转在途
|
||
balance.settle_transit(withdrawal_amount).unwrap();
|
||
|
||
// 4. 验证结果
|
||
assert_eq!(balance.personal_balance, dec!(3000.00));
|
||
assert_eq!(balance.labor_balance, dec!(3000.00));
|
||
assert_eq!(balance.transit_amount, dec!(0.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
|
||
let bank_balance = client.query_balance("USER001").await.unwrap();
|
||
assert_eq!(bank_balance.balance, withdrawal_amount);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_failure_recovery_flow() {
|
||
let mut balance = create_balance(dec!(5000.00), dec!(3000.00));
|
||
let client = MockBankClient::new();
|
||
client.create_account("BANK001", "银行账户", dec!(100000.00)).unwrap();
|
||
|
||
let amount = dec!(2000.00);
|
||
|
||
// 1. 扣款并建立在途
|
||
balance.deduct_with_priority(amount).unwrap();
|
||
balance.add_transit(amount);
|
||
|
||
// 2. 模拟银行失败(使用余额不足来触发失败)
|
||
// 先消耗银行账户余额,使第二笔转账失败
|
||
let consume_request = BankTransferRequest {
|
||
from_account: "BANK001".to_string(),
|
||
to_account: "USER001".to_string(),
|
||
to_account_name: "用户账户".to_string(),
|
||
to_bank_code: "MOCK".to_string(),
|
||
amount: dec!(99000.00), // 消耗几乎全部余额,只剩 1000
|
||
remark: None,
|
||
business_no: "CONSUME001".to_string(),
|
||
};
|
||
client.transfer(consume_request).await.unwrap();
|
||
|
||
// 现在银行账户余额只有 1000,转账 2000 应该失败
|
||
let request = BankTransferRequest {
|
||
from_account: "BANK001".to_string(),
|
||
to_account: "USER001".to_string(),
|
||
to_account_name: "用户账户".to_string(),
|
||
to_bank_code: "MOCK".to_string(),
|
||
amount, // 2000
|
||
remark: None,
|
||
business_no: "FAIL001".to_string(),
|
||
};
|
||
|
||
let response = client.transfer(request).await.unwrap();
|
||
assert!(!response.success); // 余额不足应该失败
|
||
|
||
// 3. 回退在途
|
||
balance.rollback_transit(amount);
|
||
|
||
// 4. 验证余额恢复
|
||
// deduct_with_priority(2000) 从 personal 扣减:5000 -> 3000
|
||
// rollback_transit(2000) 恢复 personal:3000 -> 5000
|
||
assert_eq!(balance.personal_balance, dec!(5000.00));
|
||
assert_eq!(balance.labor_balance, dec!(3000.00));
|
||
assert_eq!(balance.transit_amount, dec!(0.00));
|
||
assert!(balance.validate_invariant().is_ok());
|
||
}
|
||
}
|