- 实现账户管理改进设计文档中的所有核心功能 - 三科目余额管理 (个人余额、劳动报酬、冻结余额) - 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed) - 三键幂等体系 (JZTxId/BankTxId/SourceKey) - 优先级扣款规则 (先个人后劳动) - 在途资金管理 (可用→在途→结转/回退) - 三账对账闭环 (总账 = 银行账 + 在途净额) - 补偿服务域 (超时检测、重试、死信队列) - 虚拟银行模拟器用于业务测试 - 完整的集成测试套件 (133 个测试全部通过) - Docker 容器化部署配置 - 前端 Vue3 + TypeScript 项目结构
314 lines
8.7 KiB
Rust
314 lines
8.7 KiB
Rust
//! 不变量校验测试
|
|
//!
|
|
//! 测试 personal + labor + frozen = bank_balance 不变量
|
|
//!
|
|
//! 测试场景:
|
|
//! - 正常状态不变量验证
|
|
//! - 不变量违反检测
|
|
//! - 多操作后不变量验证
|
|
|
|
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::error::AppError;
|
|
|
|
/// 创建测试余额
|
|
fn create_balance(personal: Decimal, labor: Decimal, frozen: Decimal) -> AccountBalance {
|
|
AccountBalance {
|
|
id: 1,
|
|
account_id: 1001,
|
|
account_type: AccountType::Virtual,
|
|
personal_balance: personal,
|
|
labor_balance: labor,
|
|
frozen_balance: frozen,
|
|
bank_balance: personal + labor + frozen,
|
|
transit_amount: Decimal::ZERO,
|
|
system_balance: personal + labor + frozen,
|
|
available_balance: personal + labor,
|
|
frozen_amount: frozen,
|
|
version: 1,
|
|
updated_at: Utc::now(),
|
|
}
|
|
}
|
|
|
|
/// 创建不一致的余额(用于测试不变量违反)
|
|
fn create_invalid_balance(
|
|
personal: Decimal,
|
|
labor: Decimal,
|
|
frozen: Decimal,
|
|
bank: Decimal,
|
|
) -> AccountBalance {
|
|
AccountBalance {
|
|
id: 1,
|
|
account_id: 1001,
|
|
account_type: AccountType::Virtual,
|
|
personal_balance: personal,
|
|
labor_balance: labor,
|
|
frozen_balance: frozen,
|
|
bank_balance: bank, // 故意不一致
|
|
transit_amount: Decimal::ZERO,
|
|
system_balance: bank,
|
|
available_balance: personal + labor,
|
|
frozen_amount: frozen,
|
|
version: 1,
|
|
updated_at: Utc::now(),
|
|
}
|
|
}
|
|
|
|
// ==================== 正常不变量验证 ====================
|
|
|
|
#[test]
|
|
fn test_invariant_holds_zero_balance() {
|
|
let balance = create_balance(dec!(0.00), dec!(0.00), dec!(0.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_holds_personal_only() {
|
|
let balance = create_balance(dec!(1000.00), dec!(0.00), dec!(0.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_holds_labor_only() {
|
|
let balance = create_balance(dec!(0.00), dec!(500.00), dec!(0.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_holds_frozen_only() {
|
|
let balance = create_balance(dec!(0.00), dec!(0.00), dec!(200.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_holds_mixed() {
|
|
let balance = create_balance(dec!(1000.00), dec!(500.00), dec!(200.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_holds_large_amounts() {
|
|
let balance = create_balance(
|
|
dec!(999999999.99),
|
|
dec!(888888888.88),
|
|
dec!(777777777.77),
|
|
);
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
// ==================== 不变量违反检测 ====================
|
|
|
|
#[test]
|
|
fn test_invariant_violation_bank_too_high() {
|
|
let balance = create_invalid_balance(
|
|
dec!(1000.00),
|
|
dec!(500.00),
|
|
dec!(0.00),
|
|
dec!(2000.00), // 银行余额过高
|
|
);
|
|
|
|
let result = balance.validate_invariant();
|
|
assert!(result.is_err());
|
|
|
|
if let Err(AppError::InvariantViolation { account_id: _, expected, actual }) = result {
|
|
assert_eq!(expected, dec!(2000.00));
|
|
assert_eq!(actual, dec!(1500.00));
|
|
} else {
|
|
panic!("Expected InvariantViolation error");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_violation_bank_too_low() {
|
|
let balance = create_invalid_balance(
|
|
dec!(1000.00),
|
|
dec!(500.00),
|
|
dec!(200.00),
|
|
dec!(1000.00), // 银行余额过低
|
|
);
|
|
|
|
let result = balance.validate_invariant();
|
|
assert!(result.is_err());
|
|
|
|
if let Err(AppError::InvariantViolation { account_id: _, expected, actual }) = result {
|
|
assert_eq!(expected, dec!(1000.00));
|
|
assert_eq!(actual, dec!(1700.00));
|
|
} else {
|
|
panic!("Expected InvariantViolation error");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_violation_small_difference() {
|
|
let balance = create_invalid_balance(
|
|
dec!(1000.00),
|
|
dec!(500.00),
|
|
dec!(0.00),
|
|
dec!(1500.01), // 差 0.01
|
|
);
|
|
|
|
let result = balance.validate_invariant();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// ==================== 操作后不变量验证 ====================
|
|
|
|
#[test]
|
|
fn test_invariant_after_add_personal() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
balance.add_personal(dec!(200.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
balance.add_personal(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_after_add_labor() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
balance.add_labor(dec!(200.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
balance.add_labor(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_after_freeze_unfreeze() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
// 冻结
|
|
balance.freeze(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 部分解冻
|
|
balance.unfreeze(dec!(100.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 全部解冻
|
|
balance.unfreeze(dec!(200.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_after_deduction() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
// 只扣个人
|
|
balance.deduct_with_priority(dec!(500.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 扣个人+劳动
|
|
balance.deduct_with_priority(dec!(800.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_after_transit_operations() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
// 在途划转
|
|
balance.deduct_with_priority(dec!(300.00)).unwrap();
|
|
balance.add_transit(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 在途结转
|
|
balance.settle_transit(dec!(300.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_after_transit_rollback() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
// 在途划转
|
|
balance.deduct_with_priority(dec!(300.00)).unwrap();
|
|
balance.add_transit(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 在途回退
|
|
balance.rollback_transit(dec!(300.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 余额应该恢复
|
|
assert_eq!(balance.personal_balance, dec!(1000.00));
|
|
assert_eq!(balance.transit_amount, dec!(0.00));
|
|
}
|
|
|
|
// ==================== 复杂场景测试 ====================
|
|
|
|
#[test]
|
|
fn test_invariant_complex_scenario() {
|
|
let mut balance = create_balance(dec!(5000.00), dec!(3000.00), dec!(0.00));
|
|
|
|
// 1. 冻结部分
|
|
balance.freeze(dec!(1000.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 2. 入账
|
|
balance.add_labor(dec!(500.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 3. 扣款
|
|
balance.deduct_with_priority(dec!(2000.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 4. 在途
|
|
balance.deduct_with_priority(dec!(1000.00)).unwrap();
|
|
balance.add_transit(dec!(1000.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 5. 解冻部分
|
|
balance.unfreeze(dec!(500.00));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
// 6. 在途结转
|
|
balance.settle_transit(dec!(1000.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_idempotent_operations() {
|
|
let mut balance = create_balance(dec!(1000.00), dec!(500.00), dec!(0.00));
|
|
|
|
// 多次同样的操作
|
|
for _ in 0..10 {
|
|
balance.add_personal(dec!(100.00));
|
|
balance.subtract_personal(dec!(100.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
}
|
|
|
|
// 最终余额应该不变
|
|
assert_eq!(balance.personal_balance, dec!(1000.00));
|
|
assert_eq!(balance.labor_balance, dec!(500.00));
|
|
}
|
|
|
|
// ==================== 边界条件 ====================
|
|
|
|
#[test]
|
|
fn test_invariant_with_zero_after_operations() {
|
|
let mut balance = create_balance(dec!(100.00), dec!(0.00), dec!(0.00));
|
|
|
|
// 全部扣完
|
|
balance.deduct_with_priority(dec!(100.00)).unwrap();
|
|
assert!(balance.validate_invariant().is_ok());
|
|
|
|
assert_eq!(balance.personal_balance, dec!(0.00));
|
|
assert_eq!(balance.bank_balance, dec!(0.00));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invariant_precision() {
|
|
// 测试小数精度
|
|
let balance = create_balance(dec!(0.01), dec!(0.02), dec!(0.03));
|
|
assert!(balance.validate_invariant().is_ok());
|
|
assert_eq!(balance.bank_balance, dec!(0.06));
|
|
}
|
|
|