datasynth_test_utils/
mocks.rs1use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5
6use datasynth_core::models::{GLAccount, JournalEntry};
7use rust_decimal::Decimal;
8
9pub struct MockBalanceTracker {
11 balances: Arc<RwLock<HashMap<String, Decimal>>>,
12}
13
14impl MockBalanceTracker {
15 pub fn new() -> Self {
16 Self {
17 balances: Arc::new(RwLock::new(HashMap::new())),
18 }
19 }
20
21 pub fn get_balance(&self, account: &str) -> Decimal {
23 self.balances
24 .read()
25 .unwrap()
26 .get(account)
27 .copied()
28 .unwrap_or(Decimal::ZERO)
29 }
30
31 pub fn update_balance(&self, account: &str, amount: Decimal) {
33 let mut balances = self.balances.write().unwrap();
34 *balances.entry(account.to_string()).or_insert(Decimal::ZERO) += amount;
35 }
36
37 pub fn apply_entry(&self, entry: &JournalEntry) {
39 for line in &entry.lines {
40 let net_amount = line.debit_amount - line.credit_amount;
41 self.update_balance(&line.gl_account, net_amount);
42 }
43 }
44
45 pub fn total_debits(&self) -> Decimal {
47 self.balances
48 .read()
49 .unwrap()
50 .values()
51 .filter(|v| **v > Decimal::ZERO)
52 .copied()
53 .sum()
54 }
55
56 pub fn total_credits(&self) -> Decimal {
58 self.balances
59 .read()
60 .unwrap()
61 .values()
62 .filter(|v| **v < Decimal::ZERO)
63 .map(|v| v.abs())
64 .sum()
65 }
66
67 pub fn clear(&self) {
69 self.balances.write().unwrap().clear();
70 }
71}
72
73impl Default for MockBalanceTracker {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79pub struct MockChartOfAccounts {
81 accounts: Vec<GLAccount>,
82}
83
84impl MockChartOfAccounts {
85 pub fn new(accounts: Vec<GLAccount>) -> Self {
86 Self { accounts }
87 }
88
89 pub fn get_account(&self, number: &str) -> Option<&GLAccount> {
91 self.accounts.iter().find(|a| a.account_number == number)
92 }
93
94 pub fn all_accounts(&self) -> &[GLAccount] {
96 &self.accounts
97 }
98
99 pub fn has_account(&self, number: &str) -> bool {
101 self.accounts.iter().any(|a| a.account_number == number)
102 }
103
104 pub fn get_accounts_by_type(
106 &self,
107 account_type: datasynth_core::models::AccountType,
108 ) -> Vec<&GLAccount> {
109 self.accounts
110 .iter()
111 .filter(|a| a.account_type == account_type)
112 .collect()
113 }
114}
115
116impl Default for MockChartOfAccounts {
117 fn default() -> Self {
118 Self::new(crate::fixtures::standard_test_accounts())
119 }
120}
121
122pub struct MockRng {
124 sequence: Vec<u64>,
125 index: usize,
126}
127
128impl MockRng {
129 pub fn new(sequence: Vec<u64>) -> Self {
130 Self { sequence, index: 0 }
131 }
132
133 #[allow(clippy::should_implement_trait)]
135 pub fn next(&mut self) -> u64 {
136 let value = self.sequence[self.index % self.sequence.len()];
137 self.index += 1;
138 value
139 }
140
141 pub fn next_in_range(&mut self, min: u64, max: u64) -> u64 {
143 let value = self.next();
144 min + (value % (max - min + 1))
145 }
146
147 pub fn next_float(&mut self) -> f64 {
149 (self.next() as f64) / (u64::MAX as f64)
150 }
151
152 pub fn reset(&mut self) {
154 self.index = 0;
155 }
156}
157
158impl Default for MockRng {
159 fn default() -> Self {
160 Self::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::fixtures::balanced_journal_entry;
169 use datasynth_core::models::AccountType;
170
171 #[test]
172 fn test_mock_balance_tracker() {
173 let tracker = MockBalanceTracker::new();
174
175 assert_eq!(tracker.get_balance("100000"), Decimal::ZERO);
176
177 tracker.update_balance("100000", Decimal::new(1000, 0));
178 assert_eq!(tracker.get_balance("100000"), Decimal::new(1000, 0));
179
180 tracker.update_balance("100000", Decimal::new(-500, 0));
181 assert_eq!(tracker.get_balance("100000"), Decimal::new(500, 0));
182 }
183
184 #[test]
185 fn test_mock_balance_tracker_apply_entry() {
186 let tracker = MockBalanceTracker::new();
187 let entry = balanced_journal_entry(Decimal::new(10000, 2));
188
189 tracker.apply_entry(&entry);
190
191 assert_eq!(tracker.get_balance("100000"), Decimal::new(10000, 2));
193 assert_eq!(tracker.get_balance("200000"), Decimal::new(-10000, 2));
195 }
196
197 #[test]
198 fn test_mock_chart_of_accounts() {
199 let coa = MockChartOfAccounts::default();
200
201 assert!(coa.has_account("100000"));
202 assert!(!coa.has_account("999999"));
203
204 let account = coa.get_account("100000").unwrap();
205 assert_eq!(account.account_type, AccountType::Asset);
206
207 let assets = coa.get_accounts_by_type(AccountType::Asset);
208 assert!(!assets.is_empty());
209 }
210
211 #[test]
212 fn test_mock_rng() {
213 let mut rng = MockRng::new(vec![10, 20, 30]);
214
215 assert_eq!(rng.next(), 10);
216 assert_eq!(rng.next(), 20);
217 assert_eq!(rng.next(), 30);
218 assert_eq!(rng.next(), 10); rng.reset();
221 assert_eq!(rng.next(), 10);
222 }
223
224 #[test]
225 fn test_mock_rng_range() {
226 let mut rng = MockRng::new(vec![0, 5, 10, 15, 20]);
227
228 let value = rng.next_in_range(1, 10);
229 assert!((1..=10).contains(&value));
230 }
231}