diff --git a/cashctrl_api/cashed_client.py b/cashctrl_api/cashed_client.py index dcf362b..578aa83 100644 --- a/cashctrl_api/cashed_client.py +++ b/cashctrl_api/cashed_client.py @@ -157,14 +157,12 @@ def account_category_from_id(self, id: int) -> int: path: The path associated with the provided account category id. Raises: - ValueError: If the account category id does not exist or is duplicated. + ValueError: If the account category id does not exist. """ df = self.list_account_categories() result = df.loc[df["id"] == id, "path"] if result.empty: raise ValueError(f"No path found for account category id: {id}") - elif len(result) > 1: - raise ValueError(f"Multiple paths found for account category id: {id}") else: return result.item() @@ -263,8 +261,7 @@ def tax_code_from_id(self, id: int, allow_missing: bool = False) -> Optional[str or None if allow_missing is True and there is no such tax code. Raises: - ValueError: If the tax id does not exist and allow_missing=False, - or if the id is duplicated. + ValueError: If the tax id does not exist and allow_missing=False. """ df = self.list_tax_rates() result = df.loc[df["id"] == id, "name"] @@ -273,8 +270,6 @@ def tax_code_from_id(self, id: int, allow_missing: bool = False) -> Optional[str return None else: raise ValueError(f"No tax code found for id: {id}") - elif len(result) > 1: - raise ValueError(f"Multiple tax codes found for id: {id}") else: return result.item() @@ -333,8 +328,7 @@ def account_from_id(self, id: int, allow_missing: bool = False) -> Optional[int] or None if allow_missing is True and there is no such account. Raises: - ValueError: If the id does not exist and allow_missing=False, - or if the id is duplicated. + ValueError: If the id does not exist and allow_missing=False. """ df = self.list_accounts() result = df.loc[df["id"] == id, "number"] @@ -343,8 +337,6 @@ def account_from_id(self, id: int, allow_missing: bool = False) -> Optional[int] return None else: raise ValueError(f"No account found for id {id}") - elif len(result) > 1: - raise ValueError(f"Multiple accounts found for id {id}") else: return result.item() @@ -428,14 +420,12 @@ def currency_from_id(self, id: int) -> str: str: The currency name associated with the provided id. Raises: - ValueError: If the currency id does not exist or is duplicated. + ValueError: If the currency id does not exist. """ df = self.list_currencies() result = df.loc[df["id"] == id, "text"] if result.empty: raise ValueError(f"No currency found for id: {id}") - elif len(result) > 1: - raise ValueError(f"Multiple currencies found for id: {id}") else: return result.item() diff --git a/cashctrl_api/client.py b/cashctrl_api/client.py index b35fc5d..43f03da 100644 --- a/cashctrl_api/client.py +++ b/cashctrl_api/client.py @@ -8,7 +8,7 @@ import time from typing import Dict, List import pandas as pd -from requests import request, HTTPError, RequestException, Response +from requests import HTTPError, request, RequestException, Response import requests.exceptions import urllib3.exceptions from .constants import ACCOUNT_COLUMNS, CATEGORY_COLUMNS, FILE_COLUMNS, JOURNAL_ENTRIES, TAX_COLUMNS diff --git a/tests/test_api_client.py b/tests/test_api_client.py index ca4c03e..71a25bf 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -4,48 +4,31 @@ import pytest -def test_person_list(): +def test_get_person_list(): cc_client = CashCtrlClient() cc_client.get("person/list.json") -def test_person_flatten_dict(): - """Test creating, reading, and deleting a person.""" - # create +def test_create_read_delete_person(): contact = { "firstName": "Tina", "lastName": "Test", - "addresses": [ - { - "type": "MAIN", - "address": "Teststreet 15", - "zip": "1234", - "city": "Testtown", - } - ], "titleId": 2, } cc_client = CashCtrlClient() response = cc_client.post("person/create.json", data=contact) id = response["insertId"] - # read response = cc_client.get("person/read.json", params={"id": id}) - # delete response = cc_client.post("person/delete.json", params={"ids": id}) -def test_exception_when_not_successful(): - """Test exception handling for unsuccessful API calls.""" +def test_create_category_failed_with_invalid_payload(): cc_client = CashCtrlClient() - - # Error message with filed name (error['field'] is set) - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match='API call failed'): cc_client.post("file/category/create.json") - assert str(e.value) == "API call failed. name: This field cannot be empty." - # Error message without filed name (error['field']=None) - with pytest.raises(Exception) as e: + +def test_create_person_failed_with_invalid_payload(): + cc_client = CashCtrlClient() + with pytest.raises(Exception, match='API call failed'): cc_client.post("person/create.json") - assert str(e.value) == ( - "API call failed. Either first name, last name or company must be set." - ) diff --git a/tests/test_cached_account_categories.py b/tests/test_cached_account_categories.py index 9dd6610..1b8616f 100644 --- a/tests/test_cached_account_categories.py +++ b/tests/test_cached_account_categories.py @@ -1,67 +1,56 @@ """Unit tests for cached account categories.""" import time -from cashctrl_api import CachedCashCtrlClient +from cashctrl_api import CachedCashCtrlClient, CashCtrlClient import pandas as pd import pytest @pytest.fixture(scope="module") -def cc_client() -> CachedCashCtrlClient: +def cc_client(): return CachedCashCtrlClient() @pytest.fixture(scope="module") -def account_categories() -> pd.DataFrame: - """Explicitly call the base class method to circumvent the cache.""" - cc_client = CachedCashCtrlClient() - return cc_client.list_categories("account", include_system=True) +def account_categories(cc_client): + # Explicitly call the base class method to circumvent the cache. + return CashCtrlClient.list_categories(cc_client, "account", include_system=True) -def test_account_categories_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_account_categories_cache_is_none_on_init(cc_client): assert cc_client._account_categories_cache is None assert cc_client._account_categories_cache_time is None -def test_cached_account_categories_same_to_actual( - cc_client: CachedCashCtrlClient, account_categories: pd.DataFrame -) -> None: +def test_cached_account_categories_same_to_actual(cc_client, account_categories): pd.testing.assert_frame_equal(cc_client.list_account_categories(), account_categories) -def test_account_category_from_id( - cc_client: CachedCashCtrlClient, account_categories: pd.DataFrame -) -> None: +def test_account_category_from_id(cc_client, account_categories): assert ( cc_client.account_category_from_id(account_categories["id"].iat[0]) == account_categories["path"].iat[0] ), "Cached account category doesn't correspond actual" -def test_account_category_from_id_invalid_id_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_category_from_id_invalid_id_raises_error(cc_client): with pytest.raises(ValueError, match="No path found for account category id"): cc_client.account_category_from_id(99999999) -def test_account_category_to_id( - cc_client: CachedCashCtrlClient, account_categories: pd.DataFrame -) -> None: +def test_account_category_to_id(cc_client, account_categories): assert ( cc_client.account_category_to_id(account_categories["path"].iat[1]) == account_categories["id"].iat[1] ), "Cached account category id doesn't correspond actual id" -def test_account_category_to_id_with_invalid_account_category_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_category_to_id_with_invalid_account_category_raises_error(cc_client): with pytest.raises(ValueError, match="No id found for account category path"): cc_client.account_category_to_id(99999999) -def test_account_categories_cache_timeout() -> None: +def test_account_categories_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_account_categories() assert not cc_client._is_expired(cc_client._account_categories_cache_time) @@ -69,7 +58,7 @@ def test_account_categories_cache_timeout() -> None: assert cc_client._is_expired(cc_client._account_categories_cache_time) -def test_account_categories_cache_invalidation() -> None: +def test_account_categories_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_account_categories() assert not cc_client._is_expired(cc_client._account_categories_cache_time) diff --git a/tests/test_cached_accounts.py b/tests/test_cached_accounts.py index f58e387..59558fc 100644 --- a/tests/test_cached_accounts.py +++ b/tests/test_cached_accounts.py @@ -1,99 +1,80 @@ """Unit tests for cached accounts.""" import time -from cashctrl_api import CachedCashCtrlClient +from cashctrl_api import CachedCashCtrlClient, CashCtrlClient import pandas as pd import pytest @pytest.fixture(scope="module") -def cc_client() -> CachedCashCtrlClient: +def cc_client(): return CachedCashCtrlClient() @pytest.fixture(scope="module") -def accounts() -> pd.DataFrame: - """Explicitly call the base class method to circumvent the cache.""" - cc_client = CachedCashCtrlClient() - return cc_client.list_accounts() +def accounts(cc_client): + # Explicitly call the base class method to circumvent the cache. + return CashCtrlClient.list_accounts(cc_client) -def test_account_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_account_cache_is_none_on_init(cc_client): assert cc_client._accounts_cache is None assert cc_client._accounts_cache_time is None -def test_cached_accounts_same_to_actual( - cc_client: CachedCashCtrlClient, accounts: pd.DataFrame -) -> None: +def test_cached_accounts_same_to_actual(cc_client, accounts): pd.testing.assert_frame_equal(cc_client.list_accounts(), accounts) -def test_account_from_id( - cc_client: CachedCashCtrlClient, accounts: pd.DataFrame -) -> None: +def test_account_from_id(cc_client, accounts): assert ( cc_client.account_from_id(accounts["id"].iat[0]) == accounts["number"].iat[0] ), "Cached account number doesn't correspond actual number" -def test_account_from_id_invalid_id_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_from_id_invalid_id_raises_error(cc_client): with pytest.raises(ValueError, match="No account found for id"): cc_client.account_from_id(99999999) -def test_account_from_id_invalid_id_returns_none_with_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_from_id_invalid_id_returns_none_with_allowed_missing(cc_client): assert cc_client.account_from_id(99999999, allow_missing=True) is None -def test_account_to_id( - cc_client: CachedCashCtrlClient, accounts: pd.DataFrame -) -> None: +def test_account_to_id(cc_client, accounts): assert ( cc_client.account_to_id(accounts["number"].iat[1]) == accounts["id"].iat[1] ), "Cached account id doesn't correspond actual id" -def test_account_to_id_with_invalid_account_number_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_to_id_with_invalid_account_number_raises_error(cc_client): with pytest.raises(ValueError, match="No id found for account"): cc_client.account_to_id(99999999) -def test_account_to_id_with_invalid_account_number_returns_none_with_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_to_id_with_invalid_account_number_returns_none_with_allowed_missing(cc_client): assert cc_client.account_to_id(99999999, allow_missing=True) is None -def test_account_to_currency( - cc_client: CachedCashCtrlClient, accounts: pd.DataFrame -) -> None: +def test_account_to_currency(cc_client, accounts): assert ( cc_client.account_to_currency(accounts["number"].iat[1]) == accounts["currencyCode"].iat[1] ), "Cached account currency doesn't correspond actual currency" -def test_account_to_currency_with_invalid_account_number_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_account_to_currency_with_invalid_account_number_raises_error(cc_client): with pytest.raises(ValueError, match="No currency found for account"): cc_client.account_to_currency(99999999) def test_account_to_currency_with_invalid_account_number_returns_none_with_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: + cc_client +): assert cc_client.account_to_currency(99999999, allow_missing=True) is None -def test_account_cache_timeout() -> None: +def test_account_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_accounts() assert not cc_client._is_expired(cc_client._accounts_cache_time) @@ -101,7 +82,7 @@ def test_account_cache_timeout() -> None: assert cc_client._is_expired(cc_client._accounts_cache_time) -def test_account_cache_invalidation() -> None: +def test_account_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_accounts() assert not cc_client._is_expired(cc_client._accounts_cache_time) diff --git a/tests/test_cached_currencies.py b/tests/test_cached_currencies.py index 2fa77af..29c3902 100644 --- a/tests/test_cached_currencies.py +++ b/tests/test_cached_currencies.py @@ -7,12 +7,12 @@ @pytest.fixture(scope="module") -def cc_client() -> CachedCashCtrlClient: +def cc_client(): return CachedCashCtrlClient() @pytest.fixture(scope="module") -def currencies() -> pd.DataFrame: +def currencies(): # TODO: After implementing #29 (Type-Consistent list_currencies Method), # change below lines to: # Explicitly call the base class method to circumvent the cache @@ -22,48 +22,38 @@ def currencies() -> pd.DataFrame: return pd.DataFrame(cc_client.get("currency/list.json")["data"]) -def test_currencies_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_currencies_cache_is_none_on_init(cc_client): assert cc_client._currencies_cache is None assert cc_client._currencies_cache_time is None -def test_cached_currencies_same_to_actual( - cc_client: CachedCashCtrlClient, currencies: pd.DataFrame -) -> None: +def test_cached_currencies_same_to_actual(cc_client, currencies): pd.testing.assert_frame_equal(cc_client.list_currencies(), currencies) -def test_currency_from_id( - cc_client: CachedCashCtrlClient, currencies: pd.DataFrame -) -> None: +def test_currency_from_id(cc_client, currencies): assert ( cc_client.currency_from_id(currencies["id"].iat[0]) == currencies["text"].iat[0] ), "Cached currency doesn't correspond actual" -def test_currency_from_id_invalid_id_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_currency_from_id_invalid_id_raises_error(cc_client): with pytest.raises(ValueError, match="No currency found for id"): cc_client.currency_from_id(99999999) -def test_currency_to_id( - cc_client: CachedCashCtrlClient, currencies: pd.DataFrame -) -> None: +def test_currency_to_id(cc_client, currencies): assert ( cc_client.currency_to_id(currencies["text"].iat[1]) == currencies["id"].iat[1] ), "Cached currency id doesn't correspond actual id" -def test_currency_to_id_with_invalid_currency_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_currency_to_id_with_invalid_currency_raises_error(cc_client): with pytest.raises(ValueError, match="No id found for currency"): cc_client.currency_to_id(99999999) -def test_currencies_cache_timeout() -> None: +def test_currencies_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_currencies() assert not cc_client._is_expired(cc_client._currencies_cache_time) @@ -71,7 +61,7 @@ def test_currencies_cache_timeout() -> None: assert cc_client._is_expired(cc_client._currencies_cache_time) -def test_currencies_cache_invalidation() -> None: +def test_currencies_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_currencies() assert not cc_client._is_expired(cc_client._currencies_cache_time) diff --git a/tests/test_cached_files.py b/tests/test_cached_files.py index dc3603a..3474ce7 100644 --- a/tests/test_cached_files.py +++ b/tests/test_cached_files.py @@ -7,14 +7,9 @@ @pytest.fixture(scope="module") -def tmp_path_for_module(tmp_path_factory) -> str: - return tmp_path_factory.mktemp("temp") - - -@pytest.fixture(scope="module") -def mock_directory(tmp_path_for_module) -> str: +def mock_directory(tmp_path_factory): """Create a temporary directory, populate with files and folders.""" - tmp_path = tmp_path_for_module + tmp_path = tmp_path_factory.mktemp("temp") (tmp_path / "file1.txt").write_text("This is a text file.") (tmp_path / "file2.log").write_text("Log content here.") subdir = tmp_path / "subdir" @@ -28,7 +23,7 @@ def mock_directory(tmp_path_for_module) -> str: @pytest.fixture(scope="module") -def cc_client(mock_directory) -> CachedCashCtrlClient: +def cc_client(mock_directory): """Create a CachedCashCtrlClient, populate with files and folders.""" cc_client = CachedCashCtrlClient() initial_files = cc_client.list_files() @@ -48,33 +43,27 @@ def cc_client(mock_directory) -> CachedCashCtrlClient: @pytest.fixture(scope="module") -def files(cc_client: CachedCashCtrlClient) -> pd.DataFrame: - """Explicitly call the base class method to circumvent the cache.""" +def files(cc_client): + # Explicitly call the base class method to circumvent the cache. return CashCtrlClient.list_files(cc_client) -def test_files_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_files_cache_is_none_on_init(cc_client): assert cc_client._files_cache is None assert cc_client._files_cache_time is None -def test_cached_files_same_to_actual( - cc_client: CachedCashCtrlClient, files: pd.DataFrame -) -> None: +def test_cached_files_same_to_actual(cc_client, files): pd.testing.assert_frame_equal(cc_client.list_files(), files) -def test_file_id_to_path( - cc_client: CachedCashCtrlClient, files: pd.DataFrame -) -> None: +def test_file_id_to_path(cc_client, files): assert ( cc_client.file_id_to_path(files["id"].iat[0]) == files["path"].iat[0] ), "Cached file path doesn't correspond actual" -def test_file_id_to_nested_path( - cc_client: CachedCashCtrlClient, files: pd.DataFrame -) -> None: +def test_file_id_to_nested_path(cc_client, files): path = "/nested/subdir/file4.txt" id = files.loc[files["path"] == path, "id"].item() assert cc_client.file_id_to_path(id) == path, ( @@ -82,22 +71,16 @@ def test_file_id_to_nested_path( ) -def test_file_id_to_path_invalid_id_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_file_id_to_path_invalid_id_raises_error(cc_client): with pytest.raises(ValueError, match="No path found for id"): cc_client.file_id_to_path(99999999) -def test_file_id_to_path_invalid_id_returns_none_when_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_file_id_to_path_invalid_id_returns_none_when_allowed_missing(cc_client): assert cc_client.file_id_to_path(99999999, allow_missing=True) is None -def test_file_path_to_id( - cc_client: CachedCashCtrlClient, files: pd.DataFrame -) -> None: +def test_file_path_to_id(cc_client, files): path = "/nested/subdir/file4.txt" id = files.loc[files["path"] == path, "id"].item() assert cc_client.file_path_to_id(path) == id, ( @@ -105,28 +88,22 @@ def test_file_path_to_id( ) -def test_nested_file_path_to_id( - cc_client: CachedCashCtrlClient, files: pd.DataFrame -) -> None: +def test_nested_file_path_to_id(cc_client, files): assert ( cc_client.file_path_to_id(files["path"].iat[0]) == files["id"].iat[0] ), "Cached file id doesn't correspond actual id" -def test_file_path_to_id_with_invalid_path_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_file_path_to_id_with_invalid_path_raises_error(cc_client): with pytest.raises(ValueError, match="No id found for path"): cc_client.file_path_to_id(99999999) -def test_file_path_to_id_with_invalid_returns_none_when_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_file_path_to_id_with_invalid_returns_none_when_allowed_missing(cc_client): assert cc_client.file_path_to_id(99999999, allow_missing=True) is None -def test_files_cache_timeout() -> None: +def test_files_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_files() assert not cc_client._is_expired(cc_client._files_cache_time) @@ -134,7 +111,7 @@ def test_files_cache_timeout() -> None: assert cc_client._is_expired(cc_client._files_cache_time) -def test_files_cache_invalidation() -> None: +def test_files_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_files() assert not cc_client._is_expired(cc_client._files_cache_time) diff --git a/tests/test_cached_journal_entries.py b/tests/test_cached_journal_entries.py index d5fece9..3aa7711 100644 --- a/tests/test_cached_journal_entries.py +++ b/tests/test_cached_journal_entries.py @@ -1,7 +1,7 @@ """Unit tests for cached journal entries.""" import time -from cashctrl_api import CachedCashCtrlClient +from cashctrl_api import CachedCashCtrlClient, CashCtrlClient import pandas as pd import pytest @@ -12,23 +12,21 @@ def cc_client() -> CachedCashCtrlClient: @pytest.fixture(scope="module") -def journal_entries(cc_client: CachedCashCtrlClient) -> pd.DataFrame: - """Explicitly call the base class method to circumvent the cache.""" - return cc_client.list_journal_entries() +def journal_entries(cc_client): + # Explicitly call the base class method to circumvent the cache. + return CashCtrlClient.list_journal_entries(cc_client) -def test_journal_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_journal_cache_is_none_on_init(cc_client): assert cc_client._journal_cache is None assert cc_client._journal_cache_time is None -def test_cached_journal_entries_same_to_actual( - cc_client: CachedCashCtrlClient, journal_entries: pd.DataFrame -) -> None: +def test_cached_journal_entries_same_to_actual(cc_client, journal_entries): pd.testing.assert_frame_equal(cc_client.list_journal_entries(), journal_entries) -def test_journal_cache_timeout() -> None: +def test_journal_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_journal_entries() assert not cc_client._is_expired(cc_client._journal_cache_time) @@ -36,7 +34,7 @@ def test_journal_cache_timeout() -> None: assert cc_client._is_expired(cc_client._journal_cache_time) -def test_journal_cache_invalidation() -> None: +def test_journal_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_journal_entries() assert not cc_client._is_expired(cc_client._journal_cache_time) diff --git a/tests/test_cached_tax_rates.py b/tests/test_cached_tax_rates.py index 9fd9e2b..5021f0c 100644 --- a/tests/test_cached_tax_rates.py +++ b/tests/test_cached_tax_rates.py @@ -1,7 +1,7 @@ """Unit tests for cached tax codes.""" import time -from cashctrl_api import CachedCashCtrlClient +from cashctrl_api import CachedCashCtrlClient, CashCtrlClient import pandas as pd import pytest @@ -12,65 +12,51 @@ def cc_client() -> CachedCashCtrlClient: @pytest.fixture(scope="module") -def tax_rates(cc_client: CachedCashCtrlClient) -> pd.DataFrame: - """Explicitly call the base class method to circumvent the cache.""" - return cc_client.list_tax_rates() +def tax_rates(cc_client): + # Explicitly call the base class method to circumvent the cache. + return CashCtrlClient.list_tax_rates(cc_client) -def test_tax_rates_cache_is_none_on_init(cc_client: CachedCashCtrlClient) -> None: +def test_tax_rates_cache_is_none_on_init(cc_client): assert cc_client._tax_rates_cache is None assert cc_client._tax_rates_cache_time is None -def test_cached_tax_codes_same_to_actual( - cc_client: CachedCashCtrlClient, tax_rates: pd.DataFrame -) -> None: +def test_cached_tax_codes_same_to_actual(cc_client, tax_rates): pd.testing.assert_frame_equal(cc_client.list_tax_rates(), tax_rates) -def test_tax_code_from_id( - cc_client: CachedCashCtrlClient, tax_rates: pd.DataFrame -) -> None: +def test_tax_code_from_id(cc_client, tax_rates): assert ( cc_client.tax_code_from_id(tax_rates["id"].iat[0]) == tax_rates["name"].iat[0] ), "Cached tax code name doesn't correspond actual name" -def test_tax_code_from_id_invalid_id_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_tax_code_from_id_invalid_id_raises_error(cc_client): with pytest.raises(ValueError, match="No tax code found for id"): cc_client.tax_code_from_id(99999999) -def test_tax_code_from_id_invalid_id_returns_none_with_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_tax_code_from_id_invalid_id_returns_none_with_allowed_missing(cc_client): assert cc_client.tax_code_from_id(99999999, allow_missing=True) is None -def test_tax_code_to_id( - cc_client: CachedCashCtrlClient, tax_rates: pd.DataFrame -) -> None: +def test_tax_code_to_id(cc_client, tax_rates): assert ( cc_client.tax_code_to_id(tax_rates["name"].iat[1]) == tax_rates["id"].iat[1] ), "Cached tax code id doesn't correspond actual id" -def test_tax_code_to_id_with_invalid_tax_code_raises_error( - cc_client: CachedCashCtrlClient -) -> None: +def test_tax_code_to_id_with_invalid_tax_code_raises_error(cc_client): with pytest.raises(ValueError, match="No id found for tax code"): cc_client.tax_code_to_id(99999999) -def test_tax_code_to_id_with_invalid_tax_code_returns_none_with_allowed_missing( - cc_client: CachedCashCtrlClient -) -> None: +def test_tax_code_to_id_with_invalid_tax_code_returns_none_with_allowed_missing(cc_client): assert cc_client.tax_code_to_id(99999999, allow_missing=True) is None -def test_tax_rates_cache_timeout() -> None: +def test_tax_rates_cache_timeout(): cc_client = CachedCashCtrlClient(cache_timeout=1) cc_client.list_tax_rates() assert not cc_client._is_expired(cc_client._tax_rates_cache_time) @@ -78,7 +64,7 @@ def test_tax_rates_cache_timeout() -> None: assert cc_client._is_expired(cc_client._tax_rates_cache_time) -def test_tax_rates_cache_invalidation() -> None: +def test_tax_rates_cache_invalidation(): cc_client = CachedCashCtrlClient() cc_client.list_tax_rates() assert not cc_client._is_expired(cc_client._tax_rates_cache_time) diff --git a/tests/test_categories.py b/tests/test_categories.py index ca8ce21..e4c10a2 100644 --- a/tests/test_categories.py +++ b/tests/test_categories.py @@ -27,8 +27,7 @@ } -def test_initial_category_creation() -> None: - """Test that categories are correctly created initially.""" +def test_correct_initial_category_creation(): cc_client = CashCtrlClient() cc_client.update_categories("file", target=categories) remote_categories = cc_client.list_categories("file") @@ -37,8 +36,7 @@ def test_initial_category_creation() -> None: ), "Remote categories do not match initial categories" -def test_category_addition() -> None: - """Test that new categories are added correctly (delete=False).""" +def test_update_category_with_delete_false_should_create_categories(): cc_client = CashCtrlClient() cc_client.update_categories("file", target=more_categories, delete=False) remote_categories = cc_client.list_categories("file") @@ -48,8 +46,7 @@ def test_category_addition() -> None: @pytest.mark.skip(reason="One category on test account can not be deleted.") -def test_category_deletion() -> None: - """Test that categories are deleted when specified.""" +def test_update_category_with_delete_true_should_delete_categories(): cc_client = CashCtrlClient() cc_client.update_categories("file", target=more_categories, delete=True) remote_categories = cc_client.list_categories("file") @@ -60,45 +57,39 @@ def test_category_deletion() -> None: @pytest.mark.skip(reason="One category on test account can not be deleted.") -def test_category_removal() -> None: - """Test that all categories are deleted when specified.""" +def test_update_category_should_remove_categories_with_empty_target_and_delete_true(): cc_client = CashCtrlClient() cc_client.update_categories("file", target=[], delete=True) remote_categories = cc_client.list_categories("file") assert len(remote_categories) == 0, "Some remote categories remain." -def test_invalid_path() -> None: - """Test the system's response to invalid category paths.""" +def test_invalid_path_raises_error(): invalid_categories = ["not/a/valid/path", ""] cc_client = CashCtrlClient() with pytest.raises(KeyError): cc_client.update_categories("file", target=[invalid_categories[0]]) -def test_update_account_categories_with_list() -> None: - """Test that should get error while updating account categories with target as a list type.""" +def test_update_account_categories_with_list_raises_error(): cc_client = CashCtrlClient() with pytest.raises(ValueError): cc_client.update_categories("account", target=categories) -def test_update_file_categories_with_dict() -> None: - """Test that should get error while updating file categories with target as a dict type.""" +def test_update_file_categories_with_dict_raises_error(): cc_client = CashCtrlClient() with pytest.raises(ValueError): cc_client.update_categories("file", target=account_categories) -def test_update_file_categories_raises_error_when_creating_account_root_node() -> None: - """Test that attempting to create a new root node in account categories raises an error.""" +def test_update_file_categories_raises_error_when_creating_account_root_node(): cc_client = CashCtrlClient() with pytest.raises(ValueError, match="Cannot create new root node"): cc_client.update_categories("account", target={"/new_root_node": 42}) -def test_account_category_update() -> None: - """Test update_categories for accounts and then restores initial state.""" +def test_account_category_update(): cc_client = CashCtrlClient() initial_categories = cc_client.list_categories("account", include_system=True) @@ -140,7 +131,7 @@ def test_account_category_update() -> None: assert updated == initial_paths, "Initial categories were not restored" -def test_account_category_delete_root_category_ignore_account_root_nodes() -> None: +def test_account_category_delete_root_category_ignore_account_root_nodes(): """Test that attempting to delete a root account category raises an error. unless ignore_account_root_nodes=True. """ @@ -153,10 +144,7 @@ def test_account_category_delete_root_category_ignore_account_root_nodes() -> No # indirectly tested in test_mirror_accounts() the cashctrl_ledger package. -def test_account_category_update_root_category_ignore_account_root_nodes() -> None: - """Test that attempting to update a root account category raises an error - unless ignore_account_root_nodes=True. - """ +def test_account_category_update_root_category_ignore_account_root_nodes(): cc_client = CashCtrlClient() categories = cc_client.list_categories("account", include_system=True) balance_category = categories[categories["path"] == "/Balance"] @@ -179,8 +167,7 @@ def test_account_category_update_root_category_ignore_account_root_nodes() -> No pd.testing.assert_frame_equal(updated_categories, categories) -def test_account_category_create_new_root_category_raise_error() -> None: - """Test that should raise an error trying create a new root account category.""" +def test_account_category_create_new_root_category_raises_error(): cc_client = CashCtrlClient() categories = cc_client.list_categories("account", include_system=True) target = categories.set_index("path")["number"].to_dict() diff --git a/tests/test_enforce_dtypes.py b/tests/test_enforce_dtypes.py index 6478ed7..0bd5871 100644 --- a/tests/test_enforce_dtypes.py +++ b/tests/test_enforce_dtypes.py @@ -6,16 +6,14 @@ import pytest -def test_empty_input() -> None: - """Test input is None.""" +def test_none_as_input(): result = enforce_dtypes(None) assert isinstance(result, pd.DataFrame) assert result.empty assert list(result.columns) == [] -def test_required_columns_added() -> None: - """Test adding required columns.""" +def test_should_add_required_columns(): required_columns = { "Column1": "int64", "Column2": "float64", @@ -28,8 +26,7 @@ def test_required_columns_added() -> None: assert result["Column2"].dtype == "float64" -def test_optional_columns_added() -> None: - """Test adding optional columns.""" +def test_should_add_optional_columns(): required_columns = {"Column1": "int64"} optional_columns = {"Column2": "float64"} df = pd.DataFrame({"Column1": [1]}) @@ -42,16 +39,14 @@ def test_optional_columns_added() -> None: assert result["Column2"].dtype == "float64" -def test_missing_required_columns() -> None: - """Test exception when missing required columns.""" +def test_missing_required_columns_raises_error(): df = pd.DataFrame({"Column3": ["data"]}) required_columns = {"Column1": "int64"} with pytest.raises(ValueError): enforce_dtypes(data=df, required=required_columns) -def test_keep_extra_columns() -> None: - """Test dropping columns not in the required or optional lists.""" +def test_drop_extra_columns_with_keep_extra_columns_false(): df = pd.DataFrame({"Column1": [1], "Column3": ["extra"]}) required_columns = {"Column1": "int64"} result = enforce_dtypes( @@ -61,6 +56,11 @@ def test_keep_extra_columns() -> None: ) assert "Column3" not in result.columns assert list(result.columns) == ["Column1"] + + +def test_keep_extra_columns_with_keep_extra_columns_true(): + df = pd.DataFrame({"Column1": [1], "Column3": ["extra"]}) + required_columns = {"Column1": "int64"} result = enforce_dtypes( data=df, required=required_columns, @@ -70,10 +70,7 @@ def test_keep_extra_columns() -> None: assert list(result.columns) == ["Column1", "Column3"] -def test_dtype_conversion() -> None: - """Test dtype conversion where original vector has string elements - interpretable as floats or integers. - """ +def test_should_convert_dtype_from_str_to_int_and_float(): required_columns = {"Column1": "int64", "Column2": "float64"} optional_columns = {"Column3": "object"} df = pd.DataFrame({"Column1": ["1", "2", "3"], "Column2": ["1.1", "2.2", "3.3"]}) @@ -86,8 +83,7 @@ def test_dtype_conversion() -> None: assert result["Column2"].dtype == "float64" -def test_invalid_dtype_conversion() -> None: - """Test exception when dtype conversion is invalid.""" +def test_invalid_dtype_conversion_raises_error(): df = pd.DataFrame({"Column1": ["invalid"], "Column2": ["data"]}) required_columns = {"Column1": "int64", "Column2": "float64"} with pytest.raises( @@ -96,8 +92,7 @@ def test_invalid_dtype_conversion() -> None: enforce_dtypes(data=df, required=required_columns) -def test_datetime_without_timezone() -> None: - """Test adding a datetime column without timezone.""" +def test_add_datetime_column_without_timezone(): df = pd.DataFrame({"Column1": [1, 2], "Date": ["2021-01-01", "2022-02-02"]}) required_columns = {"Column1": "int64", "Date": "datetime64[ns]"} result = enforce_dtypes(data=df, required=required_columns) @@ -105,8 +100,7 @@ def test_datetime_without_timezone() -> None: assert result["Date"].dtype == "datetime64[ns]" -def test_datetime_with_timezone() -> None: - """Test adding a datetime column with timezone using zoneinfo.""" +def test_add_datetime_column_with_timezone_using_zoneinfo(): df = pd.DataFrame({"Column1": [1], "Date": ["2021-01-01T12:00:00"]}) required_columns = {"Column1": "int64", "Date": "datetime64[ns, US/Eastern]"} result = enforce_dtypes(data=df, required=required_columns) @@ -117,8 +111,7 @@ def test_datetime_with_timezone() -> None: assert result["Date"].dtype == "datetime64[ns, US/Eastern]" -def test_datetime_conversion_fail() -> None: - """Test failure in converting invalid datetime format.""" +def test_invalid_datetime_conversion_raises_error(): df = pd.DataFrame({"Column1": [1], "Date": ["not a date"]}) required_columns = {"Column1": "int64", "Date": "datetime64[ns]"} with pytest.raises( diff --git a/tests/test_file_up_and_download.py b/tests/test_file_up_and_download.py index 692371f..2ebee21 100644 --- a/tests/test_file_up_and_download.py +++ b/tests/test_file_up_and_download.py @@ -11,8 +11,7 @@ def random_word(length: int) -> str: return "".join(random.choice(letters) for _ in range(length)) -def test_upload_file_and_download(tmp_path) -> None: - """Test uploading and downloading files.""" +def test_upload_file_and_download(tmp_path): cc_client = CashCtrlClient() # Create a temporary file with random content to upload diff --git a/tests/test_list_directory.py b/tests/test_list_directory.py index 99a5280..07df475 100644 --- a/tests/test_list_directory.py +++ b/tests/test_list_directory.py @@ -7,55 +7,46 @@ @pytest.fixture -def mock_directory(tmp_path: Path) -> Path: - """Create a mock directory with various files and subdirectories for testing.""" - # For debugging, set: tmp_path = Path("temp_test"); tmp_path.mkdir() +def mock_directory(tmp_path: Path): + """Create a temporary directory, populate with files and folders.""" (tmp_path / "file1.txt").write_text("This is a text file.") (tmp_path / "file2.log").write_text("Log content here.") (tmp_path / ".hiddenfile").write_text("Secret content.") - subdir = tmp_path / "subdir" subdir.mkdir() (subdir / "file3.txt").write_text("Another text file in a subdirectory.") - hidden_dir = tmp_path / ".hiddendir" hidden_dir.mkdir() (hidden_dir / "hidden_file.txt").write_text("Hidden file in hidden dir.") - return tmp_path -def test_non_recursive_listing(mock_directory: Path) -> None: - """Test non-recursive listing of directory contents.""" +def test_directory_non_recursive_listing(mock_directory): result_df = list_directory(mock_directory) expected_files = {"file1.txt", "file2.log", "subdir"} assert set(result_df["path"]) == expected_files -def test_exclude_directories(mock_directory: Path) -> None: - """Test excluding directories from the listing.""" +def test_exclude_directories_with_exclude_dirs_true(mock_directory): result_df = list_directory(mock_directory, exclude_dirs=True) expected_files = {"file1.txt", "file2.log"} assert set(result_df["path"]) == expected_files -def test_recursive_listing(mock_directory: Path) -> None: - """Test recursive listing of directory contents.""" +def test_directory_recursive_listing(mock_directory): result_df = list_directory(mock_directory, recursive=True) expected_files = {"file1.txt", "file2.log", "subdir", "subdir/file3.txt"} assert set(result_df["path"]) == expected_files -def test_recursive_exclude_directories(mock_directory: Path) -> None: - """Test recursive listing excluding directories.""" +def test_recursive_listing_exclude_directories(mock_directory): result_df = list_directory(mock_directory, recursive=True, exclude_dirs=True) assert all( not Path(mock_directory / p).is_dir() for p in result_df["path"] ) -def test_include_hidden_files(mock_directory: Path) -> None: - """Test including hidden files in the listing.""" +def test_listing_include_hidden_files(mock_directory): result_df = list_directory( mock_directory, recursive=True, include_hidden=True ) @@ -63,8 +54,7 @@ def test_include_hidden_files(mock_directory: Path) -> None: assert ".hiddendir/hidden_file.txt" in set(result_df["path"]) -def test_exclude_hidden_files(mock_directory: Path) -> None: - """Test excluding hidden files from the listing.""" +def test_listing_exclude_hidden_files(mock_directory): result_df = list_directory( mock_directory, recursive=True, include_hidden=False ) @@ -72,14 +62,12 @@ def test_exclude_hidden_files(mock_directory: Path) -> None: assert ".hiddendir/hidden_file.txt" not in set(result_df["path"]) -def test_non_existent_directory() -> None: - """Test listing a non-existent directory.""" +def test_non_existent_directory_raises_error(): with pytest.raises(FileNotFoundError): list_directory("nonexistent_directory") -def test_empty_directory(tmp_path: Path) -> None: - """Test listing an empty directory.""" +def test_listing_empty_directory(tmp_path): result_df = list_directory(tmp_path) assert isinstance(result_df, pd.DataFrame), ( "The result should be a pd.DataFrame." diff --git a/tests/test_listings.py b/tests/test_listings.py index ac343a2..e245468 100644 --- a/tests/test_listings.py +++ b/tests/test_listings.py @@ -4,10 +4,7 @@ import pandas as pd -def test_list_tax_rates_to_have_expected_columns() -> None: - """Test that the DataFrame returned by CashCtrlClient contains all expected columns - with the correct data types as specified in TAX_COLUMNS. - """ +def test_list_tax_rates_to_have_expected_columns_and_dtypes(): # Given: CashCtrlClient returns a DataFrame of tax rates cc_client = CashCtrlClient() tax_rates = cc_client.list_tax_rates() @@ -26,10 +23,7 @@ def test_list_tax_rates_to_have_expected_columns() -> None: ) -def test_list_accounts_to_have_expected_columns() -> None: - """Test that the DataFrame returned by CashCtrlClient's list_accounts contains all expected - columns with the correct data types as specified in ACCOUNT_COLUMNS. - """ +def test_list_accounts_to_have_expected_columns_and_dtypes(): # Create the CashCtrlClient object and fetch the DataFrame cc_client = CashCtrlClient() accounts = cc_client.list_accounts() @@ -48,10 +42,7 @@ def test_list_accounts_to_have_expected_columns() -> None: ) -def test_list_journal_entries_to_have_columns() -> None: - """Test that the DataFrame returned by CashCtrlClient's list_journal_entries contains - all expected columns with the correct data types as specified in JOURNAL_ENTRIES. - """ +def test_list_journal_entries_to_have_columns_and_dtypes(): # Create the CashCtrlClient object and fetch the DataFrame cc_client = CashCtrlClient() journal_entries = cc_client.list_journal_entries() diff --git a/tests/test_mirror_directory.py b/tests/test_mirror_directory.py index fec907a..cc64c54 100644 --- a/tests/test_mirror_directory.py +++ b/tests/test_mirror_directory.py @@ -33,8 +33,7 @@ def remote_content(cc_client: CashCtrlClient, file_id: int) -> str: return response.content.decode("utf-8") -def test_directory_mirroring(mock_directory: Path) -> None: - """Test that the directory mirroring correctly syncs local files to remote.""" +def test_directory_mirroring_sync_local_files_to_remote(mock_directory): cc_client = CashCtrlClient() # Mirror directory and check file presence @@ -105,8 +104,7 @@ def test_directory_mirroring(mock_directory: Path) -> None: ) -def test_mirror_empty_directory(tmp_path: Path) -> None: - """Ensure that mirroring an empty directory removes all remote files.""" +def test_mirror_empty_directory_removes_all_remote_files(tmp_path): cc_client = CashCtrlClient() cc_client.mirror_directory(tmp_path, delete_files=True) assert cc_client.list_files().empty, (