tktax_budgeting/
lib.rs

1// ---------------- [ File: tktax-budgeting/src/lib.rs ]
2#[macro_use] mod imports; use imports::*;
3
4//TODO: we want to be able to use the average of the past N years categorical data minus some
5//percentage specific to each category (for example, minus 5%, for FoodServices) as our budget for
6//this year. This is because we want to have an indicator which tells us if we are getting better
7//in each category.
8//
9x!{budget_comparison}
10x!{budgeting}
11x!{budget}
12x!{errors}
13x!{time_period}
14
15#[cfg(test)]
16mod budgeting_tests {
17    use super::*;
18
19    fn mock_amount(val: i64) -> MonetaryAmount {
20        // Example: MonetaryAmount::new(integer_part, fractional_part).
21        MonetaryAmount::from(val)
22    }
23
24    #[test]
25    fn test_budget_builder_defaults() {
26        let budget = BudgetBuilder::<MockTransactionCategory>::default()
27            .build() // No fields set; uses all defaults
28            .expect("Should build with all default fields");
29
30        // Check defaults:
31        assert_eq!(*budget.default_monthly_budget(), mock_amount(0));
32        assert_eq!(*budget.time_period(), BudgetTimePeriod::Monthly);
33        assert!(budget.monthly_category_budgets().is_empty());
34    }
35
36    #[test]
37    fn test_budget_builder_with_fields() {
38        let category_food = MockTransactionCategory::FoodServices;
39        let category_rent = MockTransactionCategory::Housing;
40
41        let budget = BudgetBuilder::default()
42            .monthly_category_budgets({
43                let mut map = HashMap::new();
44                map.insert(category_food.clone(), mock_amount(300));
45                map.insert(category_rent.clone(), mock_amount(800));
46                map
47            })
48            .default_monthly_budget(mock_amount(50))
49            .time_period(BudgetTimePeriod::Quarterly)
50            .build()
51            .unwrap();
52
53        assert_eq!(budget.monthly_amount_for_category(&category_food), mock_amount(300));
54        assert_eq!(budget.monthly_amount_for_category(&category_rent), mock_amount(800));
55        assert_eq!(*budget.default_monthly_budget(), mock_amount(50));
56        assert_eq!(*budget.time_period(), BudgetTimePeriod::Quarterly);
57    }
58
59    #[test]
60    fn test_set_and_remove_category_budget() {
61        let cat_util = MockTransactionCategory::UtilitiesAndServices;
62
63        // Start with default builder.
64        let mut budget = BudgetBuilder::<MockTransactionCategory>::default()
65            .default_monthly_budget(mock_amount(100))
66            .build()
67            .unwrap();
68
69        // Initially not present: fallback used
70        assert_eq!(budget.monthly_amount_for_category(&cat_util), mock_amount(100));
71
72        // Set category budget
73        budget.set_category_budget(cat_util.clone(), mock_amount(500));
74        assert_eq!(budget.monthly_amount_for_category(&cat_util), mock_amount(500));
75
76        // Remove it
77        budget.remove_category_budget(&cat_util).unwrap();
78        assert_eq!(budget.monthly_amount_for_category(&cat_util), mock_amount(100));
79    }
80
81    #[test]
82    fn test_compare_actual_to_budget_empty_account() {
83
84        // Suppose a blank account with no transactions
85        let account = Account::empty();
86
87        let category_map: CategoryMap<MockTransactionCategory> = CategoryMap::new();
88
89        let budget = BudgetBuilder::default()
90            .default_monthly_budget(MonetaryAmount::from(100.0))
91            .build()
92            .unwrap();
93
94        // No data => no comparisons (or zero actual amounts).
95        let comps = account.compare_actual_to_budget_monthly(&budget, &category_map);
96
97        assert_eq!(comps.len(), 0);
98    }
99
100    #[traced_test]
101    fn test_compare_actual_to_budget_mock_account() {
102
103        let category_food = MockTransactionCategory::FoodServices;
104        let category_rent = MockTransactionCategory::Housing;
105
106        // Possibly you do not even want to rely on reading a real file in this test. 
107        // But if you do, you can specify the path:
108        let root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
109        let config_path = PathBuf::from(&root).join("..").join("config.toml");
110
111        let program_config = load_program_config_from_path(&config_path)
112            .expect("Could not load config in test");
113
114        // Then proceed using program_config, or something that references it
115        let account = Account::new(TrackedYear::Twenty23, AccountKind::Credit, &program_config)
116            .expect("expected to create our account");
117
118        let category_map: CategoryMap<MockTransactionCategory> = CategoryMap::new();
119
120        let budget = BudgetBuilder::default()
121            .monthly_category_budgets({
122                let mut map = HashMap::new();
123                map.insert(category_food.clone(), mock_amount(300));
124                map.insert(category_rent.clone(), mock_amount(800));
125                map
126            })
127            .default_monthly_budget(mock_amount(50))
128            .time_period(BudgetTimePeriod::Quarterly)
129            .build()
130            .unwrap();
131
132        // No data => no comparisons (or zero actual amounts).
133        let comps = account.compare_actual_to_budget_monthly(&budget, &category_map);
134
135        assert!(comps.len() > 0); 
136    }
137}