use crate::client::{Account, Cashflow, NetWorthHistory};
use serde::Serialize;
#[derive(Debug, Serialize, PartialEq)]
pub struct OverviewResult {
pub net_worth: f64,
pub cashflow: CashflowSummary,
pub net_worth_change: f64,
}
#[derive(Debug, Serialize, PartialEq)]
pub struct CashflowSummary {
pub income: f64,
pub spending: f64,
pub net: f64,
}
pub fn compute_overview(
accounts: &[Account],
cashflow: &Cashflow,
history: &NetWorthHistory,
) -> OverviewResult {
let net_worth: f64 = accounts.iter().map(|a| a.current_balance).sum();
let cash_net = cashflow.income - cashflow.spending;
let net_worth_change = net_worth - history.prior_month_net_worth;
OverviewResult {
net_worth,
cashflow: CashflowSummary {
income: cashflow.income,
spending: cashflow.spending,
net: cash_net,
},
net_worth_change,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::{AccountType, Cashflow, NetWorthHistory};
fn account(balance: f64) -> Account {
Account {
id: "id".to_string(),
display_name: "Test".to_string(),
current_balance: balance,
account_type: AccountType {
name: "checking".to_string(),
},
}
}
fn zero_cashflow() -> Cashflow {
Cashflow {
income: 0.0,
spending: 0.0,
prior_month_spending: 0.0,
}
}
fn zero_history() -> NetWorthHistory {
NetWorthHistory {
prior_month_net_worth: 0.0,
}
}
#[test]
fn net_worth_is_assets_minus_liabilities() {
let accounts = vec![
account(100_000.0), account(-30_000.0), ];
let result = compute_overview(&accounts, &zero_cashflow(), &zero_history());
assert_eq!(result.net_worth, 70_000.0);
}
#[test]
fn net_worth_is_negative_when_liabilities_exceed_assets() {
let accounts = vec![account(10_000.0), account(-30_000.0)];
let result = compute_overview(&accounts, &zero_cashflow(), &zero_history());
assert_eq!(result.net_worth, -20_000.0);
}
#[test]
fn net_worth_is_zero_with_no_accounts() {
let result = compute_overview(&[], &zero_cashflow(), &zero_history());
assert_eq!(result.net_worth, 0.0);
}
#[test]
fn cashflow_net_is_income_minus_spending() {
let cashflow = Cashflow {
income: 8_000.0,
spending: 6_500.0,
prior_month_spending: 0.0,
};
let result = compute_overview(&[], &cashflow, &zero_history());
assert_eq!(result.cashflow.income, 8_000.0);
assert_eq!(result.cashflow.spending, 6_500.0);
assert_eq!(result.cashflow.net, 1_500.0);
}
#[test]
fn cashflow_net_is_zero_when_no_activity() {
let result = compute_overview(&[], &zero_cashflow(), &zero_history());
assert_eq!(result.cashflow.net, 0.0);
}
#[test]
fn net_worth_change_is_positive_when_worth_grew() {
let accounts = vec![account(70_000.0)];
let history = NetWorthHistory {
prior_month_net_worth: 68_000.0,
};
let result = compute_overview(&accounts, &zero_cashflow(), &history);
assert_eq!(result.net_worth_change, 2_000.0);
}
#[test]
fn net_worth_change_is_negative_when_worth_shrank() {
let accounts = vec![account(60_000.0)];
let history = NetWorthHistory {
prior_month_net_worth: 68_000.0,
};
let result = compute_overview(&accounts, &zero_cashflow(), &history);
assert_eq!(result.net_worth_change, -8_000.0);
}
#[test]
fn net_worth_change_is_zero_when_unchanged() {
let accounts = vec![account(50_000.0)];
let history = NetWorthHistory {
prior_month_net_worth: 50_000.0,
};
let result = compute_overview(&accounts, &zero_cashflow(), &history);
assert_eq!(result.net_worth_change, 0.0);
}
}