From bea62355b8f344039baa2e83f91aa0a018b4c353 Mon Sep 17 00:00:00 2001 From: Ulises Santana Date: Tue, 2 May 2023 07:19:37 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20split=20toggl=20repositori?= =?UTF-8?q?es=20based=20on=20entities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._continue_with_last_time_entry_use_case.py | 20 ++-- .../test_get_current_time_entry_use_case.py | 35 +++++-- .../test_get_current_week_report_use_case.py | 26 +++++- tests/cases/test_start_time_entry_use_case.py | 44 ++++++--- tests/cases/test_stop_time_entry_use_case.py | 6 +- .../test_toggl_project_repository.py | 67 +++++++++++++ ...py => test_toggl_time_entry_repository.py} | 93 +++---------------- tests/infrastructure/test_track_cli.py | 59 ++++++------ .../continue_with_last_time_entry_use_case.py | 12 ++- .../cases/get_current_time_entry_use_case.py | 10 +- .../cases/get_current_week_report_use_case.py | 10 +- .../cases/get_today_report_use_case.py | 10 +- .../cases/start_time_entry_use_case.py | 15 +-- .../cases/stop_time_entry_use_case.py | 8 +- track/application/repositories/__init__.py | 1 + .../repositories/project_repository.py | 20 ++++ .../repositories/time_entry_repository.py | 15 +-- track/infrastructure/cli/cli.py | 11 ++- track/infrastructure/repositories/__init__.py | 3 +- .../repositories/toggl_project_repository.py | 52 +++++++++++ ...tory.py => toggl_time_entry_repository.py} | 62 +++---------- track/track.py | 7 +- 22 files changed, 348 insertions(+), 238 deletions(-) create mode 100644 tests/infrastructure/test_toggl_project_repository.py rename tests/infrastructure/{test_toggl_repository.py => test_toggl_time_entry_repository.py} (59%) create mode 100644 track/application/repositories/project_repository.py create mode 100644 track/infrastructure/repositories/toggl_project_repository.py rename track/infrastructure/repositories/{toggl_repository.py => toggl_time_entry_repository.py} (55%) diff --git a/tests/cases/test_continue_with_last_time_entry_use_case.py b/tests/cases/test_continue_with_last_time_entry_use_case.py index e259f4a..eb2bdff 100644 --- a/tests/cases/test_continue_with_last_time_entry_use_case.py +++ b/tests/cases/test_continue_with_last_time_entry_use_case.py @@ -1,13 +1,21 @@ from unittest.mock import Mock +import pytest + from tests.mocks import MockTimeHelper from track.application.cases import ContinueWithLastTimeEntryUseCase from track.application.repositories import TimeEntryRepository from track.core import TimeEntry -def test_continue_with_last_time_entry_use_case_exec(): - time_entry_repository = Mock(spec=TimeEntryRepository) +@pytest.fixture +def time_entry_repository(): + repository = Mock(spec=TimeEntryRepository) + repository.workspace_id = 123 + return repository + + +def test_continue_with_last_time_entry_use_case_exec(time_entry_repository): last_entry = TimeEntry(id=1, wid=123, description="Test Description", pid=456, _duration=60) time_entry_repository.get_last_entry.return_value = last_entry expected_call = dict(start=MockTimeHelper.get_current_utc_date(), id=last_entry.id, wid=last_entry.wid, @@ -16,7 +24,7 @@ def test_continue_with_last_time_entry_use_case_exec(): description=last_entry.description, pid=last_entry.pid, _duration=-60) time_entry_repository.create_entry.return_value = expected - case = ContinueWithLastTimeEntryUseCase(time_entry_repository, MockTimeHelper) + case = ContinueWithLastTimeEntryUseCase(time_entry_repository=time_entry_repository, time_helper=MockTimeHelper) result = case.exec() @@ -25,10 +33,10 @@ def test_continue_with_last_time_entry_use_case_exec(): time_entry_repository.create_entry.assert_called_with(**expected_call) -def test_continue_with_last_time_entry_use_case_exec_no_last_entry(): - time_entry_repository = Mock(spec=TimeEntryRepository) +def test_continue_with_last_time_entry_use_case_exec_no_last_entry(time_entry_repository): time_entry_repository.get_last_entry.return_value = None - continue_with_last_time_entry_use_case = ContinueWithLastTimeEntryUseCase(time_entry_repository, MockTimeHelper) + continue_with_last_time_entry_use_case = ContinueWithLastTimeEntryUseCase( + time_entry_repository=time_entry_repository, time_helper=MockTimeHelper) result = continue_with_last_time_entry_use_case.exec() diff --git a/tests/cases/test_get_current_time_entry_use_case.py b/tests/cases/test_get_current_time_entry_use_case.py index 8744643..5279187 100644 --- a/tests/cases/test_get_current_time_entry_use_case.py +++ b/tests/cases/test_get_current_time_entry_use_case.py @@ -1,35 +1,50 @@ import time from unittest.mock import Mock +import pytest + from track.application.cases import GetCurrentTimeEntryUseCase -from track.application.repositories import TimeEntryRepository +from track.application.repositories import ProjectRepository, TimeEntryRepository from track.core import Project, TimeEntry -def test_return_current_time_entry_successfully(): - time_entry_repository = Mock(spec=TimeEntryRepository) +@pytest.fixture +def time_entry_repository(): + repository = Mock(spec=TimeEntryRepository) + repository.workspace_id = 123 + return repository + + +@pytest.fixture +def project_repository(): + repository = Mock(spec=ProjectRepository) + repository.workspace_id = 123 + return repository + + +def test_return_current_time_entry_successfully(time_entry_repository, project_repository): current_entry = TimeEntry(id=1, wid=123, _duration=-100, pid=456, description="Test") project = Project(id=456, name="Test Project") time_entry_repository.get_current_entry.return_value = current_entry - time_entry_repository.get_project_by_id.return_value = project - get_current_time_entry_use_case = GetCurrentTimeEntryUseCase(time_entry_repository) + project_repository.get_project_by_id.return_value = project + get_current_time_entry_use_case = GetCurrentTimeEntryUseCase(time_entry_repository=time_entry_repository, + project_repository=project_repository) result = get_current_time_entry_use_case.exec() time_entry_repository.get_current_entry.assert_called_once() - time_entry_repository.get_project_by_id.assert_called_with(current_entry.pid) + project_repository.get_project_by_id.assert_called_with(current_entry.pid) assert result == (current_entry, project) assert current_entry.duration >= int(time.time()) - 100 -def test_return_none_if_there_is_no_current_entry(): - time_entry_repository = Mock(spec=TimeEntryRepository) - get_current_time_entry_use_case = GetCurrentTimeEntryUseCase(time_entry_repository) +def test_return_none_if_there_is_no_current_entry(time_entry_repository, project_repository): + get_current_time_entry_use_case = GetCurrentTimeEntryUseCase(time_entry_repository, project_repository) time_entry_repository.get_current_entry.return_value = None result = get_current_time_entry_use_case.exec() time_entry_repository.get_current_entry.assert_called_once() - time_entry_repository.get_project_by_id.assert_not_called() + project_repository.get_project_by_id.assert_not_called() assert result is None diff --git a/tests/cases/test_get_current_week_report_use_case.py b/tests/cases/test_get_current_week_report_use_case.py index 94013e8..9d3b628 100644 --- a/tests/cases/test_get_current_week_report_use_case.py +++ b/tests/cases/test_get_current_week_report_use_case.py @@ -1,20 +1,36 @@ from unittest.mock import Mock +import pytest + from track.application.cases import GetCurrentWeekReportUseCase -from track.application.repositories import TimeEntryRepository +from track.application.repositories import ProjectRepository, TimeEntryRepository from track.core import Project -def test_get_current_week_report_use_case_exec(): +@pytest.fixture +def time_entry_repository(): + repository = Mock(spec=TimeEntryRepository) + repository.workspace_id = 123 + return repository + + +@pytest.fixture +def project_repository(): + repository = Mock(spec=ProjectRepository) + repository.workspace_id = 123 + return repository + + +def test_get_current_week_report_use_case_exec(time_entry_repository, project_repository): dummy_entries = [ {"duration": 3600}, {"duration": 1800}, ] projects_dict = {"project_1": Project(1, 'Test name')} - time_entry_repository = Mock(spec=TimeEntryRepository) time_entry_repository.get_current_week_entries.return_value = dummy_entries - time_entry_repository.get_projects.return_value = projects_dict - use_case = GetCurrentWeekReportUseCase(time_entry_repository=time_entry_repository) + project_repository.get_projects.return_value = projects_dict + use_case = GetCurrentWeekReportUseCase(time_entry_repository=time_entry_repository, + project_repository=project_repository) entries, projects = use_case.exec() diff --git a/tests/cases/test_start_time_entry_use_case.py b/tests/cases/test_start_time_entry_use_case.py index 1b80ee7..7c24432 100644 --- a/tests/cases/test_start_time_entry_use_case.py +++ b/tests/cases/test_start_time_entry_use_case.py @@ -1,41 +1,55 @@ from unittest.mock import Mock +import pytest + from tests.mocks import MockTimeHelper from track.application.cases import StartTimeEntryUseCase -from track.application.repositories import TimeEntryRepository +from track.application.repositories import ProjectRepository, TimeEntryRepository from track.core import Project -def test_start_time_entry_use_case_exec_with_valid_id(): - toggl_repository = Mock(TimeEntryRepository) - toggl_repository.workspace_id = 42 - start_time_entry_use_case = StartTimeEntryUseCase(toggl_repository, MockTimeHelper) +@pytest.fixture +def time_entry_repository(): + repository = Mock(spec=TimeEntryRepository) + repository.workspace_id = 123 + return repository + + +@pytest.fixture +def project_repository(): + repository = Mock(spec=ProjectRepository) + repository.workspace_id = 123 + return repository + + +def test_start_time_entry_use_case_exec_with_valid_id(time_entry_repository, project_repository): + start_time_entry_use_case = StartTimeEntryUseCase(time_entry_repository=time_entry_repository, + project_repository=project_repository, time_helper=MockTimeHelper) description = "Test description" project_id = 123 start_time_entry_use_case.exec(description, project_id) - toggl_repository.create_entry.assert_called_with( - wid=toggl_repository.workspace_id, + time_entry_repository.create_entry.assert_called_with( + wid=time_entry_repository.workspace_id, description=description, pid=project_id, start=MockTimeHelper.get_current_utc_date(), ) -def test_start_time_entry_use_case_exec_with_project_name(): - toggl_repository = Mock(TimeEntryRepository) - toggl_repository.workspace_id = 42 - start_time_entry_use_case = StartTimeEntryUseCase(toggl_repository, MockTimeHelper) +def test_start_time_entry_use_case_exec_with_project_name(time_entry_repository, project_repository): + start_time_entry_use_case = StartTimeEntryUseCase(time_entry_repository=time_entry_repository, + project_repository=project_repository, time_helper=MockTimeHelper) description = "Test description" project = Project(id=123, name="Test project") - toggl_repository.get_project_by_name.return_value = project + project_repository.get_project_by_name.return_value = project start_time_entry_use_case.exec(description, project.name) - toggl_repository.get_project_by_name.assert_called_with(project.name) - toggl_repository.create_entry.assert_called_with( - wid=toggl_repository.workspace_id, + project_repository.get_project_by_name.assert_called_with(project.name) + time_entry_repository.create_entry.assert_called_with( + wid=time_entry_repository.workspace_id, description=description, pid=project.id, start=MockTimeHelper.get_current_utc_date(), diff --git a/tests/cases/test_stop_time_entry_use_case.py b/tests/cases/test_stop_time_entry_use_case.py index 0158a54..8dec1c6 100644 --- a/tests/cases/test_stop_time_entry_use_case.py +++ b/tests/cases/test_stop_time_entry_use_case.py @@ -3,11 +3,11 @@ from tests.mocks import MockTimeHelper from track.application.cases import StopTimeEntryUseCase from track.core import TimeEntry -from track.infrastructure.repositories import TogglRepository +from track.infrastructure.repositories import TogglTimeEntryRepository def test_stop_time_entry_use_case_exec(): - toggl_repository = Mock(spec=TogglRepository) + toggl_repository = Mock(spec=TogglTimeEntryRepository) current_entry = TimeEntry(id=1, wid=123, description="Reading emails", pid=123, _duration=60) expected = dict(id=current_entry.id, wid=current_entry.wid, description=current_entry.description, stop=MockTimeHelper.get_current_utc_date()) @@ -27,7 +27,7 @@ def test_stop_time_entry_use_case_exec(): def test_stop_time_entry_use_case_exec_no_current_entry(): - toggl_repository = Mock(spec=TogglRepository) + toggl_repository = Mock(spec=TogglTimeEntryRepository) toggl_repository.get_current_entry.return_value = None stop_time_entry_use_case = StopTimeEntryUseCase(toggl_repository) diff --git a/tests/infrastructure/test_toggl_project_repository.py b/tests/infrastructure/test_toggl_project_repository.py new file mode 100644 index 0000000..12dfc1e --- /dev/null +++ b/tests/infrastructure/test_toggl_project_repository.py @@ -0,0 +1,67 @@ +from unittest.mock import Mock, patch + +from track.core import Project +from track.infrastructure.repositories import TogglProjectRepository + + +def test_get_projects(): + with patch('requests.get') as mocked_get: + # Mock the response for the GET request + mocked_response = Mock() + mocked_response.json.return_value = [ + { + "id": 456, + "name": "Test project", + }, + { + "id": 789, + "name": "Test project 2", + } + ] + mocked_get.return_value = mocked_response + + repository = TogglProjectRepository(workspace_id=123, token='test-token') + projects = repository.get_projects() + assert len(projects) == 2 + assert isinstance(projects[456], Project) + assert projects[456].name == 'Test project' + + +def test_get_project_by_id(): + with patch('requests.get') as mocked_get: + # Mock the response for the GET request + mocked_response = Mock() + mocked_response.json.return_value = { + "id": 456, + "name": "Test project", + } + mocked_get.return_value = mocked_response + + repository = TogglProjectRepository(workspace_id=123, token='test-token') + project = repository.get_project_by_id(456) + assert isinstance(project, Project) + assert project.id == 456 + assert project.name == 'Test project' + + +def test_get_project_by_name(): + with patch('requests.get') as mocked_get: + # Mock the response for the GET request + mocked_response = Mock() + mocked_response.json.return_value = [ + { + "id": 456, + "name": "Test project", + }, + { + "id": 789, + "name": "Test project 2", + } + ] + mocked_get.return_value = mocked_response + + repository = TogglProjectRepository(workspace_id=123, token='test-token') + project = repository.get_project_by_name("Test project 2") + assert isinstance(project, Project) + assert project.id == 789 + assert project.name == 'Test project 2' diff --git a/tests/infrastructure/test_toggl_repository.py b/tests/infrastructure/test_toggl_time_entry_repository.py similarity index 59% rename from tests/infrastructure/test_toggl_repository.py rename to tests/infrastructure/test_toggl_time_entry_repository.py index 1fa351d..ee3c25c 100644 --- a/tests/infrastructure/test_toggl_repository.py +++ b/tests/infrastructure/test_toggl_time_entry_repository.py @@ -1,13 +1,13 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import Mock, patch -from track.core import Project, TimeEntry, TimeEntryList -from track.infrastructure.repositories import TogglRepository +from track.core import TimeEntry, TimeEntryList +from track.infrastructure.repositories import TogglTimeEntryRepository def test_get_last_entry(): with patch('requests.get') as mocked_get: # Mock the response for the GET request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = [ { "id": 1, @@ -19,7 +19,7 @@ def test_get_last_entry(): ] mocked_get.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') last_entry = repository.get_last_entry() assert isinstance(last_entry, TimeEntry) assert last_entry.id == 1 @@ -29,7 +29,7 @@ def test_get_last_entry(): def test_get_current_entry(): with patch('requests.get') as mocked_get: # Mock the response for the GET request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = { "id": 2, "wid": 123, @@ -39,7 +39,7 @@ def test_get_current_entry(): } mocked_get.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') current_entry = repository.get_current_entry() assert isinstance(current_entry, TimeEntry) assert current_entry.id == 2 @@ -49,7 +49,7 @@ def test_get_current_entry(): def test_create_entry(): with patch('requests.post') as mocked_post: # Mock the response for the POST request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = { "id": 3, "wid": 123, @@ -59,7 +59,7 @@ def test_create_entry(): } mocked_post.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') created_entry = repository.create_entry(wid=123, pid=789, description="New entry") assert isinstance(created_entry, TimeEntry) assert created_entry.id == 3 @@ -69,7 +69,7 @@ def test_create_entry(): def test_update_entry(): with patch('requests.put') as mocked_put: # Mock the response for the PUT request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = { "id": 4, "wid": 123, @@ -79,7 +79,7 @@ def test_update_entry(): } mocked_put.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') updated_entry = repository.update_entry(id=4, wid=123, pid=456, description="Updated entry", duration=900) assert isinstance(updated_entry, TimeEntry) assert updated_entry.id == 4 @@ -89,7 +89,7 @@ def test_update_entry(): def test_get_current_week_entries(): with patch('requests.get') as mocked_get: # Mock the response for the GET request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = [ { "id": 5, @@ -108,7 +108,7 @@ def test_get_current_week_entries(): ] mocked_get.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') week_entries = repository.get_current_week_entries() assert isinstance(week_entries, TimeEntryList) assert len(week_entries.values) == 2 @@ -119,7 +119,7 @@ def test_get_current_week_entries(): def test_get_today_entries(): with patch('requests.get') as mocked_get: # Mock the response for the GET request - mocked_response = MagicMock() + mocked_response = Mock() mocked_response.json.return_value = [ { "id": 7, @@ -138,72 +138,9 @@ def test_get_today_entries(): ] mocked_get.return_value = mocked_response - repository = TogglRepository(workspace_id=123, token='test-token') + repository = TogglTimeEntryRepository(workspace_id=123, token='test-token') today_entries = repository.get_today_entries() assert isinstance(today_entries, TimeEntryList) assert len(today_entries.values) == 2 assert today_entries.values[0].id == 7 assert today_entries.values[1].id == 8 - - -def test_get_projects(): - with patch('requests.get') as mocked_get: - # Mock the response for the GET request - mocked_response = MagicMock() - mocked_response.json.return_value = [ - { - "id": 456, - "name": "Test project", - }, - { - "id": 789, - "name": "Test project 2", - } - ] - mocked_get.return_value = mocked_response - - repository = TogglRepository(workspace_id=123, token='test-token') - projects = repository.get_projects() - assert len(projects) == 2 - assert isinstance(projects[456], Project) - assert projects[456].name == 'Test project' - - -def test_get_project_by_id(): - with patch('requests.get') as mocked_get: - # Mock the response for the GET request - mocked_response = MagicMock() - mocked_response.json.return_value = { - "id": 456, - "name": "Test project", - } - mocked_get.return_value = mocked_response - - repository = TogglRepository(workspace_id=123, token='test-token') - project = repository.get_project_by_id(456) - assert isinstance(project, Project) - assert project.id == 456 - assert project.name == 'Test project' - - -def test_get_project_by_name(): - with patch('requests.get') as mocked_get: - # Mock the response for the GET request - mocked_response = MagicMock() - mocked_response.json.return_value = [ - { - "id": 456, - "name": "Test project", - }, - { - "id": 789, - "name": "Test project 2", - } - ] - mocked_get.return_value = mocked_response - - repository = TogglRepository(workspace_id=123, token='test-token') - project = repository.get_project_by_name("Test project 2") - assert isinstance(project, Project) - assert project.id == 789 - assert project.name == 'Test project 2' diff --git a/tests/infrastructure/test_track_cli.py b/tests/infrastructure/test_track_cli.py index c3de9ff..41c9484 100644 --- a/tests/infrastructure/test_track_cli.py +++ b/tests/infrastructure/test_track_cli.py @@ -2,7 +2,7 @@ import pytest -from track.application.repositories import TimeEntryRepository +from track.application.repositories import ProjectRepository, TimeEntryRepository from track.core import Project, TimeEntry, TimeEntryList from track.infrastructure.cli import TrackCLI @@ -14,6 +14,13 @@ def time_entry_repository(): return repository +@pytest.fixture +def project_repository(): + repository = Mock(spec=ProjectRepository) + repository.workspace_id = 123 + return repository + + @pytest.fixture def print_mock(): return Mock(spec=print) @@ -29,95 +36,95 @@ def project(time_entry): return Project(id=time_entry.pid, name="Test Project") -def test_restart_command(time_entry_repository, print_mock, time_entry): +def test_restart_command(time_entry_repository, project_repository, print_mock, time_entry): time_entry_repository.get_last_entry.return_value = time_entry time_entry_repository.create_entry.return_value = time_entry - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.restart() print_mock.assert_called_with(f"Continuing with '{time_entry.description}'") -def test_restart_command_with_no_last_entry(time_entry_repository, print_mock, time_entry): +def test_restart_command_with_no_last_entry(time_entry_repository, project_repository, print_mock, time_entry): time_entry_repository.get_last_entry.return_value = None - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.restart() print_mock.assert_called_with("There is no time entry to continue with.") -def test_restart_command_with_error_creating_entry(time_entry_repository, print_mock, time_entry): +def test_restart_command_with_error_creating_entry(time_entry_repository, project_repository, print_mock, time_entry): time_entry_repository.get_last_entry.return_value = time_entry time_entry_repository.create_entry.side_effect = ValueError("Boom!! 💥") - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.restart() print_mock.assert_called_with("Error creating time entry.") -def test_start_command(print_mock, time_entry_repository, time_entry, project): - track_cli = TrackCLI(time_entry_repository, print_mock) +def test_start_command(print_mock, time_entry_repository, project_repository, time_entry, project): + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.start(time_entry.description, project) print_mock.assert_called_once_with(f"Starting with '{time_entry.description}'") -def test_start_command_with_error_creating_entry(print_mock, time_entry_repository, time_entry, project): +def test_start_command_with_error_creating_entry(print_mock, time_entry_repository, project_repository, time_entry, project): time_entry_repository.create_entry.side_effect = ValueError("Boom!! 💥") - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.start(time_entry.description, project) print_mock.assert_called_with("Error creating time entry.") -def test_stop_command(print_mock, time_entry_repository, time_entry): +def test_stop_command(print_mock, time_entry_repository, project_repository, time_entry): time_entry_repository.update_entry.return_value = time_entry - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.stop() print_mock.assert_called_once_with(f"Time entry '{time_entry.description}' stopped") -def test_stop_command_with_no_current_entry(print_mock, time_entry_repository, time_entry): +def test_stop_command_with_no_current_entry(print_mock, time_entry_repository, project_repository, time_entry): time_entry_repository.get_current_entry.return_value = None - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.stop() print_mock.assert_called_once_with("There is no time entry running.") -def test_current_command_successfully(print_mock, time_entry_repository, time_entry, project): +def test_current_command_successfully(print_mock, time_entry_repository, project_repository, time_entry, project): time_entry_repository.get_current_entry.return_value = time_entry - time_entry_repository.get_project_by_id.return_value = project - track_cli = TrackCLI(time_entry_repository, print_mock) + project_repository.get_project_by_id.return_value = project + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.current() print_mock.assert_called_once_with(f"00h 01m 00s - {time_entry.description} ({project.name})") -def test_current_command_with_no_current_entry(print_mock, time_entry_repository, time_entry): +def test_current_command_with_no_current_entry(print_mock, time_entry_repository, project_repository, time_entry): time_entry_repository.get_current_entry.return_value = None - track_cli = TrackCLI(time_entry_repository, print_mock) + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.current() print_mock.assert_called_once_with("There is no time entry running.") -def test_today_command(print_mock, time_entry_repository, time_entry, project): +def test_today_command(print_mock, time_entry_repository, project_repository, time_entry, project): entries = TimeEntryList([time_entry, time_entry]) projects_dict = dict([(project.id, project)]) time_entry_repository.get_today_entries.return_value = entries - time_entry_repository.get_projects.return_value = projects_dict - track_cli = TrackCLI(time_entry_repository, print_mock) + project_repository.get_projects.return_value = projects_dict + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.today() @@ -125,12 +132,12 @@ def test_today_command(print_mock, time_entry_repository, time_entry, project): - 00h 02m 00s - Test Description (Test Project)""") -def test_week_command(print_mock, time_entry_repository, time_entry, project): +def test_week_command(print_mock, time_entry_repository, project_repository, time_entry, project): entries = TimeEntryList([time_entry, time_entry]) projects_dict = dict([(project.id, project)]) time_entry_repository.get_current_week_entries.return_value = entries - time_entry_repository.get_projects.return_value = projects_dict - track_cli = TrackCLI(time_entry_repository, print_mock) + project_repository.get_projects.return_value = projects_dict + track_cli = TrackCLI(time_entry_repository, project_repository, print_mock) track_cli.week() diff --git a/track/application/cases/continue_with_last_time_entry_use_case.py b/track/application/cases/continue_with_last_time_entry_use_case.py index c05167a..d8505b5 100644 --- a/track/application/cases/continue_with_last_time_entry_use_case.py +++ b/track/application/cases/continue_with_last_time_entry_use_case.py @@ -1,16 +1,18 @@ +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository from track.core.helpers import TimeHelper +@dataclass class ContinueWithLastTimeEntryUseCase: - def __init__(self, time_entry_repository: TimeEntryRepository, time_helper=TimeHelper()): - self.toggl_repository = time_entry_repository - self.time_helper = time_helper + time_entry_repository: TimeEntryRepository + time_helper: TimeHelper = TimeHelper() def exec(self): - last_entry = self.toggl_repository.get_last_entry() + last_entry = self.time_entry_repository.get_last_entry() if last_entry: - return self.toggl_repository.create_entry( + return self.time_entry_repository.create_entry( id=last_entry.id, wid=last_entry.wid, description=last_entry.description, diff --git a/track/application/cases/get_current_time_entry_use_case.py b/track/application/cases/get_current_time_entry_use_case.py index 5ee8bb4..5a9a6b6 100644 --- a/track/application/cases/get_current_time_entry_use_case.py +++ b/track/application/cases/get_current_time_entry_use_case.py @@ -1,12 +1,16 @@ +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository +from track.application.repositories.project_repository import ProjectRepository +@dataclass class GetCurrentTimeEntryUseCase: - def __init__(self, time_entry_repository: TimeEntryRepository): - self.time_entry_repository = time_entry_repository + time_entry_repository: TimeEntryRepository + project_repository: ProjectRepository def exec(self): current_entry = self.time_entry_repository.get_current_entry() if current_entry: - project = self.time_entry_repository.get_project_by_id(current_entry.pid) + project = self.project_repository.get_project_by_id(current_entry.pid) return current_entry, project diff --git a/track/application/cases/get_current_week_report_use_case.py b/track/application/cases/get_current_week_report_use_case.py index 4de9755..203223a 100644 --- a/track/application/cases/get_current_week_report_use_case.py +++ b/track/application/cases/get_current_week_report_use_case.py @@ -1,11 +1,15 @@ +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository +from track.application.repositories.project_repository import ProjectRepository +@dataclass class GetCurrentWeekReportUseCase: - def __init__(self, time_entry_repository: TimeEntryRepository): - self.time_entry_repository = time_entry_repository + time_entry_repository: TimeEntryRepository + project_repository: ProjectRepository def exec(self): entries = self.time_entry_repository.get_current_week_entries() - projects_dict = self.time_entry_repository.get_projects() + projects_dict = self.project_repository.get_projects() return entries, projects_dict diff --git a/track/application/cases/get_today_report_use_case.py b/track/application/cases/get_today_report_use_case.py index 1aa3387..7cf20ea 100644 --- a/track/application/cases/get_today_report_use_case.py +++ b/track/application/cases/get_today_report_use_case.py @@ -1,11 +1,15 @@ +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository +from track.application.repositories.project_repository import ProjectRepository +@dataclass class GetTodayReportUseCase: - def __init__(self, time_entry_repository: TimeEntryRepository): - self.time_entry_repository = time_entry_repository + time_entry_repository: TimeEntryRepository + project_repository: ProjectRepository def exec(self): entries = self.time_entry_repository.get_today_entries() - projects_dict = self.time_entry_repository.get_projects() + projects_dict = self.project_repository.get_projects() return entries, projects_dict diff --git a/track/application/cases/start_time_entry_use_case.py b/track/application/cases/start_time_entry_use_case.py index d780c56..8fa42bb 100644 --- a/track/application/cases/start_time_entry_use_case.py +++ b/track/application/cases/start_time_entry_use_case.py @@ -1,18 +1,22 @@ -from track.core import helpers +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository +from track.application.repositories.project_repository import ProjectRepository +from track.core import helpers from track.core.helpers import TimeHelper +@dataclass class StartTimeEntryUseCase: - def __init__(self, time_entry_repository: TimeEntryRepository, time_helper: TimeHelper = TimeHelper()): - self.time_entry_repository = time_entry_repository - self.time_helper = time_helper + time_entry_repository: TimeEntryRepository + project_repository: ProjectRepository + time_helper: TimeHelper = TimeHelper() def exec(self, description, project): if helpers.is_valid_id(str(project)): project_id = project else: - p = self.time_entry_repository.get_project_by_name(project) + p = self.project_repository.get_project_by_name(project) project_id = p.id self.time_entry_repository.create_entry( wid=self.time_entry_repository.workspace_id, @@ -20,4 +24,3 @@ def exec(self, description, project): pid=project_id, start=self.time_helper.get_current_utc_date(), ) - diff --git a/track/application/cases/stop_time_entry_use_case.py b/track/application/cases/stop_time_entry_use_case.py index 34b2c1b..c98dd77 100644 --- a/track/application/cases/stop_time_entry_use_case.py +++ b/track/application/cases/stop_time_entry_use_case.py @@ -1,11 +1,13 @@ +from dataclasses import dataclass + from track.application.repositories import TimeEntryRepository from track.core.helpers import TimeHelper +@dataclass class StopTimeEntryUseCase: - def __init__(self, time_entry_repositoy: TimeEntryRepository, time_helper: TimeHelper = TimeHelper()): - self.time_entry_repository = time_entry_repositoy - self.time_helper = time_helper + time_entry_repository: TimeEntryRepository + time_helper: TimeHelper = TimeHelper() def exec(self): current_entry = self.time_entry_repository.get_current_entry() diff --git a/track/application/repositories/__init__.py b/track/application/repositories/__init__.py index 8ea1ea8..cbabdd2 100644 --- a/track/application/repositories/__init__.py +++ b/track/application/repositories/__init__.py @@ -1 +1,2 @@ from .time_entry_repository import TimeEntryRepository +from .project_repository import ProjectRepository diff --git a/track/application/repositories/project_repository.py b/track/application/repositories/project_repository.py new file mode 100644 index 0000000..9e206cb --- /dev/null +++ b/track/application/repositories/project_repository.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod +from typing import Dict + +from track.core import Project + + +class ProjectRepository(ABC): + workspace_id: int + + @abstractmethod + def get_projects(self) -> Dict[int, Project]: + pass + + @abstractmethod + def get_project_by_id(self, id: int) -> Project: + pass + + @abstractmethod + def get_project_by_name(self, name: str) -> Project: + pass diff --git a/track/application/repositories/time_entry_repository.py b/track/application/repositories/time_entry_repository.py index 113557b..9ac1460 100644 --- a/track/application/repositories/time_entry_repository.py +++ b/track/application/repositories/time_entry_repository.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod -from typing import Dict -from track.core import Project, TimeEntry, TimeEntryList +from track.core import TimeEntry, TimeEntryList class TimeEntryRepository(ABC): @@ -30,15 +29,3 @@ def get_current_week_entries(self) -> TimeEntryList: @abstractmethod def get_today_entries(self) -> TimeEntryList: pass - - @abstractmethod - def get_projects(self) -> Dict[int, Project]: - pass - - @abstractmethod - def get_project_by_id(self, id: int) -> Project: - pass - - @abstractmethod - def get_project_by_name(self, name: str) -> Project: - pass diff --git a/track/infrastructure/cli/cli.py b/track/infrastructure/cli/cli.py index c5b422b..56a842b 100644 --- a/track/infrastructure/cli/cli.py +++ b/track/infrastructure/cli/cli.py @@ -8,13 +8,14 @@ StartTimeEntryUseCase, StopTimeEntryUseCase ) -from track.application.repositories import TimeEntryRepository +from track.application.repositories import ProjectRepository, TimeEntryRepository from track.infrastructure.cli.renderer import Renderer @dataclass class TrackCLI: time_entry_repository: TimeEntryRepository + project_repository: ProjectRepository print: Callable def restart(self): @@ -35,7 +36,7 @@ def start(self, description, project): """Start a new time entry. """ try: - case = StartTimeEntryUseCase(self.time_entry_repository) + case = StartTimeEntryUseCase(self.time_entry_repository, self.project_repository) case.exec(description, project) self.print(f"Starting with '{description}'") except Exception as e: @@ -55,7 +56,7 @@ def stop(self): def current(self): """Show the current time entry. """ - case = GetCurrentTimeEntryUseCase(self.time_entry_repository) + case = GetCurrentTimeEntryUseCase(self.time_entry_repository, self.project_repository) result = case.exec() if result: self.print(Renderer.render_time_entry(*result)) @@ -65,13 +66,13 @@ def current(self): def today(self): """Show the total time tracked today. """ - case = GetTodayReportUseCase(self.time_entry_repository) + case = GetTodayReportUseCase(self.time_entry_repository, self.project_repository) entries, projects_dict = case.exec() self.print(Renderer.render_report(entries, projects_dict)) def week(self): """Show the total time tracked this week. """ - case = GetCurrentWeekReportUseCase(self.time_entry_repository) + case = GetCurrentWeekReportUseCase(self.time_entry_repository, self.project_repository) entries, projects_dict = case.exec() self.print(Renderer.render_report(entries, projects_dict)) diff --git a/track/infrastructure/repositories/__init__.py b/track/infrastructure/repositories/__init__.py index cff7819..c65e350 100644 --- a/track/infrastructure/repositories/__init__.py +++ b/track/infrastructure/repositories/__init__.py @@ -1 +1,2 @@ -from .toggl_repository import TogglRepository +from .toggl_time_entry_repository import TogglTimeEntryRepository +from .toggl_project_repository import TogglProjectRepository diff --git a/track/infrastructure/repositories/toggl_project_repository.py b/track/infrastructure/repositories/toggl_project_repository.py new file mode 100644 index 0000000..98f00ac --- /dev/null +++ b/track/infrastructure/repositories/toggl_project_repository.py @@ -0,0 +1,52 @@ +from base64 import b64encode + +import requests + +from track.application.repositories import ProjectRepository +from track.core import Project + + +class TogglProjectRepository(ProjectRepository): + def __init__(self, workspace_id: int, token: str, base_url: str = "https://api.track.toggl.com/api/v9"): + self.workspace_id = workspace_id + self.base_url = base_url + self.basic_headers = { + "content-type": "application/json", + "Authorization": "Basic %s" % b64encode(token.encode()).decode("ascii"), + } + + def get_projects(self): + data = requests.get( + f"{self.base_url}/workspaces/{self.workspace_id}/projects", + headers=self.basic_headers + ) + project_dict = {} + for project in data.json(): + project_dict[project["id"]] = TogglProjectRepository._map_to_project(**project) + return project_dict + + def get_project_by_id(self, project_id: int) -> Project: + data = requests.get( + f"{self.base_url}/workspaces/{self.workspace_id}/projects/{project_id}", + headers=self.basic_headers + ) + result = data.json() + if result: + return TogglProjectRepository._map_to_project(**data.json()) + + def get_project_by_name(self, project_name): + projects = self.get_projects() + return self._filter_project_by_name(project_name, projects) + + @staticmethod + def _filter_project_by_name(project, projects): + for p in projects.values(): + if p.name == project: + return p + + @staticmethod + def _map_to_project(**raw_project): + return Project( + id=raw_project["id"], + name=raw_project["name"] + ) diff --git a/track/infrastructure/repositories/toggl_repository.py b/track/infrastructure/repositories/toggl_time_entry_repository.py similarity index 55% rename from track/infrastructure/repositories/toggl_repository.py rename to track/infrastructure/repositories/toggl_time_entry_repository.py index 06b1895..600aec6 100644 --- a/track/infrastructure/repositories/toggl_repository.py +++ b/track/infrastructure/repositories/toggl_time_entry_repository.py @@ -6,11 +6,11 @@ import requests from track.application.repositories import TimeEntryRepository -from track.core import Project, TimeEntry, TimeEntryList +from track.core import TimeEntry, TimeEntryList from track.core.helpers import TimeHelper -class TogglRepository(TimeEntryRepository): +class TogglTimeEntryRepository(TimeEntryRepository): def __init__(self, workspace_id: int, token: str, base_url: str = "https://api.track.toggl.com/api/v9"): self.workspace_id = workspace_id self.base_url = base_url @@ -25,7 +25,7 @@ def get_last_entry(self): headers=self.basic_headers, ) last_entry, *entries = data.json() - return TogglRepository._map_to_time_entry(**last_entry) + return TogglTimeEntryRepository._map_to_time_entry(**last_entry) def get_current_entry(self): data = requests.get( @@ -34,7 +34,7 @@ def get_current_entry(self): ) result = data.json() if result: - return TogglRepository._map_to_time_entry(**data.json()) + return TogglTimeEntryRepository._map_to_time_entry(**data.json()) def create_entry(self, **entry): data = requests.post( @@ -42,7 +42,7 @@ def create_entry(self, **entry): headers=self.basic_headers, json={**entry, "created_with": "track CLI", "duration": int(time.time()) * -1}, ) - return TogglRepository._map_to_time_entry(**data.json()) + return TogglTimeEntryRepository._map_to_time_entry(**data.json()) def update_entry(self, **entry): data = requests.put( @@ -50,61 +50,32 @@ def update_entry(self, **entry): headers=self.basic_headers, json=entry, ) - return TogglRepository._map_to_time_entry(**data.json()) + return TogglTimeEntryRepository._map_to_time_entry(**data.json()) def get_current_week_entries(self): start, end = TimeHelper.get_week_dates(datetime.today().date()) query = { - "start_date": TogglRepository._format_to_toggl_date(start), - "end_date": TogglRepository._format_to_toggl_date(end + timedelta(days=1)) + "start_date": TogglTimeEntryRepository._format_to_toggl_date(start), + "end_date": TogglTimeEntryRepository._format_to_toggl_date(end + timedelta(days=1)) } data = requests.get( f"{self.base_url}/me/time_entries?{urlencode(query)}", headers=self.basic_headers ) - return TogglRepository._map_to_time_entry_list(data.json()) + return TogglTimeEntryRepository._map_to_time_entry_list(data.json()) def get_today_entries(self): today = datetime.today().date() tomorrow = today + timedelta(days=1) query = { - "start_date": TogglRepository._format_to_toggl_date(today), - "end_date": TogglRepository._format_to_toggl_date(tomorrow) + "start_date": TogglTimeEntryRepository._format_to_toggl_date(today), + "end_date": TogglTimeEntryRepository._format_to_toggl_date(tomorrow) } data = requests.get( f"{self.base_url}/me/time_entries?{urlencode(query)}", headers=self.basic_headers ) - return TogglRepository._map_to_time_entry_list(data.json()) - - def get_projects(self): - data = requests.get( - f"{self.base_url}/workspaces/{self.workspace_id}/projects", - headers=self.basic_headers - ) - project_dict = {} - for project in data.json(): - project_dict[project["id"]] = TogglRepository._map_to_project(**project) - return project_dict - - def get_project_by_id(self, project_id: int) -> Project: - data = requests.get( - f"{self.base_url}/workspaces/{self.workspace_id}/projects/{project_id}", - headers=self.basic_headers - ) - result = data.json() - if result: - return TogglRepository._map_to_project(**data.json()) - - def get_project_by_name(self, project_name): - projects = self.get_projects() - return self._filter_project_by_name(project_name, projects) - - @staticmethod - def _filter_project_by_name(project, projects): - for p in projects.values(): - if p.name == project: - return p + return TogglTimeEntryRepository._map_to_time_entry_list(data.json()) @staticmethod def _map_to_time_entry(**raw_entry): @@ -116,16 +87,9 @@ def _map_to_time_entry(**raw_entry): _duration=raw_entry["duration"], ) - @staticmethod - def _map_to_project(**raw_project): - return Project( - id=raw_project["id"], - name=raw_project["name"] - ) - @staticmethod def _map_to_time_entry_list(raw_entries): - return TimeEntryList(values=[TogglRepository._map_to_time_entry(**x) for x in raw_entries]) + return TimeEntryList(values=[TogglTimeEntryRepository._map_to_time_entry(**x) for x in raw_entries]) @staticmethod def _format_to_toggl_date(date): diff --git a/track/track.py b/track/track.py index d98d6b2..f2bd738 100644 --- a/track/track.py +++ b/track/track.py @@ -2,10 +2,11 @@ from track.infrastructure import Config from track.infrastructure.cli import TrackCLI -from track.infrastructure.repositories import TogglRepository +from track.infrastructure.repositories import TogglTimeEntryRepository, TogglProjectRepository -toggl_repository = TogglRepository(workspace_id=Config.get_workspace_id(), token=Config.get_token()) -track_cli = TrackCLI(time_entry_repository=toggl_repository, print=click.echo) +time_entry_repository = TogglTimeEntryRepository(workspace_id=Config.get_workspace_id(), token=Config.get_token()) +project_repository = TogglProjectRepository(workspace_id=Config.get_workspace_id(), token=Config.get_token()) +track_cli = TrackCLI(time_entry_repository=time_entry_repository, project_repository=project_repository, print=click.echo) @click.group()