Skip to content

Commit a91f79e

Browse files
committed
Extended unit tests
1 parent 7abe45c commit a91f79e

21 files changed

+710
-165
lines changed

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ARG DOCKER_IMAGE=python:3.9-alpine \
2-
USER=nonroot \
3-
GROUP=nonroot \
2+
USER=coolio \
3+
GROUP=coolio \
44
UID=1234 \
55
GID=4321
66

@@ -38,7 +38,7 @@ RUN mkdir -p /home $XDG_CONFIG_HOME \
3838
&& chown -R $USER:$GROUP /home $XDG_CONFIG_HOME \
3939
&& rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
4040

41-
USER nonroot
41+
USER $USER
4242
WORKDIR /home
4343

4444
COPY --from=install-image --chown=$USER:$GROUP /root/.local /home/.local

firefly_cli/__init__.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import sys
22

33
from firefly_cli._version import get_versions
4+
from firefly_cli.api import FireflyAPI
45
from firefly_cli.cli import FireflyPrompt
56
from firefly_cli.parser import Parser
7+
from firefly_cli.transaction import Transaction
68

79
__version__ = get_versions()["version"]
10+
del get_versions
811

912

1013
def _real_main():
1114

1215
if len(sys.argv) > 1:
1316
args, ffargs = Parser.entrypoint().parse_known_args()
1417

15-
try:
16-
if args.version:
17-
FireflyPrompt().onecmd("version")
18-
elif args.help:
19-
FireflyPrompt().onecmd("help")
20-
else:
21-
FireflyPrompt().onecmd(" ".join(ffargs))
22-
except Exception:
23-
raise
18+
if args.version:
19+
FireflyPrompt().onecmd("version")
20+
elif args.help:
21+
FireflyPrompt().onecmd("help")
22+
else:
23+
FireflyPrompt().onecmd(Parser.safe_string(ffargs))
2424
else:
2525
FireflyPrompt().cmdloop()
2626

@@ -30,7 +30,6 @@ def main():
3030
_real_main()
3131
except KeyboardInterrupt:
3232
print("\nInterrupted by user")
33-
except:
33+
except Exception:
3434
# Silence raising exceptions
3535
raise
36-
# pass

firefly_cli/api.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,7 @@ def __init__(self, hostname, auth_token, check_connection=True):
2626
else hostname[:-1]
2727
) # Remove trailing backslash
2828
self.api_url = self.hostname + "/api/v1/" if hostname else self.hostname
29-
self.api_test = self._test_api() if check_connection else False
30-
31-
def _test_api(self):
32-
"""Tests API connection."""
33-
try:
34-
_ = self.get_about_user()
35-
return True
36-
except:
37-
return False
29+
self.api_test = self.test_api_connection() if check_connection else False
3830

3931
def _post(self, endpoint, payload):
4032
"""Handles general POST requests."""
@@ -51,22 +43,31 @@ def _post(self, endpoint, payload):
5143

5244
return response
5345

54-
def _get(self, endpoint, params={}, cache=False):
46+
def _get(self, endpoint, params={}, cache=False, request_raw=False, timeout=2):
5547
"""Handles general GET requests."""
5648

5749
with self.rc.cache_disabled() if not cache else nullcontext():
5850
response = self.rc.get(
5951
"{}{}".format(self.api_url, endpoint),
6052
params=params,
6153
headers=self.headers,
54+
timeout=timeout,
6255
)
6356

64-
return response.json()
57+
return response.json() if not request_raw else response
6558

66-
def get_budgets(self):
67-
"""Returns budgets of the user."""
59+
def test_api_connection(self):
60+
"""Tests API connection."""
61+
try:
62+
_ = self.get_about_user()
63+
return True
64+
except:
65+
return False
6866

69-
return self._get("budgets")
67+
def get_about_user(self):
68+
"""Returns user information."""
69+
70+
return self._get("about/user")
7071

