//! 补偿场景测试 //! //! 测试补偿任务的创建、处理和重试机制 use chrono::{Duration, Utc}; use rust_decimal::Decimal; use rust_decimal_macros::dec; use rustjr::domain::ledger::entity::AccountBalance; use rustjr::domain::account::AccountType; use rustjr::domain::compensation::{ CompensationTask, CompensationTaskStatus, CompensationTaskType, }; // ==================== 测试辅助 ==================== 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(), } } fn create_compensation_task(txn_no: &str, task_type: CompensationTaskType) -> CompensationTask { CompensationTask { id: 0, txn_no: txn_no.to_string(), task_type, status: CompensationTaskStatus::Pending, retry_count: 0, max_retries: 3, next_retry_at: None, error_message: None, created_at: Utc::now(), updated_at: Utc::now(), completed_at: None, } } // ==================== 场景1:超时检测创建补偿任务 ==================== #[test] fn test_timeout_detection_creates_task() { // 模拟交易超时检测 // 交易已提交银行超过阈值时间 let submitted_at = Utc::now() - Duration::minutes(10); let timeout_threshold = Duration::minutes(5); let is_timeout = Utc::now() - submitted_at > timeout_threshold; assert!(is_timeout); // 创建补偿任务 let task = create_compensation_task( "TXN_TIMEOUT_001", CompensationTaskType::TimeoutCheck, ); assert_eq!(task.status, CompensationTaskStatus::Pending); assert_eq!(task.retry_count, 0); assert_eq!(task.task_type, CompensationTaskType::TimeoutCheck); } // ==================== 场景2:补偿任务成功处理 ==================== #[test] fn test_compensation_task_success() { let mut task = create_compensation_task( "TXN_TIMEOUT_002", CompensationTaskType::TimeoutCheck, ); // 处理中 task.status = CompensationTaskStatus::Processing; // 模拟对账查询成功 // 银行确认交易已成功 // 任务完成 task.status = CompensationTaskStatus::Completed; task.completed_at = Some(Utc::now()); assert_eq!(task.status, CompensationTaskStatus::Completed); } // ==================== 场景3:补偿任务重试 ==================== #[test] fn test_compensation_task_retry() { let mut task = create_compensation_task( "TXN_TIMEOUT_003", CompensationTaskType::TimeoutCheck, ); // 第一次尝试失败 task.status = CompensationTaskStatus::Processing; task.retry_count = 1; task.error_message = Some("银行查询超时".to_string()); task.status = CompensationTaskStatus::Failed; // 计算下次重试时间(指数退避) let delay = Duration::minutes(2_i64.pow(task.retry_count as u32)); // 2^1 = 2分钟 task.next_retry_at = Some(Utc::now() + delay); assert_eq!(task.status, CompensationTaskStatus::Failed); assert_eq!(task.retry_count, 1); assert!(task.next_retry_at.is_some()); } // ==================== 场景4:达到最大重试进入死信 ==================== #[test] fn test_compensation_task_dead_letter() { let mut task = create_compensation_task( "TXN_TIMEOUT_004", CompensationTaskType::TimeoutCheck, ); // 模拟多次重试失败 for i in 0..task.max_retries { task.retry_count = i + 1; task.error_message = Some(format!("第{}次尝试失败", i + 1)); } // 达到最大重试次数 assert!(task.retry_count >= task.max_retries); task.status = CompensationTaskStatus::DeadLetter; assert_eq!(task.status, CompensationTaskStatus::DeadLetter); } // ==================== 场景5:手动重试死信任务 ==================== #[test] fn test_manual_retry_dead_letter() { let mut task = create_compensation_task( "TXN_TIMEOUT_005", CompensationTaskType::TimeoutCheck, ); // 设置为死信状态 task.retry_count = 3; task.status = CompensationTaskStatus::DeadLetter; task.error_message = Some("多次重试失败".to_string()); // 手动重试 task.status = CompensationTaskStatus::Pending; task.retry_count = 0; // 重置重试次数 task.error_message = None; task.next_retry_at = None; assert_eq!(task.status, CompensationTaskStatus::Pending); assert_eq!(task.retry_count, 0); } // ==================== 场景6:补偿与余额更新 ==================== #[test] fn test_compensation_with_balance_update() { let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); // 假设交易超时,在途还在 balance.deduct_with_priority(dec!(2000.00)).unwrap(); balance.add_transit(dec!(2000.00)); assert_eq!(balance.transit_amount, dec!(2000.00)); assert_eq!(balance.bank_balance, dec!(6000.00)); // 补偿任务执行,发现银行实际成功 let bank_success = true; if bank_success { // 结转在途 balance.settle_transit(dec!(2000.00)).unwrap(); } else { // 回退在途 balance.rollback_transit(dec!(2000.00)); } assert_eq!(balance.transit_amount, dec!(0.00)); assert!(balance.validate_invariant().is_ok()); } // ==================== 场景7:多任务并行处理 ==================== #[test] fn test_parallel_compensation_tasks() { let tasks: Vec = (0..5) .map(|i| { create_compensation_task( &format!("TXN_PAR_{}", i), CompensationTaskType::TimeoutCheck, ) }) .collect(); assert_eq!(tasks.len(), 5); // 所有任务都是 Pending assert!(tasks.iter().all(|t| t.status == CompensationTaskStatus::Pending)); // 模拟并行处理 let mut processed = 0; for _task in &tasks { // 处理逻辑 processed += 1; } assert_eq!(processed, 5); } // ==================== 场景8:补偿任务类型 ==================== #[test] fn test_compensation_task_types() { // 不同类型的补偿任务 let timeout_task = create_compensation_task( "TXN_T1", CompensationTaskType::TimeoutCheck, ); let reversal_task = create_compensation_task( "TXN_R1", CompensationTaskType::Reverse, ); let reconcile_task = create_compensation_task( "TXN_RC1", CompensationTaskType::Reconcile, ); assert_eq!(timeout_task.task_type, CompensationTaskType::TimeoutCheck); assert_eq!(reversal_task.task_type, CompensationTaskType::Reverse); assert_eq!(reconcile_task.task_type, CompensationTaskType::Reconcile); } // ==================== 场景9:补偿任务幂等性 ==================== #[test] fn test_compensation_idempotency() { let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); // 建立在途 balance.deduct_with_priority(dec!(2000.00)).unwrap(); balance.add_transit(dec!(2000.00)); let initial_bank = balance.bank_balance; // 第一次结转 balance.settle_transit(dec!(2000.00)).unwrap(); // 模拟重复调用结转(应该失败或无效果) let result = balance.settle_transit(dec!(2000.00)); // 在途已经为 0,再次结转会失败 assert!(result.is_err()); // 余额只扣减了一次 assert_eq!(balance.bank_balance, initial_bank); } // ==================== 场景10:补偿任务与状态机一致性 ==================== #[test] fn test_compensation_state_consistency() { // 确保补偿后交易状态与余额状态一致 #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum TxnStatus { BankSubmitted, Timeout, Success, Failed, } let mut balance = create_balance(dec!(5000.00), dec!(3000.00)); let withdrawal = dec!(2000.00); balance.deduct_with_priority(withdrawal).unwrap(); balance.add_transit(withdrawal); let mut txn_status = TxnStatus::BankSubmitted; // 超时 txn_status = TxnStatus::Timeout; let _ = txn_status; // 避免 unused 警告 // 补偿:银行成功 let bank_result = true; if bank_result { txn_status = TxnStatus::Success; balance.settle_transit(withdrawal).unwrap(); } else { txn_status = TxnStatus::Failed; balance.rollback_transit(withdrawal); } // 一致性验证 match txn_status { TxnStatus::Success => { // 成功:在途为 0,银行余额已扣 assert_eq!(balance.transit_amount, dec!(0.00)); assert_eq!(balance.bank_balance, dec!(6000.00)); } TxnStatus::Failed => { // 失败:在途为 0,银行余额恢复 assert_eq!(balance.transit_amount, dec!(0.00)); assert_eq!(balance.bank_balance, dec!(8000.00)); } _ => panic!("Invalid final state"), } assert!(balance.validate_invariant().is_ok()); }