1use nanny_core::ledger::{Ledger, LedgerDecision, LedgerError, Receipt};
11
12pub struct FakeLedger {
23 balance: u64,
25
26 total_spent: u64,
28}
29
30impl FakeLedger {
31 pub fn new(initial_balance: u64) -> Self {
36 Self {
37 balance: initial_balance,
38 total_spent: 0,
39 }
40 }
41}
42
43impl Ledger for FakeLedger {
44 fn authorize(&self, amount: u64) -> LedgerDecision {
49 if amount <= self.balance {
50 LedgerDecision::Approved
51 } else {
52 LedgerDecision::InsufficientFunds {
53 available: self.balance,
54 requested: amount,
55 }
56 }
57 }
58
59 fn debit(&mut self, amount: u64) -> Result<Receipt, LedgerError> {
64 if amount > self.balance {
65 return Err(LedgerError::InsufficientFunds {
66 requested: amount,
67 available: self.balance,
68 });
69 }
70
71 self.balance -= amount;
72 self.total_spent += amount;
73
74 Ok(Receipt {
75 amount,
76 balance_after: self.balance,
77 })
78 }
79
80 fn balance(&self) -> u64 {
82 self.balance
83 }
84
85 fn total_spent(&self) -> u64 {
87 self.total_spent
88 }
89}
90
91#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn starts_with_full_balance() {
99 let ledger = FakeLedger::new(100);
100 assert_eq!(ledger.balance(), 100);
101 assert_eq!(ledger.total_spent(), 0);
102 }
103
104 #[test]
105 fn debit_reduces_balance() {
106 let mut ledger = FakeLedger::new(100);
107 let receipt = ledger.debit(30).unwrap();
108
109 assert_eq!(receipt.amount, 30);
110 assert_eq!(receipt.balance_after, 70);
111 assert_eq!(ledger.balance(), 70);
112 assert_eq!(ledger.total_spent(), 30);
113 }
114
115 #[test]
116 fn multiple_debits_accumulate() {
117 let mut ledger = FakeLedger::new(100);
118 ledger.debit(10).unwrap();
119 ledger.debit(20).unwrap();
120 ledger.debit(30).unwrap();
121
122 assert_eq!(ledger.balance(), 40);
123 assert_eq!(ledger.total_spent(), 60);
124 }
125
126 #[test]
127 fn debit_exact_balance_succeeds() {
128 let mut ledger = FakeLedger::new(50);
129 let receipt = ledger.debit(50).unwrap();
130
131 assert_eq!(receipt.balance_after, 0);
132 assert_eq!(ledger.balance(), 0);
133 }
134
135 #[test]
136 fn debit_over_balance_fails() {
137 let mut ledger = FakeLedger::new(10);
138 let result = ledger.debit(11);
139
140 assert!(result.is_err());
141 assert_eq!(ledger.balance(), 10);
143 assert_eq!(ledger.total_spent(), 0);
144 }
145
146 #[test]
147 fn authorize_approves_within_balance() {
148 let ledger = FakeLedger::new(100);
149 assert!(matches!(ledger.authorize(100), LedgerDecision::Approved));
150 }
151
152 #[test]
153 fn authorize_denies_over_balance() {
154 let ledger = FakeLedger::new(10);
155 assert!(matches!(
156 ledger.authorize(11),
157 LedgerDecision::InsufficientFunds { available: 10, requested: 11 }
158 ));
159 }
160
161 #[test]
162 fn failed_debit_does_not_change_total_debited() {
163 let mut ledger = FakeLedger::new(5);
164 ledger.debit(3).unwrap();
165 let _ = ledger.debit(10); assert_eq!(ledger.total_spent(), 3); assert_eq!(ledger.balance(), 2);
169 }
170}