rustjr-account-management/tests/unit/invariant_tests.rs
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

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));
}