//! 测试数据夹具 //! //! 提供预定义的测试数据,确保测试可重复和一致 use chrono::Utc; use rust_decimal::Decimal; use rust_decimal_macros::dec; // ==================== 账户相关夹具 ==================== /// 标准监狱主账户 pub const PRISON_MAIN_ACCOUNT: &str = "6225001234567890"; pub const PRISON_MAIN_ACCOUNT_NAME: &str = "XX监狱收款账户"; /// 标准罪犯账户前缀 pub const INMATE_ACCOUNT_PREFIX: &str = "ZF"; /// 外部账户(家属) pub const FAMILY_ACCOUNT: &str = "6226009876543210"; pub const FAMILY_ACCOUNT_NAME: &str = "家属账户"; /// 生成罪犯账户号 pub fn gen_inmate_account_no(inmate_id: i64) -> String { format!("{}{:012}", INMATE_ACCOUNT_PREFIX, inmate_id) } // ==================== 金额相关夹具 ==================== /// 标准测试金额 pub struct TestAmounts; impl TestAmounts { /// 小额 (100元) pub fn small() -> Decimal { dec!(100.00) } /// 中等 (1000元) pub fn medium() -> Decimal { dec!(1000.00) } /// 大额 (10000元) pub fn large() -> Decimal { dec!(10000.00) } /// 零 pub fn zero() -> Decimal { Decimal::ZERO } /// 初始余额 pub fn initial_balance() -> Decimal { dec!(100000.00) } /// 负数(用于测试错误情况) pub fn negative() -> Decimal { dec!(-100.00) } } // ==================== 账户余额夹具 ==================== /// 三科目余额测试数据 #[derive(Debug, Clone)] pub struct BalanceFixture { pub personal_balance: Decimal, pub labor_balance: Decimal, pub frozen_balance: Decimal, pub bank_balance: Decimal, pub transit_amount: Decimal, } impl BalanceFixture { /// 空账户 pub fn empty() -> Self { Self { personal_balance: Decimal::ZERO, labor_balance: Decimal::ZERO, frozen_balance: Decimal::ZERO, bank_balance: Decimal::ZERO, transit_amount: Decimal::ZERO, } } /// 只有个人余额 pub fn personal_only(amount: Decimal) -> Self { Self { personal_balance: amount, labor_balance: Decimal::ZERO, frozen_balance: Decimal::ZERO, bank_balance: amount, transit_amount: Decimal::ZERO, } } /// 只有劳动报酬 pub fn labor_only(amount: Decimal) -> Self { Self { personal_balance: Decimal::ZERO, labor_balance: amount, frozen_balance: Decimal::ZERO, bank_balance: amount, transit_amount: Decimal::ZERO, } } /// 混合余额(个人 + 劳动) pub fn mixed(personal: Decimal, labor: Decimal) -> Self { Self { personal_balance: personal, labor_balance: labor, frozen_balance: Decimal::ZERO, bank_balance: personal + labor, transit_amount: Decimal::ZERO, } } /// 部分冻结 pub fn with_frozen(personal: Decimal, labor: Decimal, frozen: Decimal) -> Self { Self { personal_balance: personal, labor_balance: labor, frozen_balance: frozen, bank_balance: personal + labor + frozen, transit_amount: Decimal::ZERO, } } /// 有在途金额 pub fn with_transit(personal: Decimal, labor: Decimal, transit: Decimal) -> Self { Self { personal_balance: personal, labor_balance: labor, frozen_balance: Decimal::ZERO, bank_balance: personal + labor, transit_amount: transit, } } /// 标准测试余额 pub fn standard() -> Self { Self::mixed(dec!(5000.00), dec!(3000.00)) } /// 不变量是否满足 pub fn invariant_holds(&self) -> bool { self.personal_balance + self.labor_balance + self.frozen_balance == self.bank_balance } /// 可用余额 pub fn available(&self) -> Decimal { self.personal_balance + self.labor_balance } } // ==================== 交易夹具 ==================== /// 交易测试数据 #[derive(Debug, Clone)] pub struct TransactionFixture { pub txn_no: String, pub amount: Decimal, pub from_account: String, pub to_account: String, pub remark: Option, } impl TransactionFixture { /// 生成唯一交易号 pub fn gen_txn_no() -> String { format!("TXN{:016}", Utc::now().timestamp_nanos_opt().unwrap_or(0)) } /// 标准转账 pub fn standard_transfer() -> Self { Self { txn_no: Self::gen_txn_no(), amount: TestAmounts::medium(), from_account: PRISON_MAIN_ACCOUNT.to_string(), to_account: gen_inmate_account_no(1001), remark: Some("测试转账".to_string()), } } /// 提现交易 pub fn withdrawal(inmate_id: i64, amount: Decimal) -> Self { Self { txn_no: Self::gen_txn_no(), amount, from_account: gen_inmate_account_no(inmate_id), to_account: FAMILY_ACCOUNT.to_string(), remark: Some("罪犯提现".to_string()), } } /// 充值交易 pub fn deposit(inmate_id: i64, amount: Decimal) -> Self { Self { txn_no: Self::gen_txn_no(), amount, from_account: FAMILY_ACCOUNT.to_string(), to_account: gen_inmate_account_no(inmate_id), remark: Some("家属充值".to_string()), } } /// 劳动报酬发放 pub fn labor_payment(inmate_id: i64, amount: Decimal) -> Self { Self { txn_no: Self::gen_txn_no(), amount, from_account: PRISON_MAIN_ACCOUNT.to_string(), to_account: gen_inmate_account_no(inmate_id), remark: Some("劳动报酬".to_string()), } } } // ==================== 对账夹具 ==================== /// 对账测试数据 pub struct ReconciliationFixture; impl ReconciliationFixture { /// 平衡的三账数据 pub fn balanced() -> (Decimal, Decimal, Decimal) { ( dec!(100000.00), // 银行余额 dec!(95000.00), // 总账余额 dec!(5000.00), // 在途净额 ) // 总账 + 在途 = 银行,平衡 } /// 短款(银行少) pub fn short() -> (Decimal, Decimal, Decimal) { ( dec!(100000.00), // 银行余额 dec!(100000.00), // 总账余额 dec!(5000.00), // 在途净额 ) // 银行 < 总账 + 在途,短款 } /// 长款(银行多) pub fn long() -> (Decimal, Decimal, Decimal) { ( dec!(110000.00), // 银行余额 dec!(100000.00), // 总账余额 dec!(5000.00), // 在途净额 ) // 银行 > 总账 + 在途,长款 } } // ==================== 时间夹具 ==================== pub mod time_fixtures { use chrono::{Duration, NaiveDate, Utc}; /// 今天 pub fn today() -> NaiveDate { Utc::now().date_naive() } /// 昨天 pub fn yesterday() -> NaiveDate { (Utc::now() - Duration::days(1)).date_naive() } /// 一周前 pub fn week_ago() -> NaiveDate { (Utc::now() - Duration::days(7)).date_naive() } /// 一个月前 pub fn month_ago() -> NaiveDate { (Utc::now() - Duration::days(30)).date_naive() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_balance_fixture_invariant() { let balance = BalanceFixture::mixed(dec!(1000.00), dec!(500.00)); assert!(balance.invariant_holds()); assert_eq!(balance.available(), dec!(1500.00)); } #[test] fn test_gen_inmate_account() { let account = gen_inmate_account_no(1001); assert!(account.starts_with(INMATE_ACCOUNT_PREFIX)); assert_eq!(account.len(), 14); } }