Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: align test suits #39

Merged
merged 5 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions cashctrl_api/cashed_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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"]
Expand All @@ -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()

Expand Down Expand Up @@ -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"]
Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion cashctrl_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 8 additions & 25 deletions tests/test_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
37 changes: 13 additions & 24 deletions tests/test_cached_account_categories.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,64 @@
"""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)
time.sleep(1)
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)
Expand Down
57 changes: 19 additions & 38 deletions tests/test_cached_accounts.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,88 @@
"""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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any benefit of using a fixture?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only lines of code are reduced since no need to define a class instance in every test suit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided on the meeting to remove fixture for this case

def cc_client() -> CachedCashCtrlClient:
def cc_client():
return CachedCashCtrlClient()


@pytest.fixture(scope="module")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any benefit of using a fixture?

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)
time.sleep(1)
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)
Expand Down
Loading