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

243 lines
8.6 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.

//! 综合测试套件
//!
//! 整合所有测试模块的综合测试
#[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) 恢复 personal3000 -> 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());
}
}