Skip to content

Commit b589699

Browse files
committed
feat(time): wire up harvest -> toggl converter
1 parent b393079 commit b589699

File tree

8 files changed

+149
-17
lines changed

8 files changed

+149
-17
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
from argparse import Namespace
22

3+
import pandas as pd
4+
35
from compiler_admin import RESULT_SUCCESS
6+
from compiler_admin.services.toggl import INPUT_COLUMNS, convert_to_harvest
7+
8+
9+
def _get_source_converter(source):
10+
columns = pd.read_csv(source, nrows=0).columns.tolist()
11+
12+
if set(INPUT_COLUMNS) < set(columns):
13+
return convert_to_harvest
14+
else:
15+
raise NotImplementedError("A converter for the given source data does not exist.")
416

517

618
def convert(args: Namespace, *extras):
19+
converter = _get_source_converter(args.input)
20+
21+
converter(args.input, args.output, args.client)
22+
723
return RESULT_SUCCESS

compiler_admin/main.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,14 @@ def main(argv=None):
5252
time_cmd.set_defaults(func=time)
5353
time_subcmds = time_cmd.add_subparsers("subcommand", help="The time command to run.")
5454

55-
add_sub_cmd(time_subcmds, "convert", help="Convert a time report from one format into another.")
55+
time_convert = add_sub_cmd(time_subcmds, "convert", help="Convert a time report from one format into another.")
56+
time_convert.add_argument(
57+
"--input", default=sys.stdin, help="The path to the source data for conversion. Defaults to stdin."
58+
)
59+
time_convert.add_argument(
60+
"--output", default=sys.stdout, help="The path to the file where converted data should be written. Defaults to stdout."
61+
)
62+
time_convert.add_argument("--client", default=None, help="The name of the client to use in converted data.")
5663

5764
user_cmd = add_sub_cmd(cmd_parsers, "user", help="Work with users in the Compiler org.")
5865
user_cmd.set_defaults(func=user)

tests/commands/time/__init__.py

Whitespace-only changes.

tests/commands/time/test_convert.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from argparse import Namespace
2+
import pytest
3+
4+
from compiler_admin import RESULT_SUCCESS
5+
from compiler_admin.commands.time.convert import _get_source_converter, convert_to_harvest, convert, __name__ as MODULE
6+
7+
8+
@pytest.fixture
9+
def mock_get_source_converter(mocker):
10+
return mocker.patch(f"{MODULE}._get_source_converter")
11+
12+
13+
def test_get_source_converter_match(toggl_file):
14+
result = _get_source_converter(toggl_file)
15+
16+
assert result == convert_to_harvest
17+
18+
19+
def test_get_source_converter_mismatch(harvest_file):
20+
with pytest.raises(NotImplementedError, match="A converter for the given source data does not exist."):
21+
_get_source_converter(harvest_file)
22+
23+
24+
def test_convert(mock_get_source_converter):
25+
args = Namespace(input="input", output="output", client="client")
26+
res = convert(args)
27+
28+
assert res == RESULT_SUCCESS
29+
mock_get_source_converter.assert_called_once_with(args.input)
30+
mock_get_source_converter.return_value.assert_called_once_with(args.input, args.output, args.client)

tests/commands/user/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ def mock_commands_signout(mock_module_name):
8282
return mock_module_name("signout")
8383

8484

85+
@pytest.fixture
86+
def mock_commands_time(mock_module_name):
87+
"""Fixture returns a function that patches the time command function in a given module."""
88+
return mock_module_name("time")
89+
90+
8591
@pytest.fixture
8692
def mock_commands_user(mock_module_name):
8793
"""Fixture returns a function that patches the user command function in a given module."""
@@ -167,3 +173,13 @@ def _mock_NamedTemporaryFile(module, readlines=[""], **kwargs):
167173
return patched
168174

169175
return _mock_NamedTemporaryFile
176+
177+
178+
@pytest.fixture
179+
def harvest_file():
180+
return "notebooks/data/harvest-sample.csv"
181+
182+
183+
@pytest.fixture
184+
def toggl_file():
185+
return "notebooks/data/toggl-sample.csv"

tests/services/test_toggl.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,14 @@ def mock_google_user_info(mocker):
2424
return mocker.patch(f"{MODULE}.google_user_info")
2525

2626

27-
@pytest.fixture
28-
def source_data():
29-
return "notebooks/data/toggl-sample.csv"
30-
31-
32-
@pytest.fixture
33-
def sample_transformed_data():
34-
return "notebooks/data/harvest-sample.csv"
35-
36-
37-
def test_convert_to_harvest_mocked(source_data, mock_files, mock_google_user_info):
27+
def test_convert_to_harvest_mocked(toggl_file, mock_files, mock_google_user_info):
3828
mock_google_user_info.return_value = {}
3929