7172
def get_accounts(
7273
self, account_type="asset", cache=False, pagination=False, limit=None
@@ -96,6 +97,11 @@ def get_accounts(
9697

9798
return pages
9899

100+
def get_budgets(self):
101+
"""Returns budgets of the user."""
102+
103+
return self._get("budgets")
104+
99105
def get_autocomplete_accounts(self, limit=20):
100106
"""Returns all user accounts."""
101107
acc_data = self.get_accounts(
@@ -107,15 +113,8 @@ def get_autocomplete_accounts(self, limit=20):
107113

108114
return account_names
109115

110-
def get_about_user(self):
111-
"""Returns user information."""
112-
113-
return self._get("about/user")
114-
115116
def create_transaction(self, transaction: Transaction):
116117
"""Creates a new transaction.
117-
data:
118-
pd.DataFrame
119118
120119
`Amount, Description, Source account, Destination account, Category, Budget`
121120
Example:
@@ -127,18 +126,23 @@ def create_transaction(self, transaction: Transaction):
127126
-> `5, Large Mocha, Cash, , , UCO Bank`
128127
"""
129128

130-
trans_data = transaction.to_dict(remove_none=True, api_safe=True)
129+
payload = self.payload_formatter(transaction)
131130

132-
header = {k: v for k, v in trans_data.items() if k.startswith("header__")}
131+
return self._post(endpoint="transactions", payload=payload)
132+
133+
def payload_formatter(self, transaction):
134+
trans_data = transaction.to_dict(remove_none=True, api_safe=True)
135+
header = {
136+
k.replace("header__", ""): v
137+
for k, v in trans_data.items()
138+
if k.startswith("header__")
139+
}
133140
body = {
134141
"transactions": [
135142
{k: v for k, v in trans_data.items() if not k.startswith("header__")}
136143
]
137144
}
138-
139-
payload = {**header, **body}
140-
141-
return self._post(endpoint="transactions", payload=payload)
145+
return {**header, **body}
142146

143147
@staticmethod
144148
def refresh_api(configs):

firefly_cli/transaction.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def to_dict(
7777

7878
d = self.__dict__
7979

80+
# Remove argparse arguments not related to transaction
81+
if "bypass_prompt" in d:
82+
del d["bypass_prompt"]
83+
8084
if remove_none:
8185
d = {k: v for k, v in d.items() if v is not None}
8286

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@ cattrs==1.10.0
44
certifi==2022.6.15
55
charset-normalizer==2.0.12
66
cmd2==2.4.1
7+
coverage==6.4.2
78
idna==3.3
9+
iniconfig==1.1.1
10+
packaging==21.3
11+
pluggy==1.0.0
12+
py==1.11.0
13+
pyparsing==3.0.9
814
pyperclip==1.8.2
15+
pytest==7.1.2
916
pyxdg==0.28
1017
requests==2.28.0
1118
requests-cache==0.9.4
19+
requests-mock==1.9.3
1220
six==1.16.0
1321
tabulate==0.8.9
22+
tomli==2.0.1
1423
url-normalize==1.4.3
1524
urllib3==1.26.9
1625
wcwidth==0.2.5

test/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
import os
2+
from test.utils import ensure_project_root
23

3-
os.environ["XDG_CONFIG_HOME"] = "../.testout"
4+
os.environ["FIREFLY_CLI_CONFIG"] = "test/test_data/firefly-cli-test.ini"
5+
6+
# Ensure tests are being run from root
7+
ensure_project_root()

test/conftest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
from requests_cache import CachedSession
3+
from requests_mock import Adapter
4+
5+
from firefly_cli import FireflyAPI, FireflyPrompt
6+
from firefly_cli.configs import load_configs
7+
8+
9+
@pytest.fixture
10+
def api(configs):
11+
"""Fixture that provides a CachedSession that will make mock requests where it would normally
12+
make real requests"""
13+
api = FireflyAPI(
14+
hostname=configs["firefly-cli"]["url"],
15+
auth_token=configs["firefly-cli"]["api_token"],
16+
check_connection=False,
17+
)
18+
19+
adapter = Adapter()
20+
21+
session = CachedSession(backend="memory")
22+
session.mount("https://", adapter)
23+
24+
api.rc = session
25+
26+
yield api
27+
28+
29+
@pytest.fixture
30+
def prompt(api):
31+
prompt = FireflyPrompt()
32+
prompt.api = api
33+
return prompt
34+
35+
36+
@pytest.fixture
37+
def configs():
38+
return load_configs()

0 commit comments

Comments
 (0)