use nanny_core::ledger::{Ledger, LedgerDecision, LedgerError, Receipt};
pub struct FakeLedger {
balance: u64,
total_debited: u64,
}
impl FakeLedger {
pub fn new(initial_balance: u64) -> Self {
Self {
balance: initial_balance,
total_debited: 0,
}
}
}
impl Ledger for FakeLedger {
fn authorize(&self, amount: u64) -> LedgerDecision {
if amount <= self.balance {
LedgerDecision::Approved
} else {
LedgerDecision::InsufficientFunds {
available: self.balance,
requested: amount,
}
}
}
fn debit(&mut self, amount: u64) -> Result<Receipt, LedgerError> {
if amount > self.balance {
return Err(LedgerError::InsufficientFunds {
requested: amount,
available: self.balance,
});
}
self.balance -= amount;
self.total_debited += amount;
Ok(Receipt {
amount,
balance_after: self.balance,
})
}
fn balance(&self) -> u64 {
self.balance
}
fn total_debited(&self) -> u64 {
self.total_debited
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn starts_with_full_balance() {
let ledger = FakeLedger::new(100);
assert_eq!(ledger.balance(), 100);
assert_eq!(ledger.total_debited(), 0);
}
#[test]
fn debit_reduces_balance() {
let mut ledger = FakeLedger::new(100);
let receipt = ledger.debit(30).unwrap();
assert_eq!(receipt.amount, 30);
assert_eq!(receipt.balance_after, 70);
assert_eq!(ledger.balance(), 70);
assert_eq!(ledger.total_debited(), 30);
}
#[test]
fn multiple_debits_accumulate() {
let mut ledger = FakeLedger::new(100);
ledger.debit(10).unwrap();
ledger.debit(20).unwrap();
ledger.debit(30).unwrap();
assert_eq!(ledger.balance(), 40);
assert_eq!(ledger.total_debited(), 60);
}
#[test]
fn debit_exact_balance_succeeds() {
let mut ledger = FakeLedger::new(50);
let receipt = ledger.debit(50).unwrap();
assert_eq!(receipt.balance_after, 0);
assert_eq!(ledger.balance(), 0);
}
#[test]
fn debit_over_balance_fails() {
let mut ledger = FakeLedger::new(10);
let result = ledger.debit(11);
assert!(result.is_err());
assert_eq!(ledger.balance(), 10);
assert_eq!(ledger.total_debited(), 0);
}
#[test]
fn authorize_approves_within_balance() {
let ledger = FakeLedger::new(100);
assert!(matches!(ledger.authorize(100), LedgerDecision::Approved));
}
#[test]
fn authorize_denies_over_balance() {
let ledger = FakeLedger::new(10);
assert!(matches!(
ledger.authorize(11),
LedgerDecision::InsufficientFunds { available: 10, requested: 11 }
));
}
#[test]
fn failed_debit_does_not_change_total_debited() {
let mut ledger = FakeLedger::new(5);
ledger.debit(3).unwrap();
let _ = ledger.debit(10);
assert_eq!(ledger.total_debited(), 3); assert_eq!(ledger.balance(), 2);
}
}