40-
convert_to_harvest(source_data)
30+
convert_to_harvest(toggl_file)
4131

4232
mock_files.read_csv.assert_called_once()
4333
call_args = mock_files.read_csv.call_args
44-
assert (source_data,) in call_args
34+
assert (toggl_file,) in call_args
4535
assert call_args.kwargs["usecols"] == INPUT_COLUMNS
4636
assert call_args.kwargs["parse_dates"] == ["Start date"]
4737
assert call_args.kwargs["cache_dates"] is True
@@ -52,20 +42,20 @@ def test_convert_to_harvest_mocked(source_data, mock_files, mock_google_user_inf
5242
assert call_args.kwargs["columns"] == OUTPUT_COLUMNS
5343

5444

55-
def test_convert_to_harvest_sample(source_data, sample_transformed_data, mock_google_user_info):
45+
def test_convert_to_harvest_sample(toggl_file, harvest_file, mock_google_user_info):
5646
mock_google_user_info.return_value = {}
5747
output = None
5848

5949
with StringIO() as output_data:
60-
convert_to_harvest(source_data, output_data)
50+
convert_to_harvest(toggl_file, output_data)
6151
output = output_data.getvalue()
6252

6353
assert output
6454
assert isinstance(output, str)
6555
assert ",".join(OUTPUT_COLUMNS) in output
6656

6757
order = ["Date", "First Name", "Hours"]
68-
sample_output_df = pd.read_csv(sample_transformed_data).sort_values(order)
58+
sample_output_df = pd.read_csv(harvest_file).sort_values(order)
6959
output_df = pd.read_csv(StringIO(output)).sort_values(order)
7060

7161
assert set(output_df.columns.to_list()) < set(sample_output_df.columns.to_list())

tests/test_main.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from argparse import Namespace
22
import subprocess
3+
import sys
34

45
import pytest
56

@@ -18,6 +19,11 @@ def mock_commands_init(mock_commands_init):
1819
return mock_commands_init(MODULE)
1920

2021

22+
@pytest.fixture
23+
def mock_commands_time(mock_commands_time):
24+
return mock_commands_time(MODULE)
25+
26+
2127
@pytest.fixture
2228
def mock_commands_user(mock_commands_user):
2329
return mock_commands_user(MODULE)
@@ -65,6 +71,73 @@ def test_main_init_no_username(mock_commands_init):
6571
assert mock_commands_init.call_count == 0
6672

6773

74+
def test_main_time_convert_default(mock_commands_time):
75+
main(argv=["time", "convert"])
76+
77+
mock_commands_time.assert_called_once()
78+
call_args = mock_commands_time.call_args.args
79+
assert (
80+
Namespace(
81+
func=mock_commands_time, command="time", subcommand="convert", client=None, input=sys.stdin, output=sys.stdout
82+
)
83+
in call_args
84+
)
85+
86+
87+
def test_main_time_convert_client(mock_commands_time):
88+
main(argv=["time", "convert", "--client", "client123"])
89+
90+
mock_commands_time.assert_called_once()
91+
call_args = mock_commands_time.call_args.args
92+
assert (
93+
Namespace(
94+
func=mock_commands_time,
95+
command="time",
96+
subcommand="convert",
97+
client="client123",
98+
input=sys.stdin,
99+
output=sys.stdout,
100+
)
101+
in call_args
102+
)
103+
104+
105+
def test_main_time_convert_input(mock_commands_time):
106+
main(argv=["time", "convert", "--input", "file.csv"])
107+
108+
mock_commands_time.assert_called_once()
109+
call_args = mock_commands_time.call_args.args
110+
assert (
111+
Namespace(
112+
func=mock_commands_time,
113+
command="time",
114+
subcommand="convert",
115+
client=None,
116+
input="file.csv",
117+
output=sys.stdout,
118+
)
119+
in call_args
120+
)
121+
122+
123+
def test_main_time_convert_output(mock_commands_time):
124+
main(argv=["time", "convert", "--output", "file.csv"])
125+
126+
mock_commands_time.assert_called_once()
127+
call_args = mock_commands_time.call_args.args
128+
assert (
129+
Namespace(
130+
func=mock_commands_time,
131+
command="time",
132+
subcommand="convert",
133+
client=None,
134+
input=sys.stdin,
135+
output="file.csv",
136+
)
137+
in call_args
138+
)
139+
140+
68141
def test_main_user_create(mock_commands_user):
69142
main(argv=["user", "create", "username"])
70143

0 commit comments

Comments
 (0)