diff options
Diffstat (limited to 'rust')
15 files changed, 432 insertions, 21 deletions
diff --git a/rust/actualbudget_models/src/actualbudget_account.rs b/rust/actualbudget_models/src/actualbudget_account.rs index 9f058cd..da17146 100644 --- a/rust/actualbudget_models/src/actualbudget_account.rs +++ b/rust/actualbudget_models/src/actualbudget_account.rs @@ -1,6 +1,6 @@ use diesel::prelude::{Queryable, Selectable}; -#[derive(Queryable, Selectable)] +#[derive(Clone, Queryable, Selectable)] #[diesel(table_name = crate::actualbudget_schema::accounts)] pub struct ActualbudgetAccount { pub id: String, diff --git a/rust/actualbudget_models/src/actualbudget_category.rs b/rust/actualbudget_models/src/actualbudget_category.rs index 8702f19..df12541 100644 --- a/rust/actualbudget_models/src/actualbudget_category.rs +++ b/rust/actualbudget_models/src/actualbudget_category.rs @@ -1,6 +1,6 @@ use diesel::{prelude::Queryable, Selectable}; -#[derive(Queryable, Selectable)] +#[derive(Clone, Queryable, Selectable)] #[diesel(table_name = crate::actualbudget_schema::categories)] pub struct ActualbudgetCategory { pub id: String, diff --git a/rust/actualbudget_queries/README.md b/rust/actualbudget_queries/README.md index d16fa30..69ab0e7 100644 --- a/rust/actualbudget_queries/README.md +++ b/rust/actualbudget_queries/README.md @@ -34,7 +34,6 @@ To re-create the test database from scratch: 'Food', for 8 Aug, Sept, Oct, and Nov 2024 11. In the 'Category transfers' account, add a transaction for 23rd Oct 2024, transferring 120.00 from 'Savings' to 'General' -12. 12. Export the database by going to Settings > Export data 13. Download the resulting ZIP archive 14. Extract `db.sqlite` and `metadata.json` diff --git a/rust/actualbudget_queries/tests/actualbudget_accounts.rs b/rust/actualbudget_queries/tests/actualbudget_accounts.rs index a761c4f..fce5dfb 100644 --- a/rust/actualbudget_queries/tests/actualbudget_accounts.rs +++ b/rust/actualbudget_queries/tests/actualbudget_accounts.rs @@ -1,7 +1,7 @@ mod common; use actualbudget_queries::actualbudget_accounts::{get_all_actualbudget_accounts, get_category_transfers_account}; -use common::DB_URL; +use common::{account_names::ACCOUNT_NAMES, db_url::DB_URL}; use diesel::{Connection, SqliteConnection}; #[test] @@ -12,10 +12,10 @@ fn given_test_database_when_get_all_accounts_then_return_accounts() { assert!(accounts.is_ok()); let accounts = accounts.unwrap(); - assert_eq!(3, accounts.len()); - assert!(accounts.iter().any(|a| a.name == "My bank account")); - assert!(accounts.iter().any(|a| a.name == "My cash account")); - assert!(accounts.iter().any(|a| a.name == "Category transfers")); + assert_eq!(ACCOUNT_NAMES.len(), accounts.len()); + for account_name in ACCOUNT_NAMES { + assert!(accounts.iter().any(|a| a.name.as_str() == *account_name)); + } } #[test] diff --git a/rust/actualbudget_queries/tests/actualbudget_categories.rs b/rust/actualbudget_queries/tests/actualbudget_categories.rs index 5cc619f..1e0682c 100644 --- a/rust/actualbudget_queries/tests/actualbudget_categories.rs +++ b/rust/actualbudget_queries/tests/actualbudget_categories.rs @@ -1,7 +1,7 @@ mod common; use actualbudget_queries::actualbudget_categories::get_all_actualbudget_categories; -use common::DB_URL; +use common::{category_names::CATEGORY_NAMES, db_url::DB_URL}; use diesel::{Connection, SqliteConnection}; #[test] @@ -12,12 +12,8 @@ fn given_test_database_when_get_all_categories_then_return_categories() { assert!(categories.is_ok()); let categories = categories.unwrap(); - assert_eq!(7, categories.len()); - assert!(categories.iter().any(|a| a.name == "Food")); - assert!(categories.iter().any(|a| a.name == "General")); - assert!(categories.iter().any(|a| a.name == "Bills")); - assert!(categories.iter().any(|a| a.name == "Bills (Flexible)")); - assert!(categories.iter().any(|a| a.name == "Savings")); - assert!(categories.iter().any(|a| a.name == "Starting Balances")); - assert!(categories.iter().any(|a| a.name == "Income")); + assert_eq!(CATEGORY_NAMES.len(), categories.len()); + for &category_name in CATEGORY_NAMES { + assert!(categories.iter().any(|a| a.name.as_str() == category_name)); + } } diff --git a/rust/actualbudget_queries/tests/actualbudget_transactions.rs b/rust/actualbudget_queries/tests/actualbudget_transactions.rs index 0321d73..b42d021 100644 --- a/rust/actualbudget_queries/tests/actualbudget_transactions.rs +++ b/rust/actualbudget_queries/tests/actualbudget_transactions.rs @@ -1,18 +1,24 @@ mod common; use actualbudget_queries::actualbudget_transactions::{get_all_actualbudget_transactions, get_first_actualbudget_transaction_date}; -use common::DB_URL; +use common::{db_url::DB_URL, get_reference_data::{get_accounts, get_categories}, transaction_matchers::get_transaction_matchers}; use diesel::{Connection, SqliteConnection}; #[test] fn given_test_database_when_get_all_transactions_then_return_transactions() { let connection = &mut SqliteConnection::establish(DB_URL).unwrap(); + let accounts = get_accounts(connection); + let categories = get_categories(connection); let transactions = get_all_actualbudget_transactions(connection); assert!(transactions.is_ok()); let transactions = transactions.unwrap(); - assert_eq!(15, transactions.len()); + let transaction_matchers = get_transaction_matchers(); + assert_eq!(transaction_matchers.len(), transactions.len()); + for transaction_matcher in transaction_matchers { + assert!(transactions.iter().any(|t| transaction_matcher(t, &accounts, &categories))); + } } #[test] diff --git a/rust/actualbudget_queries/tests/actualbudget_zero_budgets.rs b/rust/actualbudget_queries/tests/actualbudget_zero_budgets.rs index c4e79be..4e9ea19 100644 --- a/rust/actualbudget_queries/tests/actualbudget_zero_budgets.rs +++ b/rust/actualbudget_queries/tests/actualbudget_zero_budgets.rs @@ -1,7 +1,7 @@ mod common; use actualbudget_queries::actualbudget_zero_budgets::get_all_actualbudget_zero_budgets; -use common::DB_URL; +use common::db_url::DB_URL; use diesel::{Connection, SqliteConnection}; #[test] diff --git a/rust/actualbudget_queries/tests/common/account_names.rs b/rust/actualbudget_queries/tests/common/account_names.rs new file mode 100644 index 0000000..1abee4b --- /dev/null +++ b/rust/actualbudget_queries/tests/common/account_names.rs @@ -0,0 +1,17 @@ +pub const ACCOUNT_NAMES: &[&'static str] = &[ + "My bank account", + "My cash account", + "Category transfers", +]; + +pub struct AccountNamesDict { + pub bank_account: &'static str, + pub cash_account: &'static str, + pub transfers_account: &'static str, +} + +pub const ACCOUNT_NAMES_DICT: AccountNamesDict = AccountNamesDict { + bank_account: ACCOUNT_NAMES[0], + cash_account: ACCOUNT_NAMES[1], + transfers_account: ACCOUNT_NAMES[2], +}; diff --git a/rust/actualbudget_queries/tests/common/category_names.rs b/rust/actualbudget_queries/tests/common/category_names.rs new file mode 100644 index 0000000..a2f7d84 --- /dev/null +++ b/rust/actualbudget_queries/tests/common/category_names.rs @@ -0,0 +1,29 @@ +pub const CATEGORY_NAMES: &[&'static str] = &[ + "Food", + "General", + "Bills", + "Bills (Flexible)", + "Savings", + "Starting Balances", + "Income", +]; + +pub struct CategoryNamesDict { + pub food: &'static str, + pub general: &'static str, + pub bills: &'static str, + pub bills_flexible: &'static str, + pub savings: &'static str, + pub starting_balances: &'static str, + pub income: &'static str, +} + +pub const CATEGORY_NAMES_DICT: CategoryNamesDict = CategoryNamesDict { + food: CATEGORY_NAMES[0], + general: CATEGORY_NAMES[1], + bills: CATEGORY_NAMES[2], + bills_flexible: CATEGORY_NAMES[3], + savings: CATEGORY_NAMES[4], + starting_balances: CATEGORY_NAMES[5], + income: CATEGORY_NAMES[6], +}; diff --git a/rust/actualbudget_queries/tests/common.rs b/rust/actualbudget_queries/tests/common/db_url.rs index ff778a8..ff778a8 100644 --- a/rust/actualbudget_queries/tests/common.rs +++ b/rust/actualbudget_queries/tests/common/db_url.rs diff --git a/rust/actualbudget_queries/tests/common/get_reference_data.rs b/rust/actualbudget_queries/tests/common/get_reference_data.rs new file mode 100644 index 0000000..d317a00 --- /dev/null +++ b/rust/actualbudget_queries/tests/common/get_reference_data.rs @@ -0,0 +1,45 @@ +use std::collections::HashMap; + +use actualbudget_models::{actualbudget_account::ActualbudgetAccount, actualbudget_category::ActualbudgetCategory}; +use actualbudget_queries::{actualbudget_accounts::get_all_actualbudget_accounts, actualbudget_categories::get_all_actualbudget_categories, actualbudget_transactions::{get_all_actualbudget_transactions, get_first_actualbudget_transaction_date}}; +use diesel::SqliteConnection; + +use super::{account_names::ACCOUNT_NAMES, category_names::CATEGORY_NAMES}; + +pub fn get_accounts(connection: &mut SqliteConnection) -> HashMap<String, ActualbudgetAccount> { + let accounts = get_all_actualbudget_accounts(connection).expect("failed to get accounts"); + let mut accounts_hash_map = HashMap::new(); + + for &account_name in ACCOUNT_NAMES { + let account = accounts + .clone() + .into_iter() + .find(|a| a.name.as_str() == account_name) + .expect(&format!("failed to get account: {}", account_name)); + accounts_hash_map.insert( + String::from(account_name), + account, + ); + } + + accounts_hash_map +} + +pub fn get_categories(connection: &mut SqliteConnection) -> HashMap<String, ActualbudgetCategory> { + let categories = get_all_actualbudget_categories(connection).expect("failed to get categories"); + let mut categories_hash_map = HashMap::new(); + + for &category_name in CATEGORY_NAMES { + let category = categories + .clone() + .into_iter() + .find(|a| a.name.as_str() == category_name) + .expect(&format!("failed to get category: {}", category_name)); + categories_hash_map.insert( + String::from(category_name), + category, + ); + } + + categories_hash_map +} diff --git a/rust/actualbudget_queries/tests/common/mod.rs b/rust/actualbudget_queries/tests/common/mod.rs new file mode 100644 index 0000000..01112b3 --- /dev/null +++ b/rust/actualbudget_queries/tests/common/mod.rs @@ -0,0 +1,5 @@ +pub mod account_names; +pub mod category_names; +pub mod db_url; +pub mod get_reference_data; +pub mod transaction_matchers; diff --git a/rust/actualbudget_queries/tests/common/transaction_matchers.rs b/rust/actualbudget_queries/tests/common/transaction_matchers.rs new file mode 100644 index 0000000..f245723 --- /dev/null +++ b/rust/actualbudget_queries/tests/common/transaction_matchers.rs @@ -0,0 +1,314 @@ +use std::collections::HashMap; + +use actualbudget_models::{actualbudget_account::ActualbudgetAccount, actualbudget_category::ActualbudgetCategory, actualbudget_transaction::ActualbudgetTransaction}; + +use super::{account_names::ACCOUNT_NAMES_DICT, category_names::CATEGORY_NAMES_DICT}; + +pub fn get_transaction_matchers() -> Vec<Box<dyn Fn(&ActualbudgetTransaction, &HashMap<String, ActualbudgetAccount>, &HashMap<String, ActualbudgetCategory>) -> bool>> { + vec![ + Box::new(|t, a, c| matches_bank_account_initial_transaction(t, a, c)), + Box::new(|t, a, c| matches_cash_account_initial_transaction(t, a, c)), + Box::new(|t, a, c| matches_income_transaction(t, a, c, "08")), + Box::new(|t, a, c| matches_income_transaction(t, a, c, "09")), + Box::new(|t, a, c| matches_income_transaction(t, a, c, "10")), + Box::new(|t, a, c| matches_income_transaction(t, a, c, "11")), + Box::new(|t, a, c| matches_food_transaction(t, a, c, "08")), + Box::new(|t, a, c| matches_food_transaction(t, a, c, "09")), + Box::new(|t, a, c| matches_food_transaction(t, a, c, "10")), + Box::new(|t, a, c| matches_food_transaction(t, a, c, "11")), + Box::new(|t, a, c| matches_category_transfer_parent_transaction(t, a, c)), + Box::new(|t, a, c| matches_category_transfer_child_out_transaction(t, a, c)), + Box::new(|t, a, c| matches_category_transfer_child_in_transaction(t, a, c)), + Box::new(|t, a, c| matches_account_transfer_out_transaction(t, a, c)), + Box::new(|t, a, c| matches_account_transfer_in_transaction(t, a, c)), + ] +} + +fn matches_bank_account_initial_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let bank_account_id = accounts + .get(ACCOUNT_NAMES_DICT.bank_account) + .expect("failed to find bank account") + .id + .clone(); + + let starting_balances_category_id = categories + .get(CATEGORY_NAMES_DICT.starting_balances) + .expect("failed to find starting balances category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id: Some(category_id), + amount: 1200_00, + date, + .. + } => *account_id == bank_account_id + && *category_id == starting_balances_category_id + && date.to_iso() == "2024-08-01", + _ => false, + } +} + +fn matches_cash_account_initial_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let bank_account_id = accounts + .get(ACCOUNT_NAMES_DICT.cash_account) + .expect("failed to find cash account") + .id + .clone(); + + let starting_balances_category_id = categories + .get(CATEGORY_NAMES_DICT.starting_balances) + .expect("failed to find starting balances category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id: Some(category_id), + amount: 45_10, + date, + .. + } => *account_id == bank_account_id + && *category_id == starting_balances_category_id + && date.to_iso() == "2024-08-01", + _ => false, + } +} + +fn matches_income_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, + month: &str, +) -> bool { + let bank_account_id = accounts + .get(ACCOUNT_NAMES_DICT.bank_account) + .expect("failed to find bank account") + .id + .clone(); + + let income_category_id = categories + .get(CATEGORY_NAMES_DICT.income) + .expect("failed to find income category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id, + amount: 846_46, + date, + notes: Some(notes), + .. + } => *account_id == bank_account_id + && *category_id == Some(income_category_id) + && date.to_iso() == format!("2024-{}-21", month) + && *notes == String::from("Salary"), + _ => false, + } +} + +fn matches_food_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, + month: &str, +) -> bool { + let bank_account_id = accounts + .get(ACCOUNT_NAMES_DICT.bank_account) + .expect("failed to find bank account") + .id + .clone(); + + let income_category_id = categories + .get(CATEGORY_NAMES_DICT.food) + .expect("failed to find food category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id, + amount: -200_00, + date, + .. + } => *account_id == bank_account_id + && *category_id == Some(income_category_id) + && date.to_iso() == format!("2024-{}-08", month), + _ => false, + } +} + +fn matches_category_transfer_parent_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + _categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let category_transfers_account_id = accounts + .get(ACCOUNT_NAMES_DICT.transfers_account) + .expect("failed to find transfers account") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: true, + is_child: false, + parent_id: None, + account_id, + category_id: None, + amount: 0, + date, + .. + } => *account_id == category_transfers_account_id + && date.to_iso() == "2024-10-23", + _ => false, + } +} + +fn matches_category_transfer_child_out_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let category_transfers_account_id = accounts + .get(ACCOUNT_NAMES_DICT.transfers_account) + .expect("failed to find transfers account") + .id + .clone(); + + let savings_category_id = categories + .get(CATEGORY_NAMES_DICT.savings) + .expect("failed to find savings category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: true, + parent_id: Some(_), + account_id, + category_id: Some(category_id), + amount: -120_00, + date, + .. + } => *account_id == category_transfers_account_id + && *category_id == savings_category_id + && date.to_iso() == "2024-10-23", + _ => false, + } +} + +fn matches_category_transfer_child_in_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let category_transfers_account_id = accounts + .get(ACCOUNT_NAMES_DICT.transfers_account) + .expect("failed to find transfers account") + .id + .clone(); + + let savings_category_id = categories + .get(CATEGORY_NAMES_DICT.general) + .expect("failed to find general category") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: true, + parent_id: Some(_), + account_id, + category_id: Some(category_id), + amount: 120_00, + date, + .. + } => *account_id == category_transfers_account_id + && *category_id == savings_category_id + && date.to_iso() == "2024-10-23", + _ => false, + } +} + +fn matches_account_transfer_out_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let bank_account_id = accounts + .get(ACCOUNT_NAMES_DICT.bank_account) + .expect("failed to find bank account") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id: None, + amount: -80_00, + date, + .. + } => *account_id == bank_account_id + && date.to_iso() == "2024-09-10", + _ => false, + } +} + +fn matches_account_transfer_in_transaction( + t: &ActualbudgetTransaction, + accounts: &HashMap<String, ActualbudgetAccount>, + categories: &HashMap<String, ActualbudgetCategory>, +) -> bool { + let cash_account_id = accounts + .get(ACCOUNT_NAMES_DICT.cash_account) + .expect("failed to find cash account") + .id + .clone(); + + match t { + ActualbudgetTransaction { + is_parent: false, + is_child: false, + parent_id: None, + account_id, + category_id: None, + amount: 80_00, + date, + .. + } => *account_id == cash_account_id + && date.to_iso() == "2024-09-10", + _ => false, + } +} diff --git a/rust/actualbudget_queries/tests/resources/actualbudget_test_db.sqlite b/rust/actualbudget_queries/tests/resources/actualbudget_test_db.sqlite Binary files differindex e22762f..4146bb0 100644 --- a/rust/actualbudget_queries/tests/resources/actualbudget_test_db.sqlite +++ b/rust/actualbudget_queries/tests/resources/actualbudget_test_db.sqlite diff --git a/rust/actualbudget_queries/tests/resources/actualbudget_test_db_metadata.json b/rust/actualbudget_queries/tests/resources/actualbudget_test_db_metadata.json index c323ad6..4d83f81 100644 --- a/rust/actualbudget_queries/tests/resources/actualbudget_test_db_metadata.json +++ b/rust/actualbudget_queries/tests/resources/actualbudget_test_db_metadata.json @@ -1 +1 @@ -{"id":"My-Finances-1-36ec11a","budgetName":"My Finances 1","userId":"fec9ba17-c260-4f55-8d44-676ef0ec5da9","lastScheduleRun":"2024-11-17","resetClock":true}
\ No newline at end of file +{"id":"My-Finances-1-36ec11a","budgetName":"My Finances 1","userId":"fec9ba17-c260-4f55-8d44-676ef0ec5da9","lastScheduleRun":"2024-11-21","resetClock":true}
\ No newline at end of file |
