From 8a09fa3b38f57d8893a977c2feeabfc1d430b2db Mon Sep 17 00:00:00 2001 From: "LAPTOP-8G8AGL8G\\yotam" Date: Wed, 16 Oct 2024 14:16:22 +0300 Subject: [PATCH 1/3] Docstring getAccounts --- main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.py b/main.py index 9ea28bf..4b1dc15 100644 --- a/main.py +++ b/main.py @@ -367,6 +367,11 @@ def getExpenseTransactionBody(exp: Expense, myshare: ExpenseUser, data: list[str return newTxn def getAccounts(account_type: str="asset") -> list: + """Get accounts from Firefly. + + :param account_type: The type of account + :return: A list of accounts + """ return callApi("accounts/", method="GET", params={"type": account_type}).json()['data'] def cache_account_currency(function): From 454f6e76a59aa6820de03b634792bb318e7cabee Mon Sep 17 00:00:00 2001 From: "LAPTOP-8G8AGL8G\\yotam" Date: Wed, 16 Oct 2024 14:18:59 +0300 Subject: [PATCH 2/3] Add basic tests for account currency code --- tests/test_account_currency.py | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/test_account_currency.py diff --git a/tests/test_account_currency.py b/tests/test_account_currency.py new file mode 100644 index 0000000..a3de839 --- /dev/null +++ b/tests/test_account_currency.py @@ -0,0 +1,78 @@ +import pytest +from unittest.mock import patch, Mock +import importlib +import requests + +# Mock the entire requests library +@pytest.fixture(autouse=True) +def mock_requests(): + with patch('requests.request') as mock: + mock.return_value.json.return_value = {'data': []} + yield mock + +# Reload the main module in each test to ensure a clean slate +def reload_main(): + with patch('main.callApi') as mock_call_api: + mock_call_api.return_value.json.return_value = {'data': []} + import main + importlib.reload(main) + return main + +def test_getAccounts(mock_requests): + mock_requests.return_value.json.return_value = { + 'data': [ + {'attributes': {'name': 'Account1', 'currency_code': 'USD'}}, + {'attributes': {'name': 'Account2', 'currency_code': 'EUR'}} + ] + } + + main = reload_main() + result = main.getAccounts() + + assert len(result) == 2 + assert result[0]['attributes']['name'] == 'Account1' + assert result[1]['attributes']['currency_code'] == 'EUR' + +def test_getAccountCurrencyCode(mock_requests): + mock_requests.return_value.json.return_value = { + 'data': [ + {'attributes': {'name': 'Account1', 'currency_code': 'USD'}}, + {'attributes': {'name': 'Account2', 'currency_code': 'EUR'}} + ] + } + + main = reload_main() + + assert main.getAccountCurrencyCode('Account1') == 'USD' + assert main.getAccountCurrencyCode('Account2') == 'EUR' + + with pytest.raises(ValueError): + main.getAccountCurrencyCode('NonexistentAccount') + +def test_cache_behavior(mock_requests): + call_count = 0 + def mock_request(*args, **kwargs): + nonlocal call_count + call_count += 1 + mock_response = Mock() + mock_response.json.return_value = { + 'data': [ + {'attributes': {'name': 'Account1', 'currency_code': 'USD'}}, + {'attributes': {'name': 'Account2', 'currency_code': 'EUR'}} + ] + } + return mock_response + + mock_requests.side_effect = mock_request + + main = reload_main() + + assert main.getAccountCurrencyCode('Account1') == 'USD' + assert main.getAccountCurrencyCode('Account2') == 'EUR' + assert main.getAccountCurrencyCode('Account1') == 'USD' + + # The request should only be made once due to caching + assert call_count == 1 + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file From 8ffb81222d5a2960978d3fe780331a12e837e46b Mon Sep 17 00:00:00 2001 From: "LAPTOP-8G8AGL8G\\yotam" Date: Fri, 18 Oct 2024 09:57:04 +0300 Subject: [PATCH 3/3] Update main tests to account for caching Mock `requests.request` before importing main to avoid a request on import due to caching. Update tests to reflect changes. Also, fix minor errors in tests. --- tests/test_main.py | 65 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 4fce107..8c9187b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -3,12 +3,20 @@ from splitwise import Splitwise, Expense, User, Comment from splitwise.user import ExpenseUser from unittest.mock import MagicMock, patch +import requests +import importlib + +@pytest.fixture(autouse=True) +def mock_requests(): + with patch('requests.request') as mock: + mock.return_value.json.return_value = {'data': []} + yield mock + + +def load_main(): + import main + return main -from main import ( - formatExpense, getSWUrlForExpense, getDate, getExpensesAfter, - processText, callApi, searchTransactions, getTransactionsAfter, - processExpense, getExpenseTransactionBody -) @pytest.fixture def mock_splitwise(): @@ -47,6 +55,7 @@ def mock_expense_user(): return expense_user def test_formatExpense(mock_expense, mock_expense_user): + formatExpense = load_main().formatExpense result = formatExpense(mock_expense, mock_expense_user) assert "Test Expense" in result assert "USD" in result @@ -55,10 +64,12 @@ def test_formatExpense(mock_expense, mock_expense_user): assert result == "Expense Test Expense for USD 10.00 on 2023-09-10T12:00:00Z" def test_getSWUrlForExpense(mock_expense): + getSWUrlForExpense = load_main().getSWUrlForExpense result = getSWUrlForExpense(mock_expense) assert result == "https://secure.splitwise.com/expenses/67890" def test_getDate(): + getDate = load_main().getDate date_str = "2023-09-10T12:00:00Z" result = getDate(date_str) assert isinstance(result, datetime) @@ -72,10 +83,12 @@ def test_getDate(): ("normal text", []), ]) def test_processText(text, expected): + processText = load_main().processText assert processText(text) == expected @patch('requests.request') def test_callApi(mock_request): + callApi = load_main().callApi mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = {"data": "test"} @@ -87,6 +100,7 @@ def test_callApi(mock_request): @patch('main.callApi') def test_searchTransactions(mock_callApi): + searchTransactions = load_main().searchTransactions # Simulate pagination mock_responses = [ MagicMock(json=lambda: {"data": [{"id": "1"}, {"id": "2"}]}), @@ -102,30 +116,45 @@ def test_searchTransactions(mock_callApi): @patch('main.searchTransactions') def test_getTransactionsAfter(mock_searchTransactions): + getTransactionsAfter = load_main().getTransactionsAfter mock_searchTransactions.return_value = [ {"attributes": {"transactions": [{"external_url": "url1"}]}}, {"attributes": {"transactions": [{"external_url": "url2"}]}} ] - result = getTransactionsAfter(datetime.now() - timedelta(days=1)) + result = getTransactionsAfter(datetime.now().astimezone() - timedelta(days=1)) assert len(result) == 2 assert "url1" in result assert "url2" in result -def test_getExpenseTransactionBody(mock_expense, mock_expense_user): - result = getExpenseTransactionBody(mock_expense, mock_expense_user, ["Dest", "Category", "Desc"]) +@patch('main.getAccountCurrencyCode') +def test_getExpenseTransactionBody(mock_getAccountCurrencyCode, mock_expense, mock_expense_user): + getExpenseTransactionBody = load_main().getExpenseTransactionBody + mock_getAccountCurrencyCode.return_value = "USD" + result = getExpenseTransactionBody(mock_expense, mock_expense_user, ["Dest", "Category", "Desc", "Amex"]) + assert result["source_name"] == "Amex" assert result["destination_name"] == "Dest" assert result["category_name"] == "Category" assert result["amount"] == "10.00" - assert result["currency_code"] == "USD" assert result["description"] == "Desc" @patch('main.callApi') @patch('main.updateTransaction') @patch('main.addTransaction') @patch('main.searchTransactions') -def test_processExpense_update(mock_searchTransactions, mock_addTransaction, mock_updateTransaction, mock_callApi, mock_expense, mock_expense_user): +@patch('main.getAccountCurrencyCode') +def test_processExpense_update(mock_getAccountCurrencyCode, + mock_searchTransactions, + mock_addTransaction, + mock_updateTransaction, + mock_callApi, + mock_expense, + mock_expense_user): + processExpense = load_main().processExpense + getSWUrlForExpense = load_main().getSWUrlForExpense + + mock_getAccountCurrencyCode.return_value = "USD" mock_callApi.return_value = MagicMock(json=lambda: {}) mock_searchTransactions.return_value = [] @@ -133,16 +162,23 @@ def test_processExpense_update(mock_searchTransactions, mock_addTransaction, moc processExpense(datetime.now().astimezone() - timedelta(days=1), ff_txns, mock_expense, mock_expense_user, []) mock_updateTransaction.assert_called_once() mock_addTransaction.assert_not_called() - mock_searchTransactions.assert_not_called() - mock_searchTransactions.assert_called_once() @patch('main.callApi') @patch('main.updateTransaction') @patch('main.addTransaction') @patch('main.searchTransactions') -def test_processExpense_add_new(mock_searchTransactions, mock_addTransaction, mock_updateTransaction, mock_callApi, mock_expense, mock_expense_user): +@patch('main.getAccountCurrencyCode') +def test_processExpense_add_new(mock_getAccountCurrencyCode, + mock_searchTransactions, + mock_addTransaction, + mock_updateTransaction, + mock_callApi, + mock_expense, + mock_expense_user): + processExpense = load_main().processExpense mock_callApi.return_value = MagicMock(json=lambda: {}) mock_searchTransactions.return_value = [] + mock_getAccountCurrencyCode.return_value = "USD" ff_txns = {} processExpense(datetime.now().astimezone() - timedelta(days=1), ff_txns, mock_expense, mock_expense_user, ["Dest", "Category", "Desc"]) @@ -155,6 +191,7 @@ def mock_splitwise(): return MagicMock() def test_getExpensesAfter(mock_splitwise, mock_user): + getExpensesAfter = load_main().getExpensesAfter # Setup mock_expense1 = MagicMock(spec=Expense) mock_expense1.getId.return_value = "1" @@ -246,4 +283,4 @@ def test_getExpensesAfter(mock_splitwise, mock_user): assert all(r[0].getId() != "3" for r in result), "Expense without Firefly data should not be returned" if __name__ == "__main__": - pytest.main() \ No newline at end of file + pytest.main([__file__]) \ No newline at end of file