From 9311ce42a62e8b0ce5f3386617bfa365ba0182ef Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 10 Nov 2021 18:37:28 -0500 Subject: [PATCH 01/12] feat: TT-401 Implemented service, end-point, dao, test- time entries --- V2/serverless.yml | 10 ++ V2/tests/api/api_fixtures.py | 39 ++++++++ .../azure/time_entry_azure_endpoints_test.py | 40 ++++++++ V2/tests/conftest.py | 1 + .../integration/daos/time_entries_dao.test.py | 45 +++++++++ .../unit/services/time_entry_service_test.py | 15 +++ .../use_cases/time_entries_use_case_test.py | 20 ++++ .../time_entries/_application/__init__.py | 1 + .../_application/_time_entries/__init__.py | 1 + .../_time_entries/_create_time_entry.py | 49 ++++++++++ .../time_entries/_domain/__init__.py | 6 ++ .../_domain/_entities/__init__.py | 1 + .../_domain/_entities/_time_entry.py | 16 ++++ .../_persistence_contracts/__init__.py | 1 + .../_time_entries_dao.py | 7 ++ .../_domain/_services/__init__.py | 1 + .../_domain/_services/_time_entry.py | 9 ++ .../_domain/_use_cases/__init__.py | 1 + .../_use_cases/_create_time_entry_use_case.py | 9 ++ .../time_entries/_infrastructure/__init__.py | 1 + .../_data_persistence/__init__.py | 1 + .../_data_persistence/_time_entries_dao.py | 38 ++++++++ .../_data_persistence/time_entries_data.json | 92 +++++++++++++++++++ V2/time_tracker/time_entries/interface.py | 1 + 24 files changed, 405 insertions(+) create mode 100644 V2/tests/api/azure/time_entry_azure_endpoints_test.py create mode 100644 V2/tests/integration/daos/time_entries_dao.test.py create mode 100644 V2/tests/unit/services/time_entry_service_test.py create mode 100644 V2/tests/unit/use_cases/time_entries_use_case_test.py create mode 100644 V2/time_tracker/time_entries/_application/__init__.py create mode 100644 V2/time_tracker/time_entries/_application/_time_entries/__init__.py create mode 100644 V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_entities/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_entities/_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py create mode 100644 V2/time_tracker/time_entries/_domain/_services/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_services/_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/__init__.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json create mode 100644 V2/time_tracker/time_entries/interface.py diff --git a/V2/serverless.yml b/V2/serverless.yml index 0eb3f42f..c6c5e34b 100644 --- a/V2/serverless.yml +++ b/V2/serverless.yml @@ -76,3 +76,13 @@ functions: - POST route: activities/ authLevel: anonymous + + create_time_entry: + handler: time_tracker/time_entries/interface.create_time_entry + events: + - http: true + x-azure-settings: + methods: + - POST + route: time-entries/ + authLevel: anonymous diff --git a/V2/tests/api/api_fixtures.py b/V2/tests/api/api_fixtures.py index 21b58021..572bc361 100644 --- a/V2/tests/api/api_fixtures.py +++ b/V2/tests/api/api_fixtures.py @@ -39,3 +39,42 @@ def create_temp_activities(tmpdir_factory): yield activities, json_file shutil.rmtree(temporary_directory) + +@pytest.fixture +def create_temp_time_entries(tmpdir_factory): + temporary_directory = tmpdir_factory.mktemp("tmp") + json_file = temporary_directory.join("time_entries.json") + time_entries = [ + { + "id" : 1, + "start_date" : "12/07/2021", + "owner_id" : 2, + "description" : "create time entry", + "activity_id" : 2, + "uri" : "http://timetracker.com", + "technologies" : ["git","jira","python"], + "end_date" : "12/07/2021", + "deleted" : "1", + "timezone_offset" : "300", + "project_id" : 1 + }, + { + "id" : 1, + "start_date" : "12/07/2021", + "owner_id" : 2, + "description" : "create activity", + "activity_id" : 2, + "uri" : "http://timetracker.com", + "technologies" : ["git","jira","python"], + "end_date" : "12/07/2021", + "deleted" : "1", + "timezone_offset" : "300", + "project_id" : 1 + }, + ] + + with open(json_file, 'w') as outfile: + json.dump(time_entries, outfile) + + yield time_entries, json_file + shutil.rmtree(temporary_directory) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py new file mode 100644 index 00000000..7d27b800 --- /dev/null +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -0,0 +1,40 @@ +from time_tracker.time_entries._application import _time_entries as time_entries +from faker import Faker + +import azure.functions as func +import json + + +ACTIVITY_URL = "/api/time-entries/" + + +def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( + create_temp_time_entries, +): + time_entries_json, tmp_directory = create_temp_time_entries + time_entries._create_time_entry._JSON_PATH = tmp_directory + + time_entry_body = { + "id" : None, + "start_date" : Faker().date(), + "owner_id" : Faker().random_int(), + "description": Faker().sentence(), + "activity_id" : Faker().random_int(), + "uri": "http://timetracker.com", + "technologies" : ["jira","git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "300", + "project_id": Faker().random_int(), + } + body = json.dumps(time_entry_body).encode("utf-8") + req = func.HttpRequest( + method="POST", + body=body, + url=ACTIVITY_URL, + ) + + response = time_entries.create_time_entry(req) + time_entry_json_data = response.get_body() + assert response.status_code == 201 + assert time_entry_json_data == body diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 2741ce95..5dcfc318 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,2 +1,3 @@ # flake8: noqa from tests.api.api_fixtures import create_temp_activities +from tests.api.api_fixtures import create_temp_time_entries diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py new file mode 100644 index 00000000..9947061a --- /dev/null +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -0,0 +1,45 @@ +import json +import pytest +import typing + +from faker import Faker + +from time_tracker.time_entries._infrastructure import TimeEntriesJsonDao +from time_tracker.time_entries._domain import TimeEntry + + +@pytest.fixture(name="create_fake_time_entries") +def _create_fake_time_entries(mocker) -> typing.List[TimeEntry]: + def _creator(time_entries): + read_data = json.dumps(time_entries) + mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) + return [TimeEntry(**time_entry) for time_entry in time_entries] + + return _creator + + +def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( + create_fake_time_entries, +): + create_fake_time_entries([]) + + time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) + time_entry_data = { + "id" : None, + "start_date" : Faker().date(), + "owner_id" : Faker().random_int(), + "description": Faker().sentence(), + "activity_id" : Faker().random_int(), + "uri": "http://hola.com", + "technologies" : ["jira","git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "UTC-5", + "project_id": Faker().random_int(), + } + result = time_entries_json_dao.create(time_entry_data) + assert result == TimeEntry(**time_entry_data) + + + + diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py new file mode 100644 index 00000000..d844d45d --- /dev/null +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -0,0 +1,15 @@ +from faker import Faker + +from time_tracker.time_entries._domain import TimeEntryService + +def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): + expected_time_entry = mocker.Mock() + time_entry_dao = mocker.Mock( + create=mocker.Mock(return_value=expected_time_entry) + ) + time_entry_service = TimeEntryService(time_entry_dao) + + actual_time_entry = time_entry_service.create(Faker().pydict()) + + assert time_entry_dao.create.called + assert expected_time_entry == actual_time_entry \ No newline at end of file diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py new file mode 100644 index 00000000..e0b2d371 --- /dev/null +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -0,0 +1,20 @@ +from faker import Faker + +from pytest_mock import MockFixture + +from time_tracker.time_entries._domain import _use_cases + +def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry( + mocker: MockFixture, +): + expected_time_entry = mocker.Mock() + time_entry_service = mocker.Mock( + create=mocker.Mock(return_value=expected_time_entry) + ) + + time_entry_use_case = _use_cases.CreateTimeEntryUseCase(time_entry_service) + actual_time_entry = time_entry_use_case.create_time_entry(Faker().pydict()) + + assert time_entry_service.create.called + assert expected_time_entry == actual_time_entry + diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py new file mode 100644 index 00000000..ff94135f --- /dev/null +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -0,0 +1 @@ +from ._time_entries import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py new file mode 100644 index 00000000..6d6f97f1 --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -0,0 +1 @@ +from ._create_time_entry import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py new file mode 100644 index 00000000..e203f16f --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -0,0 +1,49 @@ +import dataclasses +import json +import typing + +import azure.functions as func + +from ... import _domain +from ... import _infrastructure + +_JSON_PATH = ( + 'time_entries/_infrastructure/_data_persistence/time_entries_data.json' +) + +def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: + + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) + + time_entry_data = req.get_json() + + time_entry_to_create = _domain.TimeEntry( + id=None, + start_date=time_entry_data["start_date"], + owner_id=time_entry_data["owner_id"], + description=time_entry_data["description"], + activity_id=time_entry_data["activity_id"], + uri=time_entry_data["uri"], + technologies=time_entry_data["technologies"], + end_date=time_entry_data["end_date"], + deleted=time_entry_data["deleted"], + timezone_offset=time_entry_data["timezone_offset"], + project_id=time_entry_data["project_id"] + ) + + created_time_entry = use_case.create_time_entry(time_entry_to_create.__dict__) + + if not created_time_entry: + return func.HttpResponse( + body=json.dumps({'error': 'time_entry could not be created'}), + status_code=500, + mimetype="application/json" + ) + + return func.HttpResponse( + body=json.dumps(created_time_entry.__dict__), + status_code=201, + mimetype="application/json" + ) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py new file mode 100644 index 00000000..8d430493 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -0,0 +1,6 @@ +from ._entities import TimeEntry +from ._persistence_contracts import TimeEntriesDao +from ._services import TimeEntryService +from ._use_cases import ( + CreateTimeEntryUseCase, +) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/__init__.py b/V2/time_tracker/time_entries/_domain/_entities/__init__.py new file mode 100644 index 00000000..da523368 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -0,0 +1 @@ +from ._time_entry import TimeEntry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py new file mode 100644 index 00000000..f5c3c573 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class TimeEntry: + id: int + start_date: str + owner_id: int + description: str + activity_id: int + uri: str + technologies: List[str] + end_date: str + deleted: str + timezone_offset: str + project_id: int \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py new file mode 100644 index 00000000..ccf8230e --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -0,0 +1 @@ +from ._time_entries_dao import TimeEntriesDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py new file mode 100644 index 00000000..c4475fe4 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -0,0 +1,7 @@ +import abc + +from time_tracker.time_entries._domain import TimeEntry + +class TimeEntriesDao(abc.ABC): + def create(self, time_entry_data: dict) -> TimeEntry: + pass \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/__init__.py b/V2/time_tracker/time_entries/_domain/_services/__init__.py new file mode 100644 index 00000000..5eba7f77 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -0,0 +1 @@ +from ._time_entry import TimeEntryService \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py new file mode 100644 index 00000000..2b41b1f5 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -0,0 +1,9 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntriesDao + +class TimeEntryService: + + def __init__(self, time_entry_dao: TimeEntriesDao): + self.time_entry_dao = time_entry_dao + + def create(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_dao.create(time_entry_data) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py new file mode 100644 index 00000000..8c917202 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -0,0 +1 @@ +from ._create_time_entry_use_case import CreateTimeEntryUseCase \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py new file mode 100644 index 00000000..1df7be91 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -0,0 +1,9 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + +class CreateTimeEntryUseCase: + + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def create_time_entry(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_service.create(time_entry_data) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/__init__.py b/V2/time_tracker/time_entries/_infrastructure/__init__.py new file mode 100644 index 00000000..89ae5c03 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -0,0 +1 @@ +from ._data_persistence import TimeEntriesJsonDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py new file mode 100644 index 00000000..d0db4d15 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -0,0 +1 @@ +from ._time_entries_dao import TimeEntriesJsonDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py new file mode 100644 index 00000000..8d0152f0 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -0,0 +1,38 @@ +import json +import dataclasses +import typing + +from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry + +class TimeEntriesJsonDao(TimeEntriesDao): + + def __init__(self, json_data_file_path: str): + self.json_data_file_path = json_data_file_path + self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] + + def create(self, time_entry_data: dict) -> TimeEntry: + time_entries = self.__get_time_entries_from_file() + time_entries.append(time_entry_data) + + try: + with open(self.json_data_file_path, 'w') as outfile: + json.dump(time_entries, outfile) + + return self.__create_time_entry_dto(time_entry_data) + except FileNotFoundError: + print("Can not create activity") + + def __get_time_entries_from_file(self) -> typing.List[dict]: + try: + file = open(self.json_data_file_path) + time_entries = json.load(file) + file.close() + + return time_entries + + except FileNotFoundError: + return [] + + def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: + time_entry = {key: time_entry.get(key) for key in self.time_entry_key} + return TimeEntry(**time_entry) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json new file mode 100644 index 00000000..2bc98c92 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -0,0 +1,92 @@ +[{ + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}] \ No newline at end of file diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py new file mode 100644 index 00000000..94349dcc --- /dev/null +++ b/V2/time_tracker/time_entries/interface.py @@ -0,0 +1 @@ +from ._application import create_time_entry \ No newline at end of file From 42cfc460663873bd46ada4bcda085e31eb215523 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:04:18 -0500 Subject: [PATCH 02/12] feat: TT-401 validated request create time entry --- V2/tests/api/api_fixtures.py | 80 ------------ .../azure/time_entry_azure_endpoints_test.py | 12 +- V2/tests/conftest.py | 5 +- V2/tests/fixtures.py | 116 ++++++++++++++++++ .../integration/daos/time_entries_dao.test.py | 20 ++- .../unit/services/time_entry_service_test.py | 3 +- .../use_cases/time_entries_use_case_test.py | 8 +- .../time_entries/_application/__init__.py | 1 + .../_application/_time_entries/__init__.py | 1 + .../_time_entries/_create_time_entry.py | 79 +++++++----- .../time_entries/_domain/__init__.py | 1 + .../_domain/_entities/__init__.py | 1 + .../_domain/_entities/_time_entry.py | 5 +- .../_persistence_contracts/__init__.py | 1 + .../_time_entries_dao.py | 3 +- .../_domain/_services/__init__.py | 1 + .../_domain/_services/_time_entry.py | 11 +- .../_domain/_use_cases/__init__.py | 3 +- .../_use_cases/_create_time_entry_use_case.py | 11 +- .../time_entries/_infrastructure/__init__.py | 3 +- .../_data_persistence/__init__.py | 3 +- .../_data_persistence/_time_entries_dao.py | 31 ++--- .../_data_persistence/time_entries_data.json | 93 +------------- V2/time_tracker/time_entries/interface.py | 1 + 24 files changed, 233 insertions(+), 260 deletions(-) delete mode 100644 V2/tests/api/api_fixtures.py create mode 100644 V2/tests/fixtures.py diff --git a/V2/tests/api/api_fixtures.py b/V2/tests/api/api_fixtures.py deleted file mode 100644 index 572bc361..00000000 --- a/V2/tests/api/api_fixtures.py +++ /dev/null @@ -1,80 +0,0 @@ -import json -import pytest -import shutil - - -@pytest.fixture -def create_temp_activities(tmpdir_factory): - temporary_directory = tmpdir_factory.mktemp("tmp") - json_file = temporary_directory.join("activities.json") - activities = [ - { - 'id': 'c61a4a49-3364-49a3-a7f7-0c5f2d15072b', - 'name': 'Development', - 'description': 'Development', - 'deleted': 'b4327ba6-9f96-49ee-a9ac-3c1edf525172', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - { - 'id': '94ec92e2-a500-4700-a9f6-e41eb7b5507c', - 'name': 'Management', - 'description': 'Description of management', - 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - { - 'id': 'd45c770a-b1a0-4bd8-a713-22c01a23e41b', - 'name': 'Operations', - 'description': 'Operation activities performed.', - 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - ] - - with open(json_file, 'w') as outfile: - json.dump(activities, outfile) - - yield activities, json_file - shutil.rmtree(temporary_directory) - -@pytest.fixture -def create_temp_time_entries(tmpdir_factory): - temporary_directory = tmpdir_factory.mktemp("tmp") - json_file = temporary_directory.join("time_entries.json") - time_entries = [ - { - "id" : 1, - "start_date" : "12/07/2021", - "owner_id" : 2, - "description" : "create time entry", - "activity_id" : 2, - "uri" : "http://timetracker.com", - "technologies" : ["git","jira","python"], - "end_date" : "12/07/2021", - "deleted" : "1", - "timezone_offset" : "300", - "project_id" : 1 - }, - { - "id" : 1, - "start_date" : "12/07/2021", - "owner_id" : 2, - "description" : "create activity", - "activity_id" : 2, - "uri" : "http://timetracker.com", - "technologies" : ["git","jira","python"], - "end_date" : "12/07/2021", - "deleted" : "1", - "timezone_offset" : "300", - "project_id" : 1 - }, - ] - - with open(json_file, 'w') as outfile: - json.dump(time_entries, outfile) - - yield time_entries, json_file - shutil.rmtree(temporary_directory) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 7d27b800..8a56e7e6 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -15,15 +15,15 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ time_entries._create_time_entry._JSON_PATH = tmp_directory time_entry_body = { - "id" : None, - "start_date" : Faker().date(), - "owner_id" : Faker().random_int(), + "id": None, + "start_date": Faker().date(), + "owner_id": Faker().random_int(), "description": Faker().sentence(), - "activity_id" : Faker().random_int(), + "activity_id": Faker().random_int(), "uri": "http://timetracker.com", - "technologies" : ["jira","git"], + "technologies": ["jira", "git"], "end_date": Faker().date(), - "deleted": Faker().random_int(), + "deleted": False, "timezone_offset": "300", "project_id": Faker().random_int(), } diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 5dcfc318..e4237e13 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,3 +1,4 @@ # flake8: noqa -from tests.api.api_fixtures import create_temp_activities -from tests.api.api_fixtures import create_temp_time_entries +from fixtures import create_temp_activities +from fixtures import create_temp_time_entries +from fixtures import _time_entry_factory diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py new file mode 100644 index 00000000..8924e88a --- /dev/null +++ b/V2/tests/fixtures.py @@ -0,0 +1,116 @@ +import json +import pytest +import shutil +from faker import Faker + +from time_tracker.time_entries._domain import TimeEntry + + +@pytest.fixture +def create_temp_activities(tmpdir_factory): + temporary_directory = tmpdir_factory.mktemp("tmp") + json_file = temporary_directory.join("activities.json") + activities = [ + { + 'id': 'c61a4a49-3364-49a3-a7f7-0c5f2d15072b', + 'name': 'Development', + 'description': 'Development', + 'deleted': 'b4327ba6-9f96-49ee-a9ac-3c1edf525172', + 'status': 'active', + 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + }, + { + 'id': '94ec92e2-a500-4700-a9f6-e41eb7b5507c', + 'name': 'Management', + 'description': 'Description of management', + 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', + 'status': 'active', + 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + }, + { + 'id': 'd45c770a-b1a0-4bd8-a713-22c01a23e41b', + 'name': 'Operations', + 'description': 'Operation activities performed.', + 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', + 'status': 'active', + 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', + }, + ] + + with open(json_file, 'w') as outfile: + json.dump(activities, outfile) + + yield activities, json_file + shutil.rmtree(temporary_directory) + + +@pytest.fixture +def create_temp_time_entries(tmpdir_factory): + temporary_directory = tmpdir_factory.mktemp("tmp") + json_file = temporary_directory.join("time_entries.json") + time_entries = [ + { + "id": Faker().random_int(), + "start_date": Faker().date(), + "owner_id": Faker().random_int(), + "description": Faker().sentence(), + "activity_id": Faker().random_int(), + "uri": "http://time-tracker.com", + "technologies": ["jira", "git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "300", + "project_id": Faker().random_int(), + }, + { + "id": Faker().random_int(), + "start_date": Faker().date(), + "owner_id": Faker().random_int(), + "description": Faker().sentence(), + "activity_id": Faker().random_int(), + "uri": "http://time-tracker.com", + "technologies": ["jira", "git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "300", + "project_id": Faker().random_int(), + }, + ] + + with open(json_file, 'w') as outfile: + json.dump(time_entries, outfile) + + yield time_entries, json_file + shutil.rmtree(temporary_directory) + + +@pytest.fixture(name='time_entry_factory') +def _time_entry_factory() -> TimeEntry: + def _make_time_entry( + id=Faker().random_int(), + start_date=Faker().date(), + owner_id=Faker().random_int(), + description=Faker().sentence(), + activity_id=Faker().random_int(), + uri="http://time-tracker.com", + technologies=["jira", "git"], + end_date=Faker().date(), + deleted=Faker().random_int(), + timezone_offset="300", + project_id=Faker().random_int(), + ): + time_entry = TimeEntry( + id=id, + start_date=start_date, + owner_id=owner_id, + description=description, + activity_id=activity_id, + uri=uri, + technologies=technologies, + end_date=end_date, + deleted=deleted, + timezone_offset=timezone_offset, + project_id=project_id, + ) + return time_entry + return _make_time_entry diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 9947061a..6f9b3e6f 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -25,21 +25,17 @@ def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) time_entry_data = { - "id" : None, - "start_date" : Faker().date(), - "owner_id" : Faker().random_int(), + "id": None, + "start_date": Faker().date(), + "owner_id": Faker().random_int(), "description": Faker().sentence(), - "activity_id" : Faker().random_int(), - "uri": "http://hola.com", - "technologies" : ["jira","git"], + "activity_id": Faker().random_int(), + "uri": "http://timetracker.com", + "technologies": ["jira", "git"], "end_date": Faker().date(), - "deleted": Faker().random_int(), - "timezone_offset": "UTC-5", + "deleted": False, + "timezone_offset": "300", "project_id": Faker().random_int(), } result = time_entries_json_dao.create(time_entry_data) assert result == TimeEntry(**time_entry_data) - - - - diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index d844d45d..5274adbb 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -2,6 +2,7 @@ from time_tracker.time_entries._domain import TimeEntryService + def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): expected_time_entry = mocker.Mock() time_entry_dao = mocker.Mock( @@ -12,4 +13,4 @@ def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry( actual_time_entry = time_entry_service.create(Faker().pydict()) assert time_entry_dao.create.called - assert expected_time_entry == actual_time_entry \ No newline at end of file + assert expected_time_entry == actual_time_entry diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index e0b2d371..d2a31eb7 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -1,11 +1,10 @@ -from faker import Faker - from pytest_mock import MockFixture from time_tracker.time_entries._domain import _use_cases + def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry( - mocker: MockFixture, + mocker: MockFixture, time_entry_factory ): expected_time_entry = mocker.Mock() time_entry_service = mocker.Mock( @@ -13,8 +12,7 @@ def test__create_time_entry_function__uses_the_time_entries_service__to_create_t ) time_entry_use_case = _use_cases.CreateTimeEntryUseCase(time_entry_service) - actual_time_entry = time_entry_use_case.create_time_entry(Faker().pydict()) + actual_time_entry = time_entry_use_case.create_time_entry(time_entry_factory()) assert time_entry_service.create.called assert expected_time_entry == actual_time_entry - diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py index ff94135f..6e4ba9c3 100644 --- a/V2/time_tracker/time_entries/_application/__init__.py +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entries import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py index 6d6f97f1..b46cddce 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._create_time_entry import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py index e203f16f..9d1ed223 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -8,42 +8,59 @@ from ... import _infrastructure _JSON_PATH = ( - 'time_entries/_infrastructure/_data_persistence/time_entries_data.json' + 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' ) + def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: - time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) - time_entry_service = _domain.TimeEntryService(time_entry_dao) - use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) - - time_entry_data = req.get_json() - - time_entry_to_create = _domain.TimeEntry( - id=None, - start_date=time_entry_data["start_date"], - owner_id=time_entry_data["owner_id"], - description=time_entry_data["description"], - activity_id=time_entry_data["activity_id"], - uri=time_entry_data["uri"], - technologies=time_entry_data["technologies"], - end_date=time_entry_data["end_date"], - deleted=time_entry_data["deleted"], - timezone_offset=time_entry_data["timezone_offset"], - project_id=time_entry_data["project_id"] - ) - - created_time_entry = use_case.create_time_entry(time_entry_to_create.__dict__) - - if not created_time_entry: + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) + + time_entry_data = req.get_json() + + validation_errors = _validate_time_entry(time_entry_data) + if validation_errors: + return func.HttpResponse( + body=json.dumps(validation_errors), status_code=400, mimetype="application/json" + ) + + time_entry_to_create = _domain.TimeEntry( + id=None, + start_date=time_entry_data["start_date"], + owner_id=time_entry_data["owner_id"], + description=time_entry_data["description"], + activity_id=time_entry_data["activity_id"], + uri=time_entry_data["uri"], + technologies=time_entry_data["technologies"], + end_date=time_entry_data["end_date"], + deleted=False, + timezone_offset=time_entry_data["timezone_offset"], + project_id=time_entry_data["project_id"] + ) + + created_time_entry = use_case.create_time_entry(time_entry_to_create) + + if not created_time_entry: + return func.HttpResponse( + body=json.dumps({'error': 'time_entry could not be created'}), + status_code=500, + mimetype="application/json" + ) + return func.HttpResponse( - body=json.dumps({'error': 'time_entry could not be created'}), - status_code=500, + body=json.dumps(created_time_entry.__dict__), + status_code=201, mimetype="application/json" ) - return func.HttpResponse( - body=json.dumps(created_time_entry.__dict__), - status_code=201, - mimetype="application/json" - ) \ No newline at end of file + +def _validate_time_entry(time_entry_data: dict) -> typing.List[str]: + time_entry_fields = [field.name for field in dataclasses.fields(_domain.TimeEntry)] + time_entry_fields.pop(8) + missing_keys = [field for field in time_entry_fields if field not in time_entry_data] + return [ + f'The {missing_key} key is missing in the input data' + for missing_key in missing_keys + ] diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py index 8d430493..a8b2081c 100644 --- a/V2/time_tracker/time_entries/_domain/__init__.py +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._entities import TimeEntry from ._persistence_contracts import TimeEntriesDao from ._services import TimeEntryService diff --git a/V2/time_tracker/time_entries/_domain/_entities/__init__.py b/V2/time_tracker/time_entries/_domain/_entities/__init__.py index da523368..88b4a739 100644 --- a/V2/time_tracker/time_entries/_domain/_entities/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entry import TimeEntry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py index f5c3c573..aa73a879 100644 --- a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import List + @dataclass(frozen=True) class TimeEntry: id: int @@ -11,6 +12,6 @@ class TimeEntry: uri: str technologies: List[str] end_date: str - deleted: str + deleted: bool timezone_offset: str - project_id: int \ No newline at end of file + project_id: int diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py index ccf8230e..e10700ce 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entries_dao import TimeEntriesDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index c4475fe4..aba78170 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -2,6 +2,7 @@ from time_tracker.time_entries._domain import TimeEntry + class TimeEntriesDao(abc.ABC): def create(self, time_entry_data: dict) -> TimeEntry: - pass \ No newline at end of file + pass diff --git a/V2/time_tracker/time_entries/_domain/_services/__init__.py b/V2/time_tracker/time_entries/_domain/_services/__init__.py index 5eba7f77..e5e6ba1b 100644 --- a/V2/time_tracker/time_entries/_domain/_services/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entry import TimeEntryService \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index 2b41b1f5..368a4780 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -1,9 +1,10 @@ from time_tracker.time_entries._domain import TimeEntry, TimeEntriesDao + class TimeEntryService: - - def __init__(self, time_entry_dao: TimeEntriesDao): - self.time_entry_dao = time_entry_dao - def create(self, time_entry_data: dict) -> TimeEntry: - return self.time_entry_dao.create(time_entry_data) \ No newline at end of file + def __init__(self, time_entry_dao: TimeEntriesDao): + self.time_entry_dao = time_entry_dao + + def create(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_dao.create(time_entry_data) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py index 8c917202..41aca738 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -1 +1,2 @@ -from ._create_time_entry_use_case import CreateTimeEntryUseCase \ No newline at end of file +# flake8: noqa +from ._create_time_entry_use_case import CreateTimeEntryUseCase diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py index 1df7be91..73c32d81 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -1,9 +1,10 @@ from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + class CreateTimeEntryUseCase: - def __init__(self, time_entry_service: TimeEntryService): - self.time_entry_service = time_entry_service - - def create_time_entry(self, time_entry_data: dict) -> TimeEntry: - return self.time_entry_service.create(time_entry_data) \ No newline at end of file + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def create_time_entry(self, time_entry_data: TimeEntry) -> TimeEntry: + return self.time_entry_service.create(time_entry_data.__dict__) diff --git a/V2/time_tracker/time_entries/_infrastructure/__init__.py b/V2/time_tracker/time_entries/_infrastructure/__init__.py index 89ae5c03..0e90c78a 100644 --- a/V2/time_tracker/time_entries/_infrastructure/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -1 +1,2 @@ -from ._data_persistence import TimeEntriesJsonDao \ No newline at end of file +# flake8: noqa +from ._data_persistence import TimeEntriesJsonDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py index d0db4d15..91ebd7cf 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -1 +1,2 @@ -from ._time_entries_dao import TimeEntriesJsonDao \ No newline at end of file +# flake8: noqa +from ._time_entries_dao import TimeEntriesJsonDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index 8d0152f0..84e8f200 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -4,13 +4,14 @@ from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry + class TimeEntriesJsonDao(TimeEntriesDao): - - def __init__(self, json_data_file_path: str): - self.json_data_file_path = json_data_file_path - self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] - def create(self, time_entry_data: dict) -> TimeEntry: + def __init__(self, json_data_file_path: str): + self.json_data_file_path = json_data_file_path + self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] + + def create(self, time_entry_data: dict) -> TimeEntry: time_entries = self.__get_time_entries_from_file() time_entries.append(time_entry_data) @@ -22,17 +23,17 @@ def create(self, time_entry_data: dict) -> TimeEntry: except FileNotFoundError: print("Can not create activity") - def __get_time_entries_from_file(self) -> typing.List[dict]: - try: - file = open(self.json_data_file_path) - time_entries = json.load(file) - file.close() + def __get_time_entries_from_file(self) -> typing.List[dict]: + try: + file = open(self.json_data_file_path) + time_entries = json.load(file) + file.close() - return time_entries + return time_entries - except FileNotFoundError: - return [] + except FileNotFoundError: + return [] - def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: + def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: time_entry = {key: time_entry.get(key) for key in self.time_entry_key} - return TimeEntry(**time_entry) \ No newline at end of file + return TimeEntry(**time_entry) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json index 2bc98c92..0ebbd0e7 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -1,92 +1 @@ -[{ - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}] \ No newline at end of file +[{"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}] \ No newline at end of file diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py index 94349dcc..d0182780 100644 --- a/V2/time_tracker/time_entries/interface.py +++ b/V2/time_tracker/time_entries/interface.py @@ -1 +1,2 @@ +# flake8: noqa from ._application import create_time_entry \ No newline at end of file From bf76dafb2d6eea9f344c4eb9599135a1712b85cf Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:47:20 -0500 Subject: [PATCH 03/12] fix: TT-401 implemented faker url --- .../azure/time_entry_azure_endpoints_test.py | 17 +-------- V2/tests/fixtures.py | 21 ++--------- .../integration/daos/time_entries_dao.test.py | 18 ++------- .../_time_entries_dao.py | 1 + .../_data_persistence/time_entries_data.json | 37 ++++++++++++++++++- 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 8a56e7e6..4477052a 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,5 +1,4 @@ from time_tracker.time_entries._application import _time_entries as time_entries -from faker import Faker import azure.functions as func import json @@ -9,24 +8,12 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( - create_temp_time_entries, + create_temp_time_entries, time_entry_factory ): time_entries_json, tmp_directory = create_temp_time_entries time_entries._create_time_entry._JSON_PATH = tmp_directory - time_entry_body = { - "id": None, - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": "http://timetracker.com", - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": False, - "timezone_offset": "300", - "project_id": Faker().random_int(), - } + time_entry_body = time_entry_factory(None).__dict__ body = json.dumps(time_entry_body).encode("utf-8") req = func.HttpRequest( method="POST", diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index 8924e88a..7df85e02 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -55,26 +55,13 @@ def create_temp_time_entries(tmpdir_factory): "owner_id": Faker().random_int(), "description": Faker().sentence(), "activity_id": Faker().random_int(), - "uri": "http://time-tracker.com", + "uri": Faker().domain_name(), "technologies": ["jira", "git"], "end_date": Faker().date(), "deleted": Faker().random_int(), "timezone_offset": "300", "project_id": Faker().random_int(), - }, - { - "id": Faker().random_int(), - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": "http://time-tracker.com", - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": Faker().random_int(), - "timezone_offset": "300", - "project_id": Faker().random_int(), - }, + } ] with open(json_file, 'w') as outfile: @@ -92,10 +79,10 @@ def _make_time_entry( owner_id=Faker().random_int(), description=Faker().sentence(), activity_id=Faker().random_int(), - uri="http://time-tracker.com", + uri=Faker().domain_name(), technologies=["jira", "git"], end_date=Faker().date(), - deleted=Faker().random_int(), + deleted=False, timezone_offset="300", project_id=Faker().random_int(), ): diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 6f9b3e6f..db32d08f 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -19,23 +19,11 @@ def _creator(time_entries): def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( - create_fake_time_entries, + create_fake_time_entries, time_entry_factory ): create_fake_time_entries([]) time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - time_entry_data = { - "id": None, - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": "http://timetracker.com", - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": False, - "timezone_offset": "300", - "project_id": Faker().random_int(), - } - result = time_entries_json_dao.create(time_entry_data) + time_entry_data = time_entry_factory() + result = time_entries_json_dao.create(time_entry_data.__dict__) assert result == TimeEntry(**time_entry_data) diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index aba78170..f4c58bbd 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -4,5 +4,6 @@ class TimeEntriesDao(abc.ABC): + @abc.abstractmethod def create(self, time_entry_data: dict) -> TimeEntry: pass diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json index 0ebbd0e7..60dc4aa2 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -1 +1,36 @@ -[{"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}] \ No newline at end of file +[ + { + "id": 1, + "start_date": "12/07/2021", + "owner_id": 2, + "description": "Review system of Time Tracker", + "activity_id": 2, + "uri": "http://time_tracker.com", + "technologies": [ + "git", + "jira", + "python" + ], + "end_date": "12/07/2021", + "deleted": false, + "timezone_offset": "300", + "project_id": 1 + }, + { + "id": 2, + "start_date": "12/07/2021", + "owner_id": 2, + "description": "Review system of doors in the ioet office Loja, change the current raspberry pi ", + "activity_id": 2, + "uri": null, + "technologies": [ + "git", + "jira", + "python" + ], + "end_date": "12/07/2021", + "deleted": false, + "timezone_offset": "300", + "project_id": 1 + } +] \ No newline at end of file From d5caf068ec70148b355307961dcfde449cf43536 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 15:31:31 -0500 Subject: [PATCH 04/12] feat: TT-403 created end-point to DELETE of time_entries --- V2/serverless.yml | 10 +++++ .../azure/time_entry_azure_endpoints_test.py | 41 ++++++++++++++++++- V2/tests/fixtures.py | 2 +- .../integration/daos/time_entries_dao.test.py | 27 ++++++++++++ .../unit/services/time_entry_service_test.py | 15 +++++++ .../use_cases/time_entries_use_case_test.py | 14 +++++++ .../time_entries/_application/__init__.py | 3 +- .../_application/_time_entries/__init__.py | 3 +- .../_time_entries/_delete_time_entry.py | 41 +++++++++++++++++++ .../time_entries/_domain/__init__.py | 1 + .../_time_entries_dao.py | 4 ++ .../_domain/_services/_time_entry.py | 3 ++ .../_domain/_use_cases/__init__.py | 1 + .../_use_cases/_delete_time_entry_use_case.py | 10 +++++ .../_data_persistence/_time_entries_dao.py | 31 ++++++++++++++ V2/time_tracker/time_entries/interface.py | 3 +- 16 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py diff --git a/V2/serverless.yml b/V2/serverless.yml index c6c5e34b..fc5942d9 100644 --- a/V2/serverless.yml +++ b/V2/serverless.yml @@ -86,3 +86,13 @@ functions: - POST route: time-entries/ authLevel: anonymous + + delete_time_entry: + handler: time_tracker/time_entries/interface.delete_time_entry + events: + - http: true + x-azure-settings: + methods: + - DELETE + route: time-entries/{id} + authLevel: anonymous diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 4477052a..566de7e1 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -4,7 +4,7 @@ import json -ACTIVITY_URL = "/api/time-entries/" +TIME_ENTRY_URL = "/api/time-entries/" def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( @@ -18,10 +18,47 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ req = func.HttpRequest( method="POST", body=body, - url=ACTIVITY_URL, + url=TIME_ENTRY_URL, ) response = time_entries.create_time_entry(req) time_entry_json_data = response.get_body() assert response.status_code == 201 assert time_entry_json_data == body + + +def test__time_entry_azure_endpoint__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( + create_temp_time_entries, +): + time_entries_json, tmp_directory = create_temp_time_entries + time_entries.delete_time_entry.JSON_PATH = tmp_directory + req = func.HttpRequest( + method="DELETE", + body=None, + url=TIME_ENTRY_URL, + route_params={"id": time_entries_json[0]["id"]}, + ) + + response = time_entries.delete_time_entry(req) + time_entry_json_data = json.loads(response.get_body().decode("utf-8")) + + assert response.status_code == 200 + assert time_entry_json_data["deleted"] is True + + +def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id( + create_temp_time_entries, +): + tmp_directory = create_temp_time_entries + time_entries.delete_time_entry.JSON_PATH = tmp_directory + req = func.HttpRequest( + method="DELETE", + body=None, + url=TIME_ENTRY_URL, + route_params={"id": "invalid id"}, + ) + + response = time_entries.delete_time_entry(req) + + assert response.status_code == 400 + assert response.get_body() == b'Invalid Format ID' \ No newline at end of file diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index 7df85e02..ed6517f3 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -50,7 +50,7 @@ def create_temp_time_entries(tmpdir_factory): json_file = temporary_directory.join("time_entries.json") time_entries = [ { - "id": Faker().random_int(), + "id": 1, "start_date": Faker().date(), "owner_id": Faker().random_int(), "description": Faker().sentence(), diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index db32d08f..22e495c5 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -27,3 +27,30 @@ def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry time_entry_data = time_entry_factory() result = time_entries_json_dao.create(time_entry_data.__dict__) assert result == TimeEntry(**time_entry_data) + + +def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( + create_fake_time_entries, time_entry_factory +): + time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) + time_entries = create_fake_time_entries( + [ + time_entry_factory().__dict__ + ] + ) + + time_entry_dto = time_entries[0] + result = time_entries_json_dao.delete(time_entry_dto.id) + + assert result.deleted == True + + +def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( + create_fake_time_entries, +): + time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) + create_fake_time_entries([]) + + result = time_entries_json_dao.delete(Faker().pyint()) + + assert result is None diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index 5274adbb..396a6fe1 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -14,3 +14,18 @@ def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry( assert time_entry_dao.create.called assert expected_time_entry == actual_time_entry + + +def test__delete_time_entry__uses_the_time_entry_dao__to_change_time_entry_deleted( + mocker, +): + expected_time_entry = mocker.Mock() + time_entry_dao = mocker.Mock( + delete=mocker.Mock(return_value=expected_time_entry) + ) + + time_entry_service = TimeEntryService(time_entry_dao) + deleted_time_entry = time_entry_service.delete(Faker().pyint()) + + assert time_entry_dao.delete.called + assert expected_time_entry == deleted_time_entry \ No newline at end of file diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index d2a31eb7..76f7bc6d 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -1,4 +1,5 @@ from pytest_mock import MockFixture +from faker import Faker from time_tracker.time_entries._domain import _use_cases @@ -16,3 +17,16 @@ def test__create_time_entry_function__uses_the_time_entries_service__to_create_t assert time_entry_service.create.called assert expected_time_entry == actual_time_entry + + +def test__delete_time_entry_function__uses_the_time_entry_service__to_change_time_entry_deleted( + mocker: MockFixture, +): + expected_time_entry = mocker.Mock() + time_entry_service = mocker.Mock(delete=mocker.Mock(return_value=expected_time_entry)) + + time_entry_use_case = _use_cases.DeleteTimeEntryUseCase(time_entry_service) + deleted_time_entry = time_entry_use_case.delete_time_entry(Faker().pyint()) + + assert time_entry_service.delete.called + assert expected_time_entry == deleted_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py index 6e4ba9c3..30287f8f 100644 --- a/V2/time_tracker/time_entries/_application/__init__.py +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -1,2 +1,3 @@ # flake8: noqa -from ._time_entries import create_time_entry \ No newline at end of file +from ._time_entries import create_time_entry +from ._time_entries import delete_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py index b46cddce..4cb4d4b0 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -1,2 +1,3 @@ # flake8: noqa -from ._create_time_entry import create_time_entry \ No newline at end of file +from ._create_time_entry import create_time_entry +from ._delete_time_entry import delete_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py new file mode 100644 index 00000000..0eb1a0ea --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py @@ -0,0 +1,41 @@ +import json + +import azure.functions as func + +from ... import _domain +from ... import _infrastructure + +_JSON_PATH = ( + 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' +) + + +def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse: + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.DeleteTimeEntryUseCase(time_entry_service) + + try: + time_entry_id = int(req.route_params.get("id")) + + deleted_time_entry = use_case.delete_time_entry(time_entry_id) + if not deleted_time_entry: + return func.HttpResponse( + body='Not found', + status_code=404, + mimetype="application/json" + ) + + return func.HttpResponse( + body=json.dumps(deleted_time_entry.__dict__), + status_code=200, + mimetype="application/json" + ) + + except ValueError: + return func.HttpResponse( + body=b'Invalid Format ID', + status_code=400, + mimetype="application/json" + ) + diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py index a8b2081c..ad927811 100644 --- a/V2/time_tracker/time_entries/_domain/__init__.py +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -4,4 +4,5 @@ from ._services import TimeEntryService from ._use_cases import ( CreateTimeEntryUseCase, + DeleteTimeEntryUseCase ) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index f4c58bbd..7ebb4de9 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -7,3 +7,7 @@ class TimeEntriesDao(abc.ABC): @abc.abstractmethod def create(self, time_entry_data: dict) -> TimeEntry: pass + + @abc.abstractmethod + def delete(self, id: int) -> TimeEntry: + pass diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index 368a4780..af0ffc62 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -8,3 +8,6 @@ def __init__(self, time_entry_dao: TimeEntriesDao): def create(self, time_entry_data: dict) -> TimeEntry: return self.time_entry_dao.create(time_entry_data) + + def delete(self, time_entry_id: int) -> TimeEntry: + return self.time_entry_dao.delete(time_entry_id) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py index 41aca738..17b2442a 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -1,2 +1,3 @@ # flake8: noqa from ._create_time_entry_use_case import CreateTimeEntryUseCase +from ._delete_time_entry_use_case import DeleteTimeEntryUseCase diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py new file mode 100644 index 00000000..a195c303 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_delete_time_entry_use_case.py @@ -0,0 +1,10 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + + +class DeleteTimeEntryUseCase: + + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def delete_time_entry(self, id: int) -> TimeEntry: + return self.time_entry_service.delete(id) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index 84e8f200..b62f8d7a 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -37,3 +37,34 @@ def __get_time_entries_from_file(self) -> typing.List[dict]: def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: time_entry = {key: time_entry.get(key) for key in self.time_entry_key} return TimeEntry(**time_entry) + + def delete(self, time_entry_id: int) -> TimeEntry: + time_entry = { + time_entry.get('id'): time_entry + for time_entry in self.__get_time_entries_from_file() + }.get(int(time_entry_id)) + + if time_entry: + time_entry_deleted = {**time_entry, 'deleted': True} + + time_entries_updated = list( + map( + lambda time_entry: time_entry + if time_entry.get('id') != time_entry_id + else time_entry_deleted, + self.__get_time_entries_from_file(), + ) + ) + + try: + file = open(self.json_data_file_path, 'w') + json.dump(time_entries_updated, file) + file.close() + + return self.__create_time_entry_dto(time_entry_deleted) + + except FileNotFoundError: + return None + + else: + return None diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py index d0182780..773314bb 100644 --- a/V2/time_tracker/time_entries/interface.py +++ b/V2/time_tracker/time_entries/interface.py @@ -1,2 +1,3 @@ # flake8: noqa -from ._application import create_time_entry \ No newline at end of file +from ._application import create_time_entry +from ._application import delete_time_entry \ No newline at end of file From da5929d48a6ec66f1bfa154f985307ca8f6e825d Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 16:15:05 -0500 Subject: [PATCH 05/12] fix: TT-403 validation of id as integer --- .../azure/time_entry_azure_endpoints_test.py | 4 +- .../integration/daos/time_entries_dao.test.py | 2 +- .../unit/services/time_entry_service_test.py | 2 +- .../use_cases/time_entries_use_case_test.py | 2 +- .../_time_entries/_delete_time_entry.py | 1 - .../_data_persistence/time_entries_data.json | 37 +------------------ 6 files changed, 6 insertions(+), 42 deletions(-) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 566de7e1..afcbf190 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -27,7 +27,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ assert time_entry_json_data == body -def test__time_entry_azure_endpoint__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( +def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found( create_temp_time_entries, ): time_entries_json, tmp_directory = create_temp_time_entries @@ -61,4 +61,4 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti response = time_entries.delete_time_entry(req) assert response.status_code == 400 - assert response.get_body() == b'Invalid Format ID' \ No newline at end of file + assert response.get_body() == b'Invalid Format ID' diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 22e495c5..98718a20 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -42,7 +42,7 @@ def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_mat time_entry_dto = time_entries[0] result = time_entries_json_dao.delete(time_entry_dto.id) - assert result.deleted == True + assert result.deleted is True def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index 396a6fe1..b17518fd 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -28,4 +28,4 @@ def test__delete_time_entry__uses_the_time_entry_dao__to_change_time_entry_delet deleted_time_entry = time_entry_service.delete(Faker().pyint()) assert time_entry_dao.delete.called - assert expected_time_entry == deleted_time_entry \ No newline at end of file + assert expected_time_entry == deleted_time_entry diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index 76f7bc6d..0007fb59 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -29,4 +29,4 @@ def test__delete_time_entry_function__uses_the_time_entry_service__to_change_tim deleted_time_entry = time_entry_use_case.delete_time_entry(Faker().pyint()) assert time_entry_service.delete.called - assert expected_time_entry == deleted_time_entry \ No newline at end of file + assert expected_time_entry == deleted_time_entry diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py index 0eb1a0ea..5664b1e6 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py @@ -38,4 +38,3 @@ def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse: status_code=400, mimetype="application/json" ) - diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json index 60dc4aa2..aa3bef88 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -1,36 +1 @@ -[ - { - "id": 1, - "start_date": "12/07/2021", - "owner_id": 2, - "description": "Review system of Time Tracker", - "activity_id": 2, - "uri": "http://time_tracker.com", - "technologies": [ - "git", - "jira", - "python" - ], - "end_date": "12/07/2021", - "deleted": false, - "timezone_offset": "300", - "project_id": 1 - }, - { - "id": 2, - "start_date": "12/07/2021", - "owner_id": 2, - "description": "Review system of doors in the ioet office Loja, change the current raspberry pi ", - "activity_id": 2, - "uri": null, - "technologies": [ - "git", - "jira", - "python" - ], - "end_date": "12/07/2021", - "deleted": false, - "timezone_offset": "300", - "project_id": 1 - } -] \ No newline at end of file +[{"id": 1, "start_date": "12/07/2021", "owner_id": 2, "description": "Review system of Time Tracker", "activity_id": 2, "uri": "http://time_tracker.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": true, "timezone_offset": "300", "project_id": 1}, {"id": 2, "start_date": "12/07/2021", "owner_id": 2, "description": "Review system of doors in the ioet office Loja, change the current raspberry pi ", "activity_id": 2, "uri": null, "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "300", "project_id": 1}] \ No newline at end of file From 9b333e2a02df0c85c8f717c07dd32fe7426cc4c1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 16:27:42 -0500 Subject: [PATCH 06/12] fix: TT-403 remove method POST --- V2/serverless.yml | 10 --- .../azure/time_entry_azure_endpoints_test.py | 20 ------ .../integration/daos/time_entries_dao.test.py | 11 ---- .../unit/services/time_entry_service_test.py | 13 ---- .../use_cases/time_entries_use_case_test.py | 15 ----- .../time_entries/_application/__init__.py | 1 - .../_application/_time_entries/__init__.py | 1 - .../_time_entries/_create_time_entry.py | 66 ------------------- .../time_entries/_domain/__init__.py | 1 - .../_time_entries_dao.py | 4 -- .../_domain/_services/_time_entry.py | 3 - .../_domain/_use_cases/__init__.py | 1 - .../_use_cases/_create_time_entry_use_case.py | 10 --- .../_data_persistence/_time_entries_dao.py | 12 ---- V2/time_tracker/time_entries/interface.py | 1 - 15 files changed, 169 deletions(-) delete mode 100644 V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py delete mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py diff --git a/V2/serverless.yml b/V2/serverless.yml index fc5942d9..dc025f68 100644 --- a/V2/serverless.yml +++ b/V2/serverless.yml @@ -77,16 +77,6 @@ functions: route: activities/ authLevel: anonymous - create_time_entry: - handler: time_tracker/time_entries/interface.create_time_entry - events: - - http: true - x-azure-settings: - methods: - - POST - route: time-entries/ - authLevel: anonymous - delete_time_entry: handler: time_tracker/time_entries/interface.delete_time_entry events: diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index afcbf190..70a70b35 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -7,26 +7,6 @@ TIME_ENTRY_URL = "/api/time-entries/" -def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( - create_temp_time_entries, time_entry_factory -): - time_entries_json, tmp_directory = create_temp_time_entries - time_entries._create_time_entry._JSON_PATH = tmp_directory - - time_entry_body = time_entry_factory(None).__dict__ - body = json.dumps(time_entry_body).encode("utf-8") - req = func.HttpRequest( - method="POST", - body=body, - url=TIME_ENTRY_URL, - ) - - response = time_entries.create_time_entry(req) - time_entry_json_data = response.get_body() - assert response.status_code == 201 - assert time_entry_json_data == body - - def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found( create_temp_time_entries, ): diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 98718a20..49191e62 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -18,17 +18,6 @@ def _creator(time_entries): return _creator -def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( - create_fake_time_entries, time_entry_factory -): - create_fake_time_entries([]) - - time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - time_entry_data = time_entry_factory() - result = time_entries_json_dao.create(time_entry_data.__dict__) - assert result == TimeEntry(**time_entry_data) - - def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( create_fake_time_entries, time_entry_factory ): diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index b17518fd..d13af483 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -3,19 +3,6 @@ from time_tracker.time_entries._domain import TimeEntryService -def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): - expected_time_entry = mocker.Mock() - time_entry_dao = mocker.Mock( - create=mocker.Mock(return_value=expected_time_entry) - ) - time_entry_service = TimeEntryService(time_entry_dao) - - actual_time_entry = time_entry_service.create(Faker().pydict()) - - assert time_entry_dao.create.called - assert expected_time_entry == actual_time_entry - - def test__delete_time_entry__uses_the_time_entry_dao__to_change_time_entry_deleted( mocker, ): diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index 0007fb59..d5095c4f 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -4,21 +4,6 @@ from time_tracker.time_entries._domain import _use_cases -def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry( - mocker: MockFixture, time_entry_factory -): - expected_time_entry = mocker.Mock() - time_entry_service = mocker.Mock( - create=mocker.Mock(return_value=expected_time_entry) - ) - - time_entry_use_case = _use_cases.CreateTimeEntryUseCase(time_entry_service) - actual_time_entry = time_entry_use_case.create_time_entry(time_entry_factory()) - - assert time_entry_service.create.called - assert expected_time_entry == actual_time_entry - - def test__delete_time_entry_function__uses_the_time_entry_service__to_change_time_entry_deleted( mocker: MockFixture, ): diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py index 30287f8f..2f7cebc5 100644 --- a/V2/time_tracker/time_entries/_application/__init__.py +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -1,3 +1,2 @@ # flake8: noqa -from ._time_entries import create_time_entry from ._time_entries import delete_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py index 4cb4d4b0..7203d16d 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -1,3 +1,2 @@ # flake8: noqa -from ._create_time_entry import create_time_entry from ._delete_time_entry import delete_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py deleted file mode 100644 index 9d1ed223..00000000 --- a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py +++ /dev/null @@ -1,66 +0,0 @@ -import dataclasses -import json -import typing - -import azure.functions as func - -from ... import _domain -from ... import _infrastructure - -_JSON_PATH = ( - 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' -) - - -def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: - - time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) - time_entry_service = _domain.TimeEntryService(time_entry_dao) - use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) - - time_entry_data = req.get_json() - - validation_errors = _validate_time_entry(time_entry_data) - if validation_errors: - return func.HttpResponse( - body=json.dumps(validation_errors), status_code=400, mimetype="application/json" - ) - - time_entry_to_create = _domain.TimeEntry( - id=None, - start_date=time_entry_data["start_date"], - owner_id=time_entry_data["owner_id"], - description=time_entry_data["description"], - activity_id=time_entry_data["activity_id"], - uri=time_entry_data["uri"], - technologies=time_entry_data["technologies"], - end_date=time_entry_data["end_date"], - deleted=False, - timezone_offset=time_entry_data["timezone_offset"], - project_id=time_entry_data["project_id"] - ) - - created_time_entry = use_case.create_time_entry(time_entry_to_create) - - if not created_time_entry: - return func.HttpResponse( - body=json.dumps({'error': 'time_entry could not be created'}), - status_code=500, - mimetype="application/json" - ) - - return func.HttpResponse( - body=json.dumps(created_time_entry.__dict__), - status_code=201, - mimetype="application/json" - ) - - -def _validate_time_entry(time_entry_data: dict) -> typing.List[str]: - time_entry_fields = [field.name for field in dataclasses.fields(_domain.TimeEntry)] - time_entry_fields.pop(8) - missing_keys = [field for field in time_entry_fields if field not in time_entry_data] - return [ - f'The {missing_key} key is missing in the input data' - for missing_key in missing_keys - ] diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py index ad927811..3baaf4f0 100644 --- a/V2/time_tracker/time_entries/_domain/__init__.py +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -3,6 +3,5 @@ from ._persistence_contracts import TimeEntriesDao from ._services import TimeEntryService from ._use_cases import ( - CreateTimeEntryUseCase, DeleteTimeEntryUseCase ) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index 7ebb4de9..ea02f002 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -4,10 +4,6 @@ class TimeEntriesDao(abc.ABC): - @abc.abstractmethod - def create(self, time_entry_data: dict) -> TimeEntry: - pass - @abc.abstractmethod def delete(self, id: int) -> TimeEntry: pass diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index af0ffc62..1b7202b0 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -6,8 +6,5 @@ class TimeEntryService: def __init__(self, time_entry_dao: TimeEntriesDao): self.time_entry_dao = time_entry_dao - def create(self, time_entry_data: dict) -> TimeEntry: - return self.time_entry_dao.create(time_entry_data) - def delete(self, time_entry_id: int) -> TimeEntry: return self.time_entry_dao.delete(time_entry_id) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py index 17b2442a..62a40986 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -1,3 +1,2 @@ # flake8: noqa -from ._create_time_entry_use_case import CreateTimeEntryUseCase from ._delete_time_entry_use_case import DeleteTimeEntryUseCase diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py deleted file mode 100644 index 73c32d81..00000000 --- a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py +++ /dev/null @@ -1,10 +0,0 @@ -from time_tracker.time_entries._domain import TimeEntry, TimeEntryService - - -class CreateTimeEntryUseCase: - - def __init__(self, time_entry_service: TimeEntryService): - self.time_entry_service = time_entry_service - - def create_time_entry(self, time_entry_data: TimeEntry) -> TimeEntry: - return self.time_entry_service.create(time_entry_data.__dict__) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index b62f8d7a..e5a74516 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -11,18 +11,6 @@ def __init__(self, json_data_file_path: str): self.json_data_file_path = json_data_file_path self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] - def create(self, time_entry_data: dict) -> TimeEntry: - time_entries = self.__get_time_entries_from_file() - time_entries.append(time_entry_data) - - try: - with open(self.json_data_file_path, 'w') as outfile: - json.dump(time_entries, outfile) - - return self.__create_time_entry_dto(time_entry_data) - except FileNotFoundError: - print("Can not create activity") - def __get_time_entries_from_file(self) -> typing.List[dict]: try: file = open(self.json_data_file_path) diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py index 773314bb..22e1c166 100644 --- a/V2/time_tracker/time_entries/interface.py +++ b/V2/time_tracker/time_entries/interface.py @@ -1,3 +1,2 @@ # flake8: noqa -from ._application import create_time_entry from ._application import delete_time_entry \ No newline at end of file From 45251d2330382f86a665df61fb98d82147363ddc Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 10 Nov 2021 18:37:28 -0500 Subject: [PATCH 07/12] feat: TT-401 Implemented service, end-point, dao, test- time entries --- V2/serverless.yml | 10 ++ .../azure/time_entry_azure_endpoints_test.py | 40 ++++++++ V2/tests/conftest.py | 2 +- .../integration/daos/time_entries_dao.test.py | 45 +++++++++ .../unit/services/time_entry_service_test.py | 15 +++ .../use_cases/time_entries_use_case_test.py | 20 ++++ .../time_entries/_application/__init__.py | 1 + .../_application/_time_entries/__init__.py | 1 + .../_time_entries/_create_time_entry.py | 49 ++++++++++ .../time_entries/_domain/__init__.py | 6 ++ .../_domain/_entities/__init__.py | 1 + .../_domain/_entities/_time_entry.py | 16 ++++ .../_persistence_contracts/__init__.py | 1 + .../_time_entries_dao.py | 7 ++ .../_domain/_services/__init__.py | 1 + .../_domain/_services/_time_entry.py | 9 ++ .../_domain/_use_cases/__init__.py | 1 + .../_use_cases/_create_time_entry_use_case.py | 9 ++ .../time_entries/_infrastructure/__init__.py | 1 + .../_data_persistence/__init__.py | 1 + .../_data_persistence/_time_entries_dao.py | 38 ++++++++ .../_data_persistence/time_entries_data.json | 92 +++++++++++++++++++ V2/time_tracker/time_entries/interface.py | 1 + 23 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 V2/tests/api/azure/time_entry_azure_endpoints_test.py create mode 100644 V2/tests/integration/daos/time_entries_dao.test.py create mode 100644 V2/tests/unit/services/time_entry_service_test.py create mode 100644 V2/tests/unit/use_cases/time_entries_use_case_test.py create mode 100644 V2/time_tracker/time_entries/_application/__init__.py create mode 100644 V2/time_tracker/time_entries/_application/_time_entries/__init__.py create mode 100644 V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_entities/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_entities/_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py create mode 100644 V2/time_tracker/time_entries/_domain/_services/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_services/_time_entry.py create mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/__init__.py create mode 100644 V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/__init__.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json create mode 100644 V2/time_tracker/time_entries/interface.py diff --git a/V2/serverless.yml b/V2/serverless.yml index 0eb3f42f..c6c5e34b 100644 --- a/V2/serverless.yml +++ b/V2/serverless.yml @@ -76,3 +76,13 @@ functions: - POST route: activities/ authLevel: anonymous + + create_time_entry: + handler: time_tracker/time_entries/interface.create_time_entry + events: + - http: true + x-azure-settings: + methods: + - POST + route: time-entries/ + authLevel: anonymous diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py new file mode 100644 index 00000000..7d27b800 --- /dev/null +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -0,0 +1,40 @@ +from time_tracker.time_entries._application import _time_entries as time_entries +from faker import Faker + +import azure.functions as func +import json + + +ACTIVITY_URL = "/api/time-entries/" + + +def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( + create_temp_time_entries, +): + time_entries_json, tmp_directory = create_temp_time_entries + time_entries._create_time_entry._JSON_PATH = tmp_directory + + time_entry_body = { + "id" : None, + "start_date" : Faker().date(), + "owner_id" : Faker().random_int(), + "description": Faker().sentence(), + "activity_id" : Faker().random_int(), + "uri": "http://timetracker.com", + "technologies" : ["jira","git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "300", + "project_id": Faker().random_int(), + } + body = json.dumps(time_entry_body).encode("utf-8") + req = func.HttpRequest( + method="POST", + body=body, + url=ACTIVITY_URL, + ) + + response = time_entries.create_time_entry(req) + time_entry_json_data = response.get_body() + assert response.status_code == 201 + assert time_entry_json_data == body diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index d1c4928f..92368d4e 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,2 +1,2 @@ # flake8: noqa -from fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file +from fixtures import _activity_factory, _create_fake_dao, _create_fake_database diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py new file mode 100644 index 00000000..9947061a --- /dev/null +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -0,0 +1,45 @@ +import json +import pytest +import typing + +from faker import Faker + +from time_tracker.time_entries._infrastructure import TimeEntriesJsonDao +from time_tracker.time_entries._domain import TimeEntry + + +@pytest.fixture(name="create_fake_time_entries") +def _create_fake_time_entries(mocker) -> typing.List[TimeEntry]: + def _creator(time_entries): + read_data = json.dumps(time_entries) + mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) + return [TimeEntry(**time_entry) for time_entry in time_entries] + + return _creator + + +def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( + create_fake_time_entries, +): + create_fake_time_entries([]) + + time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) + time_entry_data = { + "id" : None, + "start_date" : Faker().date(), + "owner_id" : Faker().random_int(), + "description": Faker().sentence(), + "activity_id" : Faker().random_int(), + "uri": "http://hola.com", + "technologies" : ["jira","git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "UTC-5", + "project_id": Faker().random_int(), + } + result = time_entries_json_dao.create(time_entry_data) + assert result == TimeEntry(**time_entry_data) + + + + diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py new file mode 100644 index 00000000..d844d45d --- /dev/null +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -0,0 +1,15 @@ +from faker import Faker + +from time_tracker.time_entries._domain import TimeEntryService + +def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): + expected_time_entry = mocker.Mock() + time_entry_dao = mocker.Mock( + create=mocker.Mock(return_value=expected_time_entry) + ) + time_entry_service = TimeEntryService(time_entry_dao) + + actual_time_entry = time_entry_service.create(Faker().pydict()) + + assert time_entry_dao.create.called + assert expected_time_entry == actual_time_entry \ No newline at end of file diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py new file mode 100644 index 00000000..e0b2d371 --- /dev/null +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -0,0 +1,20 @@ +from faker import Faker + +from pytest_mock import MockFixture + +from time_tracker.time_entries._domain import _use_cases + +def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry( + mocker: MockFixture, +): + expected_time_entry = mocker.Mock() + time_entry_service = mocker.Mock( + create=mocker.Mock(return_value=expected_time_entry) + ) + + time_entry_use_case = _use_cases.CreateTimeEntryUseCase(time_entry_service) + actual_time_entry = time_entry_use_case.create_time_entry(Faker().pydict()) + + assert time_entry_service.create.called + assert expected_time_entry == actual_time_entry + diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py new file mode 100644 index 00000000..ff94135f --- /dev/null +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -0,0 +1 @@ +from ._time_entries import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py new file mode 100644 index 00000000..6d6f97f1 --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -0,0 +1 @@ +from ._create_time_entry import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py new file mode 100644 index 00000000..e203f16f --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -0,0 +1,49 @@ +import dataclasses +import json +import typing + +import azure.functions as func + +from ... import _domain +from ... import _infrastructure + +_JSON_PATH = ( + 'time_entries/_infrastructure/_data_persistence/time_entries_data.json' +) + +def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: + + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) + + time_entry_data = req.get_json() + + time_entry_to_create = _domain.TimeEntry( + id=None, + start_date=time_entry_data["start_date"], + owner_id=time_entry_data["owner_id"], + description=time_entry_data["description"], + activity_id=time_entry_data["activity_id"], + uri=time_entry_data["uri"], + technologies=time_entry_data["technologies"], + end_date=time_entry_data["end_date"], + deleted=time_entry_data["deleted"], + timezone_offset=time_entry_data["timezone_offset"], + project_id=time_entry_data["project_id"] + ) + + created_time_entry = use_case.create_time_entry(time_entry_to_create.__dict__) + + if not created_time_entry: + return func.HttpResponse( + body=json.dumps({'error': 'time_entry could not be created'}), + status_code=500, + mimetype="application/json" + ) + + return func.HttpResponse( + body=json.dumps(created_time_entry.__dict__), + status_code=201, + mimetype="application/json" + ) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py new file mode 100644 index 00000000..8d430493 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -0,0 +1,6 @@ +from ._entities import TimeEntry +from ._persistence_contracts import TimeEntriesDao +from ._services import TimeEntryService +from ._use_cases import ( + CreateTimeEntryUseCase, +) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/__init__.py b/V2/time_tracker/time_entries/_domain/_entities/__init__.py new file mode 100644 index 00000000..da523368 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -0,0 +1 @@ +from ._time_entry import TimeEntry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py new file mode 100644 index 00000000..f5c3c573 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class TimeEntry: + id: int + start_date: str + owner_id: int + description: str + activity_id: int + uri: str + technologies: List[str] + end_date: str + deleted: str + timezone_offset: str + project_id: int \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py new file mode 100644 index 00000000..ccf8230e --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -0,0 +1 @@ +from ._time_entries_dao import TimeEntriesDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py new file mode 100644 index 00000000..c4475fe4 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -0,0 +1,7 @@ +import abc + +from time_tracker.time_entries._domain import TimeEntry + +class TimeEntriesDao(abc.ABC): + def create(self, time_entry_data: dict) -> TimeEntry: + pass \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/__init__.py b/V2/time_tracker/time_entries/_domain/_services/__init__.py new file mode 100644 index 00000000..5eba7f77 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -0,0 +1 @@ +from ._time_entry import TimeEntryService \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py new file mode 100644 index 00000000..2b41b1f5 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -0,0 +1,9 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntriesDao + +class TimeEntryService: + + def __init__(self, time_entry_dao: TimeEntriesDao): + self.time_entry_dao = time_entry_dao + + def create(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_dao.create(time_entry_data) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py new file mode 100644 index 00000000..8c917202 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -0,0 +1 @@ +from ._create_time_entry_use_case import CreateTimeEntryUseCase \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py new file mode 100644 index 00000000..1df7be91 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -0,0 +1,9 @@ +from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + +class CreateTimeEntryUseCase: + + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def create_time_entry(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_service.create(time_entry_data) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/__init__.py b/V2/time_tracker/time_entries/_infrastructure/__init__.py new file mode 100644 index 00000000..89ae5c03 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -0,0 +1 @@ +from ._data_persistence import TimeEntriesJsonDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py new file mode 100644 index 00000000..d0db4d15 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -0,0 +1 @@ +from ._time_entries_dao import TimeEntriesJsonDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py new file mode 100644 index 00000000..8d0152f0 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -0,0 +1,38 @@ +import json +import dataclasses +import typing + +from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry + +class TimeEntriesJsonDao(TimeEntriesDao): + + def __init__(self, json_data_file_path: str): + self.json_data_file_path = json_data_file_path + self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] + + def create(self, time_entry_data: dict) -> TimeEntry: + time_entries = self.__get_time_entries_from_file() + time_entries.append(time_entry_data) + + try: + with open(self.json_data_file_path, 'w') as outfile: + json.dump(time_entries, outfile) + + return self.__create_time_entry_dto(time_entry_data) + except FileNotFoundError: + print("Can not create activity") + + def __get_time_entries_from_file(self) -> typing.List[dict]: + try: + file = open(self.json_data_file_path) + time_entries = json.load(file) + file.close() + + return time_entries + + except FileNotFoundError: + return [] + + def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: + time_entry = {key: time_entry.get(key) for key in self.time_entry_key} + return TimeEntry(**time_entry) \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json new file mode 100644 index 00000000..2bc98c92 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -0,0 +1,92 @@ +[{ + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}, { + "id": null, + "start_date": "12/07/2021", + "owner_id": 2, + "tenant_id": "asdasdsa", + "description": "No se que poner jajaj ", + "activity_id": 2, + "uri": "http://hola.que.hace.com", + "technologies": ["git", "jira", "python"], + "end_date": "12/07/2021", + "deleted": "asdasdsaadssa", + "timezone_offset": "asdasdsa", + "project_id": 1 +}] \ No newline at end of file diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py new file mode 100644 index 00000000..94349dcc --- /dev/null +++ b/V2/time_tracker/time_entries/interface.py @@ -0,0 +1 @@ +from ._application import create_time_entry \ No newline at end of file From 03ca4df387744e0ef545800757936883e0f50827 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:04:18 -0500 Subject: [PATCH 08/12] feat: TT-401 validated request create time entry --- .../azure/time_entry_azure_endpoints_test.py | 12 +-- V2/tests/conftest.py | 1 + V2/tests/fixtures.py | 32 +++++++ .../integration/daos/time_entries_dao.test.py | 20 ++-- .../unit/services/time_entry_service_test.py | 3 +- .../use_cases/time_entries_use_case_test.py | 8 +- .../time_entries/_application/__init__.py | 1 + .../_application/_time_entries/__init__.py | 1 + .../_time_entries/_create_time_entry.py | 79 +++++++++------- .../time_entries/_domain/__init__.py | 1 + .../_domain/_entities/__init__.py | 1 + .../_domain/_entities/_time_entry.py | 5 +- .../_persistence_contracts/__init__.py | 1 + .../_time_entries_dao.py | 3 +- .../_domain/_services/__init__.py | 1 + .../_domain/_services/_time_entry.py | 11 ++- .../_domain/_use_cases/__init__.py | 3 +- .../_use_cases/_create_time_entry_use_case.py | 11 ++- .../time_entries/_infrastructure/__init__.py | 3 +- .../_data_persistence/__init__.py | 3 +- .../_data_persistence/_time_entries_dao.py | 31 ++++--- .../_data_persistence/time_entries_data.json | 93 +------------------ V2/time_tracker/time_entries/interface.py | 1 + 23 files changed, 147 insertions(+), 178 deletions(-) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 7d27b800..8a56e7e6 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -15,15 +15,15 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ time_entries._create_time_entry._JSON_PATH = tmp_directory time_entry_body = { - "id" : None, - "start_date" : Faker().date(), - "owner_id" : Faker().random_int(), + "id": None, + "start_date": Faker().date(), + "owner_id": Faker().random_int(), "description": Faker().sentence(), - "activity_id" : Faker().random_int(), + "activity_id": Faker().random_int(), "uri": "http://timetracker.com", - "technologies" : ["jira","git"], + "technologies": ["jira", "git"], "end_date": Faker().date(), - "deleted": Faker().random_int(), + "deleted": False, "timezone_offset": "300", "project_id": Faker().random_int(), } diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 92368d4e..d0a557ce 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,2 +1,3 @@ # flake8: noqa from fixtures import _activity_factory, _create_fake_dao, _create_fake_database +from fixtures import _time_entry_factory diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index d9539035..e81bf2e4 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -33,3 +33,35 @@ def _create_fake_dao() -> domain.ActivitiesDao: def _create_fake_database() -> domain.ActivitiesDao: db_fake = DB('sqlite:///:memory:') return db_fake + + +@pytest.fixture(name='time_entry_factory') +def _time_entry_factory() -> TimeEntry: + def _make_time_entry( + id=Faker().random_int(), + start_date=Faker().date(), + owner_id=Faker().random_int(), + description=Faker().sentence(), + activity_id=Faker().random_int(), + uri="http://time-tracker.com", + technologies=["jira", "git"], + end_date=Faker().date(), + deleted=Faker().random_int(), + timezone_offset="300", + project_id=Faker().random_int(), + ): + time_entry = TimeEntry( + id=id, + start_date=start_date, + owner_id=owner_id, + description=description, + activity_id=activity_id, + uri=uri, + technologies=technologies, + end_date=end_date, + deleted=deleted, + timezone_offset=timezone_offset, + project_id=project_id, + ) + return time_entry + return _make_time_entry diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 9947061a..6f9b3e6f 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -25,21 +25,17 @@ def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) time_entry_data = { - "id" : None, - "start_date" : Faker().date(), - "owner_id" : Faker().random_int(), + "id": None, + "start_date": Faker().date(), + "owner_id": Faker().random_int(), "description": Faker().sentence(), - "activity_id" : Faker().random_int(), - "uri": "http://hola.com", - "technologies" : ["jira","git"], + "activity_id": Faker().random_int(), + "uri": "http://timetracker.com", + "technologies": ["jira", "git"], "end_date": Faker().date(), - "deleted": Faker().random_int(), - "timezone_offset": "UTC-5", + "deleted": False, + "timezone_offset": "300", "project_id": Faker().random_int(), } result = time_entries_json_dao.create(time_entry_data) assert result == TimeEntry(**time_entry_data) - - - - diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index d844d45d..5274adbb 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -2,6 +2,7 @@ from time_tracker.time_entries._domain import TimeEntryService + def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): expected_time_entry = mocker.Mock() time_entry_dao = mocker.Mock( @@ -12,4 +13,4 @@ def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry( actual_time_entry = time_entry_service.create(Faker().pydict()) assert time_entry_dao.create.called - assert expected_time_entry == actual_time_entry \ No newline at end of file + assert expected_time_entry == actual_time_entry diff --git a/V2/tests/unit/use_cases/time_entries_use_case_test.py b/V2/tests/unit/use_cases/time_entries_use_case_test.py index e0b2d371..d2a31eb7 100644 --- a/V2/tests/unit/use_cases/time_entries_use_case_test.py +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -1,11 +1,10 @@ -from faker import Faker - from pytest_mock import MockFixture from time_tracker.time_entries._domain import _use_cases + def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry( - mocker: MockFixture, + mocker: MockFixture, time_entry_factory ): expected_time_entry = mocker.Mock() time_entry_service = mocker.Mock( @@ -13,8 +12,7 @@ def test__create_time_entry_function__uses_the_time_entries_service__to_create_t ) time_entry_use_case = _use_cases.CreateTimeEntryUseCase(time_entry_service) - actual_time_entry = time_entry_use_case.create_time_entry(Faker().pydict()) + actual_time_entry = time_entry_use_case.create_time_entry(time_entry_factory()) assert time_entry_service.create.called assert expected_time_entry == actual_time_entry - diff --git a/V2/time_tracker/time_entries/_application/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py index ff94135f..6e4ba9c3 100644 --- a/V2/time_tracker/time_entries/_application/__init__.py +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entries import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py index 6d6f97f1..b46cddce 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/__init__.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._create_time_entry import create_time_entry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py index e203f16f..9d1ed223 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -8,42 +8,59 @@ from ... import _infrastructure _JSON_PATH = ( - 'time_entries/_infrastructure/_data_persistence/time_entries_data.json' + 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' ) + def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: - time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) - time_entry_service = _domain.TimeEntryService(time_entry_dao) - use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) - - time_entry_data = req.get_json() - - time_entry_to_create = _domain.TimeEntry( - id=None, - start_date=time_entry_data["start_date"], - owner_id=time_entry_data["owner_id"], - description=time_entry_data["description"], - activity_id=time_entry_data["activity_id"], - uri=time_entry_data["uri"], - technologies=time_entry_data["technologies"], - end_date=time_entry_data["end_date"], - deleted=time_entry_data["deleted"], - timezone_offset=time_entry_data["timezone_offset"], - project_id=time_entry_data["project_id"] - ) - - created_time_entry = use_case.create_time_entry(time_entry_to_create.__dict__) - - if not created_time_entry: + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_service = _domain.TimeEntryService(time_entry_dao) + use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) + + time_entry_data = req.get_json() + + validation_errors = _validate_time_entry(time_entry_data) + if validation_errors: + return func.HttpResponse( + body=json.dumps(validation_errors), status_code=400, mimetype="application/json" + ) + + time_entry_to_create = _domain.TimeEntry( + id=None, + start_date=time_entry_data["start_date"], + owner_id=time_entry_data["owner_id"], + description=time_entry_data["description"], + activity_id=time_entry_data["activity_id"], + uri=time_entry_data["uri"], + technologies=time_entry_data["technologies"], + end_date=time_entry_data["end_date"], + deleted=False, + timezone_offset=time_entry_data["timezone_offset"], + project_id=time_entry_data["project_id"] + ) + + created_time_entry = use_case.create_time_entry(time_entry_to_create) + + if not created_time_entry: + return func.HttpResponse( + body=json.dumps({'error': 'time_entry could not be created'}), + status_code=500, + mimetype="application/json" + ) + return func.HttpResponse( - body=json.dumps({'error': 'time_entry could not be created'}), - status_code=500, + body=json.dumps(created_time_entry.__dict__), + status_code=201, mimetype="application/json" ) - return func.HttpResponse( - body=json.dumps(created_time_entry.__dict__), - status_code=201, - mimetype="application/json" - ) \ No newline at end of file + +def _validate_time_entry(time_entry_data: dict) -> typing.List[str]: + time_entry_fields = [field.name for field in dataclasses.fields(_domain.TimeEntry)] + time_entry_fields.pop(8) + missing_keys = [field for field in time_entry_fields if field not in time_entry_data] + return [ + f'The {missing_key} key is missing in the input data' + for missing_key in missing_keys + ] diff --git a/V2/time_tracker/time_entries/_domain/__init__.py b/V2/time_tracker/time_entries/_domain/__init__.py index 8d430493..a8b2081c 100644 --- a/V2/time_tracker/time_entries/_domain/__init__.py +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._entities import TimeEntry from ._persistence_contracts import TimeEntriesDao from ._services import TimeEntryService diff --git a/V2/time_tracker/time_entries/_domain/_entities/__init__.py b/V2/time_tracker/time_entries/_domain/_entities/__init__.py index da523368..88b4a739 100644 --- a/V2/time_tracker/time_entries/_domain/_entities/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entry import TimeEntry \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py index f5c3c573..aa73a879 100644 --- a/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import List + @dataclass(frozen=True) class TimeEntry: id: int @@ -11,6 +12,6 @@ class TimeEntry: uri: str technologies: List[str] end_date: str - deleted: str + deleted: bool timezone_offset: str - project_id: int \ No newline at end of file + project_id: int diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py index ccf8230e..e10700ce 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entries_dao import TimeEntriesDao \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index c4475fe4..aba78170 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -2,6 +2,7 @@ from time_tracker.time_entries._domain import TimeEntry + class TimeEntriesDao(abc.ABC): def create(self, time_entry_data: dict) -> TimeEntry: - pass \ No newline at end of file + pass diff --git a/V2/time_tracker/time_entries/_domain/_services/__init__.py b/V2/time_tracker/time_entries/_domain/_services/__init__.py index 5eba7f77..e5e6ba1b 100644 --- a/V2/time_tracker/time_entries/_domain/_services/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._time_entry import TimeEntryService \ No newline at end of file diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index 2b41b1f5..368a4780 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -1,9 +1,10 @@ from time_tracker.time_entries._domain import TimeEntry, TimeEntriesDao + class TimeEntryService: - - def __init__(self, time_entry_dao: TimeEntriesDao): - self.time_entry_dao = time_entry_dao - def create(self, time_entry_data: dict) -> TimeEntry: - return self.time_entry_dao.create(time_entry_data) \ No newline at end of file + def __init__(self, time_entry_dao: TimeEntriesDao): + self.time_entry_dao = time_entry_dao + + def create(self, time_entry_data: dict) -> TimeEntry: + return self.time_entry_dao.create(time_entry_data) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py index 8c917202..41aca738 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -1 +1,2 @@ -from ._create_time_entry_use_case import CreateTimeEntryUseCase \ No newline at end of file +# flake8: noqa +from ._create_time_entry_use_case import CreateTimeEntryUseCase diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py index 1df7be91..73c32d81 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -1,9 +1,10 @@ from time_tracker.time_entries._domain import TimeEntry, TimeEntryService + class CreateTimeEntryUseCase: - def __init__(self, time_entry_service: TimeEntryService): - self.time_entry_service = time_entry_service - - def create_time_entry(self, time_entry_data: dict) -> TimeEntry: - return self.time_entry_service.create(time_entry_data) \ No newline at end of file + def __init__(self, time_entry_service: TimeEntryService): + self.time_entry_service = time_entry_service + + def create_time_entry(self, time_entry_data: TimeEntry) -> TimeEntry: + return self.time_entry_service.create(time_entry_data.__dict__) diff --git a/V2/time_tracker/time_entries/_infrastructure/__init__.py b/V2/time_tracker/time_entries/_infrastructure/__init__.py index 89ae5c03..0e90c78a 100644 --- a/V2/time_tracker/time_entries/_infrastructure/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -1 +1,2 @@ -from ._data_persistence import TimeEntriesJsonDao \ No newline at end of file +# flake8: noqa +from ._data_persistence import TimeEntriesJsonDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py index d0db4d15..91ebd7cf 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -1 +1,2 @@ -from ._time_entries_dao import TimeEntriesJsonDao \ No newline at end of file +# flake8: noqa +from ._time_entries_dao import TimeEntriesJsonDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index 8d0152f0..84e8f200 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -4,13 +4,14 @@ from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry + class TimeEntriesJsonDao(TimeEntriesDao): - - def __init__(self, json_data_file_path: str): - self.json_data_file_path = json_data_file_path - self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] - def create(self, time_entry_data: dict) -> TimeEntry: + def __init__(self, json_data_file_path: str): + self.json_data_file_path = json_data_file_path + self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] + + def create(self, time_entry_data: dict) -> TimeEntry: time_entries = self.__get_time_entries_from_file() time_entries.append(time_entry_data) @@ -22,17 +23,17 @@ def create(self, time_entry_data: dict) -> TimeEntry: except FileNotFoundError: print("Can not create activity") - def __get_time_entries_from_file(self) -> typing.List[dict]: - try: - file = open(self.json_data_file_path) - time_entries = json.load(file) - file.close() + def __get_time_entries_from_file(self) -> typing.List[dict]: + try: + file = open(self.json_data_file_path) + time_entries = json.load(file) + file.close() - return time_entries + return time_entries - except FileNotFoundError: - return [] + except FileNotFoundError: + return [] - def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: + def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: time_entry = {key: time_entry.get(key) for key in self.time_entry_key} - return TimeEntry(**time_entry) \ No newline at end of file + return TimeEntry(**time_entry) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json index 2bc98c92..0ebbd0e7 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -1,92 +1 @@ -[{ - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}, { - "id": null, - "start_date": "12/07/2021", - "owner_id": 2, - "tenant_id": "asdasdsa", - "description": "No se que poner jajaj ", - "activity_id": 2, - "uri": "http://hola.que.hace.com", - "technologies": ["git", "jira", "python"], - "end_date": "12/07/2021", - "deleted": "asdasdsaadssa", - "timezone_offset": "asdasdsa", - "project_id": 1 -}] \ No newline at end of file +[{"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}] \ No newline at end of file diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py index 94349dcc..d0182780 100644 --- a/V2/time_tracker/time_entries/interface.py +++ b/V2/time_tracker/time_entries/interface.py @@ -1 +1,2 @@ +# flake8: noqa from ._application import create_time_entry \ No newline at end of file From bdd788d4fb5b1befd42405fe3924084a4da68f88 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:47:20 -0500 Subject: [PATCH 09/12] fix: TT-401 implemented faker url --- .../azure/time_entry_azure_endpoints_test.py | 17 +-------- V2/tests/fixtures.py | 23 +++++++++++- .../integration/daos/time_entries_dao.test.py | 18 ++------- .../_time_entries_dao.py | 1 + .../_data_persistence/time_entries_data.json | 37 ++++++++++++++++++- 5 files changed, 63 insertions(+), 33 deletions(-) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 8a56e7e6..4477052a 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,5 +1,4 @@ from time_tracker.time_entries._application import _time_entries as time_entries -from faker import Faker import azure.functions as func import json @@ -9,24 +8,12 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( - create_temp_time_entries, + create_temp_time_entries, time_entry_factory ): time_entries_json, tmp_directory = create_temp_time_entries time_entries._create_time_entry._JSON_PATH = tmp_directory - time_entry_body = { - "id": None, - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": "http://timetracker.com", - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": False, - "timezone_offset": "300", - "project_id": Faker().random_int(), - } + time_entry_body = time_entry_factory(None).__dict__ body = json.dumps(time_entry_body).encode("utf-8") req = func.HttpRequest( method="POST", diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index e81bf2e4..05540e5b 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -34,6 +34,25 @@ def _create_fake_database() -> domain.ActivitiesDao: db_fake = DB('sqlite:///:memory:') return db_fake +@pytest.fixture +def create_temp_time_entries(tmpdir_factory): + temporary_directory = tmpdir_factory.mktemp("tmp") + json_file = temporary_directory.join("time_entries.json") + time_entries = [ + { + "id": Faker().random_int(), + "start_date": Faker().date(), + "owner_id": Faker().random_int(), + "description": Faker().sentence(), + "activity_id": Faker().random_int(), + "uri": Faker().domain_name(), + "technologies": ["jira", "git"], + "end_date": Faker().date(), + "deleted": Faker().random_int(), + "timezone_offset": "300", + "project_id": Faker().random_int(), + } + ] @pytest.fixture(name='time_entry_factory') def _time_entry_factory() -> TimeEntry: @@ -43,10 +62,10 @@ def _make_time_entry( owner_id=Faker().random_int(), description=Faker().sentence(), activity_id=Faker().random_int(), - uri="http://time-tracker.com", + uri=Faker().domain_name(), technologies=["jira", "git"], end_date=Faker().date(), - deleted=Faker().random_int(), + deleted=False, timezone_offset="300", project_id=Faker().random_int(), ): diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py index 6f9b3e6f..db32d08f 100644 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ b/V2/tests/integration/daos/time_entries_dao.test.py @@ -19,23 +19,11 @@ def _creator(time_entries): def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( - create_fake_time_entries, + create_fake_time_entries, time_entry_factory ): create_fake_time_entries([]) time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - time_entry_data = { - "id": None, - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": "http://timetracker.com", - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": False, - "timezone_offset": "300", - "project_id": Faker().random_int(), - } - result = time_entries_json_dao.create(time_entry_data) + time_entry_data = time_entry_factory() + result = time_entries_json_dao.create(time_entry_data.__dict__) assert result == TimeEntry(**time_entry_data) diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index aba78170..f4c58bbd 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -4,5 +4,6 @@ class TimeEntriesDao(abc.ABC): + @abc.abstractmethod def create(self, time_entry_data: dict) -> TimeEntry: pass diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json index 0ebbd0e7..60dc4aa2 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json @@ -1 +1,36 @@ -[{"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "tenant_id": "asdasdsa", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": "asdasdsaadssa", "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": 2, "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}, {"id": null, "start_date": "12/07/2021", "owner_id": "2", "description": "No se que poner jajaj ", "activity_id": 2, "uri": "http://hola.que.hace.com", "technologies": ["git", "jira", "python"], "end_date": "12/07/2021", "deleted": false, "timezone_offset": "asdasdsa", "project_id": 1}] \ No newline at end of file +[ + { + "id": 1, + "start_date": "12/07/2021", + "owner_id": 2, + "description": "Review system of Time Tracker", + "activity_id": 2, + "uri": "http://time_tracker.com", + "technologies": [ + "git", + "jira", + "python" + ], + "end_date": "12/07/2021", + "deleted": false, + "timezone_offset": "300", + "project_id": 1 + }, + { + "id": 2, + "start_date": "12/07/2021", + "owner_id": 2, + "description": "Review system of doors in the ioet office Loja, change the current raspberry pi ", + "activity_id": 2, + "uri": null, + "technologies": [ + "git", + "jira", + "python" + ], + "end_date": "12/07/2021", + "deleted": false, + "timezone_offset": "300", + "project_id": 1 + } +] \ No newline at end of file From 34107bcb7bb5a8b099425e65ba56fe72973bc60d Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 15 Nov 2021 09:01:49 -0500 Subject: [PATCH 10/12] refactor: TT-401 changed the variable name --- V2/tests/api/azure/time_entry_azure_endpoints_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 4477052a..eada464e 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,10 +1,10 @@ -from time_tracker.time_entries._application import _time_entries as time_entries +import json import azure.functions as func -import json +from time_tracker.time_entries._application import _time_entries as time_entries -ACTIVITY_URL = "/api/time-entries/" +TIME_ENTRY_URL = "/api/time-entries/" def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( @@ -18,7 +18,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ req = func.HttpRequest( method="POST", body=body, - url=ACTIVITY_URL, + url=TIME_ENTRY_URL, ) response = time_entries.create_time_entry(req) From 41ae86642123e3e94ad9c7e19e25789e1b337e28 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 16 Nov 2021 10:01:02 -0500 Subject: [PATCH 11/12] fix: implementation of the database connection --- V2/create_activity/function.json | 22 ------ V2/delete_activity/function.json | 22 ------ V2/get_activities/function.json | 22 ------ .../azure/time_entry_azure_endpoints_test.py | 38 +++++++--- V2/tests/conftest.py | 2 +- V2/tests/fixtures.py | 46 +++--------- .../daos/activities_sql_dao_test.py | 7 ++ .../integration/daos/time_entries_dao.test.py | 29 -------- .../integration/daos/time_entries_dao_test.py | 58 +++++++++++++++ .../unit/services/time_entry_service_test.py | 6 +- V2/time_tracker/_infrastructure/_db.py | 7 +- .../_time_entries/_create_time_entry.py | 7 +- .../_time_entries_dao.py | 2 +- .../_domain/_services/_time_entry.py | 2 +- .../_use_cases/_create_time_entry_use_case.py | 2 +- .../_data_persistence/_time_entries_dao.py | 74 +++++++++++-------- .../_data_persistence/time_entries_data.json | 36 --------- V2/update_activity/function.json | 22 ------ 18 files changed, 159 insertions(+), 245 deletions(-) delete mode 100644 V2/create_activity/function.json delete mode 100644 V2/delete_activity/function.json delete mode 100644 V2/get_activities/function.json delete mode 100644 V2/tests/integration/daos/time_entries_dao.test.py create mode 100644 V2/tests/integration/daos/time_entries_dao_test.py delete mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json delete mode 100644 V2/update_activity/function.json diff --git a/V2/create_activity/function.json b/V2/create_activity/function.json deleted file mode 100644 index ed3454a9..00000000 --- a/V2/create_activity/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "activities/", - "authLevel": "anonymous", - "methods": [ - "POST" - ] - }, - { - "type": "http", - "direction": "out", - "name": "$return" - } - ], - "entryPoint": "create_activity", - "scriptFile": "../time_tracker/activities/interface.py" -} \ No newline at end of file diff --git a/V2/delete_activity/function.json b/V2/delete_activity/function.json deleted file mode 100644 index d51170fd..00000000 --- a/V2/delete_activity/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "activities/{id}", - "authLevel": "anonymous", - "methods": [ - "DELETE" - ] - }, - { - "type": "http", - "direction": "out", - "name": "$return" - } - ], - "entryPoint": "delete_activity", - "scriptFile": "../time_tracker/activities/interface.py" -} \ No newline at end of file diff --git a/V2/get_activities/function.json b/V2/get_activities/function.json deleted file mode 100644 index ee1efe53..00000000 --- a/V2/get_activities/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "activities/{id:?}", - "authLevel": "anonymous", - "methods": [ - "GET" - ] - }, - { - "type": "http", - "direction": "out", - "name": "$return" - } - ], - "entryPoint": "get_activities", - "scriptFile": "../time_tracker/activities/interface.py" -} \ No newline at end of file diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index eada464e..d6b15eda 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,27 +1,43 @@ +import pytest import json import azure.functions as func -from time_tracker.time_entries._application import _time_entries as time_entries +import time_tracker.time_entries._application._time_entries as azure_time_entries +from time_tracker._infrastructure import DB +from time_tracker.activities import _domain as domain_activities +from time_tracker.activities import _infrastructure as infrastructure_activities TIME_ENTRY_URL = "/api/time-entries/" +@pytest.fixture(name='insert_activity') +def _insert_activity() -> dict: + def _new_activity(activity: domain_activities.Activity, database: DB): + dao = infrastructure_activities.ActivitiesSQLDao(database) + new_activity = dao.create(activity) + return new_activity.__dict__ + return _new_activity + + def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( - create_temp_time_entries, time_entry_factory + create_fake_database, time_entry_factory, activity_factory, insert_activity ): - time_entries_json, tmp_directory = create_temp_time_entries - time_entries._create_time_entry._JSON_PATH = tmp_directory + db = create_fake_database + inserted_activity = insert_activity(activity_factory(), db) + time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]").__dict__ - time_entry_body = time_entry_factory(None).__dict__ + azure_time_entries._create_time_entry._DATABASE = db body = json.dumps(time_entry_body).encode("utf-8") req = func.HttpRequest( - method="POST", - body=body, - url=TIME_ENTRY_URL, + method='POST', + body=body, + url=TIME_ENTRY_URL, ) - response = time_entries.create_time_entry(req) - time_entry_json_data = response.get_body() + response = azure_time_entries._create_time_entry.create_time_entry(req) + time_entry_json_data = json.loads(response.get_body()) + time_entry_body['id'] = time_entry_json_data['id'] + assert response.status_code == 201 - assert time_entry_json_data == body + assert time_entry_json_data == time_entry_body diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index d0a557ce..a9de6879 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,3 +1,3 @@ # flake8: noqa -from fixtures import _activity_factory, _create_fake_dao, _create_fake_database +from fixtures import _activity_factory, _create_fake_database from fixtures import _time_entry_factory diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index 05540e5b..6ec81e6d 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -1,17 +1,17 @@ import pytest +from faker import Faker -import time_tracker.activities._domain as domain -import time_tracker.activities._infrastructure as infrastructure +import time_tracker.activities._domain as domain_activities +import time_tracker.time_entries._domain as domain_time_entries from time_tracker._infrastructure import DB -from faker import Faker @pytest.fixture(name='activity_factory') -def _activity_factory() -> domain.Activity: +def _activity_factory() -> domain_activities.Activity: def _make_activity( name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1 ): - activity = domain.Activity( + activity = domain_activities.Activity( id=None, name=name, description=description, @@ -22,54 +22,28 @@ def _make_activity( return _make_activity -@pytest.fixture(name='create_fake_dao') -def _create_fake_dao() -> domain.ActivitiesDao: - db_fake = DB('sqlite:///:memory:') - dao = infrastructure.ActivitiesSQLDao(db_fake) - return dao - - @pytest.fixture(name='create_fake_database') -def _create_fake_database() -> domain.ActivitiesDao: +def _create_fake_database() -> DB: db_fake = DB('sqlite:///:memory:') return db_fake -@pytest.fixture -def create_temp_time_entries(tmpdir_factory): - temporary_directory = tmpdir_factory.mktemp("tmp") - json_file = temporary_directory.join("time_entries.json") - time_entries = [ - { - "id": Faker().random_int(), - "start_date": Faker().date(), - "owner_id": Faker().random_int(), - "description": Faker().sentence(), - "activity_id": Faker().random_int(), - "uri": Faker().domain_name(), - "technologies": ["jira", "git"], - "end_date": Faker().date(), - "deleted": Faker().random_int(), - "timezone_offset": "300", - "project_id": Faker().random_int(), - } - ] @pytest.fixture(name='time_entry_factory') -def _time_entry_factory() -> TimeEntry: +def _time_entry_factory() -> domain_time_entries.TimeEntry: def _make_time_entry( id=Faker().random_int(), - start_date=Faker().date(), + start_date=str(Faker().date_time()), owner_id=Faker().random_int(), description=Faker().sentence(), activity_id=Faker().random_int(), uri=Faker().domain_name(), technologies=["jira", "git"], - end_date=Faker().date(), + end_date=str(Faker().date_time()), deleted=False, timezone_offset="300", project_id=Faker().random_int(), ): - time_entry = TimeEntry( + time_entry = domain_time_entries.TimeEntry( id=id, start_date=start_date, owner_id=owner_id, diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py index 25f62500..1c36358c 100644 --- a/V2/tests/integration/daos/activities_sql_dao_test.py +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -15,6 +15,13 @@ def _new_activity(activity: domain.Activity, dao: domain.ActivitiesDao): return _new_activity +@pytest.fixture(name='create_fake_dao') +def _create_fake_dao() -> domain.ActivitiesDao: + db_fake = DB('sqlite:///:memory:') + dao = infrastructure.ActivitiesSQLDao(db_fake) + return dao + + @pytest.fixture(name='clean_database', autouse=True) def _clean_database(): yield diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py deleted file mode 100644 index db32d08f..00000000 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -import pytest -import typing - -from faker import Faker - -from time_tracker.time_entries._infrastructure import TimeEntriesJsonDao -from time_tracker.time_entries._domain import TimeEntry - - -@pytest.fixture(name="create_fake_time_entries") -def _create_fake_time_entries(mocker) -> typing.List[TimeEntry]: - def _creator(time_entries): - read_data = json.dumps(time_entries) - mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) - return [TimeEntry(**time_entry) for time_entry in time_entries] - - return _creator - - -def test_create_time_entry__returns_an_time_entry_dto__when_create_an_time_entry_that_matches_attributes( - create_fake_time_entries, time_entry_factory -): - create_fake_time_entries([]) - - time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - time_entry_data = time_entry_factory() - result = time_entries_json_dao.create(time_entry_data.__dict__) - assert result == TimeEntry(**time_entry_data) diff --git a/V2/tests/integration/daos/time_entries_dao_test.py b/V2/tests/integration/daos/time_entries_dao_test.py new file mode 100644 index 00000000..db4732d5 --- /dev/null +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -0,0 +1,58 @@ +import pytest + + +import time_tracker.time_entries._domain as domain +import time_tracker.activities._domain as domain_activities +import time_tracker.time_entries._infrastructure as infrastructure +import time_tracker.activities._infrastructure as infrastructure_activities +from time_tracker._infrastructure import DB + + +@pytest.fixture(name='insert_activity') +def _insert_activity() -> domain_activities.Activity: + def _new_activity(activity: domain_activities.Activity, database: DB): + dao = infrastructure_activities.ActivitiesSQLDao(database) + new_activity = dao.create(activity) + return new_activity + return _new_activity + + +@pytest.fixture(name='create_fake_dao') +def _create_fake_dao() -> domain.TimeEntriesDao: + db_fake = DB('sqlite:///:memory:') + dao = infrastructure.TimeEntriesJsonDao(db_fake) + return dao + + +@pytest.fixture(name='clean_database', autouse=True) +def _clean_database(): + yield + db_fake = DB('sqlite:///:memory:') + dao = infrastructure.TimeEntriesJsonDao(db_fake) + query = dao.time_entry.delete() + dao.db.get_session().execute(query) + + +def test__time_entry__returns_a_time_entry_dto__when_saves_correctly_with_sql_database( + time_entry_factory, create_fake_dao, insert_activity, activity_factory +): + dao = create_fake_dao + inserted_activity = insert_activity(activity_factory(), dao.db) + + existent_time_entry = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + + inserted_time_entry = dao.create(existent_time_entry) + + assert isinstance(inserted_time_entry, domain.TimeEntry) + assert inserted_time_entry == existent_time_entry + + +def test__time_entry__returns_None__when_not_saves_correctly( + time_entry_factory, create_fake_dao, +): + dao = create_fake_dao + existent_time_entry = time_entry_factory(activity_id=1203, technologies="[jira,sql]") + + inserted_time_entry = dao.create(existent_time_entry) + + assert inserted_time_entry is None diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index 5274adbb..bd5ce085 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -1,16 +1,14 @@ -from faker import Faker - from time_tracker.time_entries._domain import TimeEntryService -def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker): +def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(mocker, time_entry_factory): expected_time_entry = mocker.Mock() time_entry_dao = mocker.Mock( create=mocker.Mock(return_value=expected_time_entry) ) time_entry_service = TimeEntryService(time_entry_dao) - actual_time_entry = time_entry_service.create(Faker().pydict()) + actual_time_entry = time_entry_service.create(time_entry_factory()) assert time_entry_dao.create.called assert expected_time_entry == actual_time_entry diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index 8fe5cef1..0f7dae1a 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -2,6 +2,8 @@ from . import _config +_TEST_DIALECT = "sqlite" + class DB(): config = _config.load_config() @@ -14,7 +16,10 @@ def __init__(self, conn_string: str = conn_string): self.engine = sqlalchemy.create_engine(conn_string) def get_session(self): + + self.metadata.create_all(self.engine) if self.connection is None: - self.metadata.create_all(self.engine) self.connection = self.engine.connect() + if self.engine.dialect.name == _TEST_DIALECT: + self.connection.execute("pragma foreign_keys=ON") return self.connection diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py index 9d1ed223..cbab7754 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -6,15 +6,14 @@ from ... import _domain from ... import _infrastructure +from time_tracker._infrastructure import DB -_JSON_PATH = ( - 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' -) +_DATABASE = DB() def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: - time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_DATABASE) time_entry_service = _domain.TimeEntryService(time_entry_dao) use_case = _domain._use_cases.CreateTimeEntryUseCase(time_entry_service) diff --git a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py index f4c58bbd..5d04c861 100644 --- a/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -5,5 +5,5 @@ class TimeEntriesDao(abc.ABC): @abc.abstractmethod - def create(self, time_entry_data: dict) -> TimeEntry: + def create(self, time_entry_data: TimeEntry) -> TimeEntry: pass diff --git a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py index 368a4780..d7aaf3ba 100644 --- a/V2/time_tracker/time_entries/_domain/_services/_time_entry.py +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -6,5 +6,5 @@ class TimeEntryService: def __init__(self, time_entry_dao: TimeEntriesDao): self.time_entry_dao = time_entry_dao - def create(self, time_entry_data: dict) -> TimeEntry: + def create(self, time_entry_data: TimeEntry) -> TimeEntry: return self.time_entry_dao.create(time_entry_data) diff --git a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py index 73c32d81..f2258468 100644 --- a/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -7,4 +7,4 @@ def __init__(self, time_entry_service: TimeEntryService): self.time_entry_service = time_entry_service def create_time_entry(self, time_entry_data: TimeEntry) -> TimeEntry: - return self.time_entry_service.create(time_entry_data.__dict__) + return self.time_entry_service.create(time_entry_data) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index 84e8f200..aaed45c5 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -1,39 +1,49 @@ -import json import dataclasses -import typing -from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry - - -class TimeEntriesJsonDao(TimeEntriesDao): - - def __init__(self, json_data_file_path: str): - self.json_data_file_path = json_data_file_path - self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] - - def create(self, time_entry_data: dict) -> TimeEntry: - time_entries = self.__get_time_entries_from_file() - time_entries.append(time_entry_data) - - try: - with open(self.json_data_file_path, 'w') as outfile: - json.dump(time_entries, outfile) - - return self.__create_time_entry_dto(time_entry_data) - except FileNotFoundError: - print("Can not create activity") - - def __get_time_entries_from_file(self) -> typing.List[dict]: +import sqlalchemy + +import time_tracker.time_entries._domain as domain +from time_tracker._infrastructure import _db + + +class TimeEntriesJsonDao(domain.TimeEntriesDao): + + def __init__(self, database: _db.DB): + self.time_entry_key = [field.name for field in dataclasses.fields(domain.TimeEntry)] + self.db = database + self.time_entry = sqlalchemy.Table( + 'time_entry', + self.db.metadata, + sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True, autoincrement=True), + sqlalchemy.Column('start_date', sqlalchemy.DateTime().with_variant(sqlalchemy.String, "sqlite")), + sqlalchemy.Column('owner_id', sqlalchemy.Integer), + sqlalchemy.Column('description', sqlalchemy.String), + sqlalchemy.Column('activity_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('activity.id')), + sqlalchemy.Column('uri', sqlalchemy.String), + sqlalchemy.Column( + 'technologies', + sqlalchemy.ARRAY(sqlalchemy.String).with_variant(sqlalchemy.String, "sqlite") + ), + sqlalchemy.Column('end_date', sqlalchemy.DateTime().with_variant(sqlalchemy.String, "sqlite")), + sqlalchemy.Column('deleted', sqlalchemy.Boolean), + sqlalchemy.Column('timezone_offset', sqlalchemy.String), + sqlalchemy.Column('project_id', sqlalchemy.Integer), + extend_existing=True, + ) + + def create(self, time_entry_data: domain.TimeEntry) -> domain.TimeEntry: try: - file = open(self.json_data_file_path) - time_entries = json.load(file) - file.close() + new_time_entry = time_entry_data.__dict__ + new_time_entry.pop('id', None) - return time_entries + query = self.time_entry.insert().values(new_time_entry).return_defaults() + time_entry = self.db.get_session().execute(query) + new_time_entry.update({"id": time_entry.inserted_primary_key[0]}) + return self.__create_time_entry_dto(new_time_entry) - except FileNotFoundError: - return [] + except sqlalchemy.exc.SQLAlchemyError: + return None - def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: + def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry: time_entry = {key: time_entry.get(key) for key in self.time_entry_key} - return TimeEntry(**time_entry) + return domain.TimeEntry(**time_entry) diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json deleted file mode 100644 index 60dc4aa2..00000000 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "id": 1, - "start_date": "12/07/2021", - "owner_id": 2, - "description": "Review system of Time Tracker", - "activity_id": 2, - "uri": "http://time_tracker.com", - "technologies": [ - "git", - "jira", - "python" - ], - "end_date": "12/07/2021", - "deleted": false, - "timezone_offset": "300", - "project_id": 1 - }, - { - "id": 2, - "start_date": "12/07/2021", - "owner_id": 2, - "description": "Review system of doors in the ioet office Loja, change the current raspberry pi ", - "activity_id": 2, - "uri": null, - "technologies": [ - "git", - "jira", - "python" - ], - "end_date": "12/07/2021", - "deleted": false, - "timezone_offset": "300", - "project_id": 1 - } -] \ No newline at end of file diff --git a/V2/update_activity/function.json b/V2/update_activity/function.json deleted file mode 100644 index 97c9fb49..00000000 --- a/V2/update_activity/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "activities/{id}", - "authLevel": "anonymous", - "methods": [ - "PUT" - ] - }, - { - "type": "http", - "direction": "out", - "name": "$return" - } - ], - "entryPoint": "update_activity", - "scriptFile": "../time_tracker/activities/interface.py" -} \ No newline at end of file From 54eeb7e2143e4f9ff53b2a7257a7144b4b64ba07 Mon Sep 17 00:00:00 2001 From: mandres2015 Date: Tue, 16 Nov 2021 10:43:33 -0500 Subject: [PATCH 12/12] feat: TT-403 deleted time entries --- V2/pyvenv.cfg | 3 + .../azure/time_entry_azure_endpoints_test.py | 78 +++++++++++++++- V2/tests/conftest.py | 3 - .../integration/daos/time_entries_dao.test.py | 45 ---------- .../integration/daos/time_entries_dao_test.py | 47 ++++++++++ V2/time_tracker/_infrastructure/_db.py | 2 +- .../_time_entries/_delete_time_entry.py | 23 +++-- .../time_entries/_infrastructure/__init__.py | 2 +- .../_data_persistence/__init__.py | 2 +- .../_data_persistence/_time_entries_dao.py | 88 +++++++++---------- 10 files changed, 180 insertions(+), 113 deletions(-) create mode 100644 V2/pyvenv.cfg delete mode 100644 V2/tests/integration/daos/time_entries_dao.test.py create mode 100644 V2/tests/integration/daos/time_entries_dao_test.py diff --git a/V2/pyvenv.cfg b/V2/pyvenv.cfg new file mode 100644 index 00000000..853404e2 --- /dev/null +++ b/V2/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.8.10 diff --git a/V2/tests/api/azure/time_entry_azure_endpoints_test.py b/V2/tests/api/azure/time_entry_azure_endpoints_test.py index 70a70b35..da00779b 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,15 +1,86 @@ -from time_tracker.time_entries._application import _time_entries as time_entries +import pytest +import json +from faker import Faker import azure.functions as func -import json + +from time_tracker.time_entries._application import _time_entries as time_entries +import time_tracker.time_entries._application._time_entries as azure_time_entries +import time_tracker.time_entries._infrastructure as infrastructure +from time_tracker._infrastructure import DB +from time_tracker.time_entries import _domain + TIME_ENTRY_URL = "/api/time-entries/" +@pytest.fixture(name='insert_time_entry') +def _insert_time_entry() -> dict: + def _new_time_entry(time_entry: _domain.TimeEntry, database: DB): + dao = infrastructure.TimeEntriesSQLDao(database) + new_time_entry = dao.create(time_entry) + return new_time_entry.__dict__ + return _new_time_entry + + def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found( - create_temp_time_entries, + create_fake_database, time_entry_factory, insert_time_entry, create_temp_time_entries, ): + # fake_database = create_fake_database + # existent_activity = activity_factory() + # inserted_activity = insert_activity(existent_activity, fake_database) + # existent_time_entry = time_entry_factory() + # inserted_time_entry = insert_time_entry(existent_time_entry, fake_database) + azure_time_entries._.DATABASE = create_fake_database + time_entry_body = { + 'id': 1, + 'start_date': Faker().date(), + 'owner_id': Faker().pyint(), + 'description': Faker().sentence(), + 'activity_id': 1, + 'uri': Faker().uri(), + 'technologies': None, + 'end_date': Faker().date(), + 'deleted': False, + 'timezone_offset': None, + 'project_id': 1, + } + body = json.dumps(time_entry_body).encode("utf-8") + req = func.HttpRequest( + method='DELETE', + body=body, + url=TIME_ENTRY_URL, + # route_params={"id": 1}, + ) + + response = azure_time_entries._delete_time_entry.delete_time_entry(req) + assert response.get_body() == "" + time_entry_json_data = json.loads(response.get_body().decode("utf-8")) + # activity_body['id'] = activitiy_json_data['id'] + + # azure_time_entries._delete_time_entry.DATABASE = fake_database + # req = func.HttpRequest( + # method='DELETE', + # body=None, + # url=TIME_ENTRY_URL, + # route_params={"id": inserted_time_entry["id"]}, + # ) + + # assert req.get_body() == "" + + # response = azure_activities._delete_activity.delete_activity(req) + # activity_json_data = json.loads(response.get_body().decode("utf-8")) + + assert response.status_code == 200 + # assert activity_json_data['status'] == 0 + # assert activity_json_data['deleted'] is True + + + + + + time_entries_json, tmp_directory = create_temp_time_entries time_entries.delete_time_entry.JSON_PATH = tmp_directory req = func.HttpRequest( @@ -19,6 +90,7 @@ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_de route_params={"id": time_entries_json[0]["id"]}, ) + response = time_entries.delete_time_entry(req) time_entry_json_data = json.loads(response.get_body().decode("utf-8")) diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index cafb2355..0199cc4f 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,8 +1,5 @@ # flake8: noqa -<<<<<<< HEAD from fixtures import _activity_factory, _create_fake_dao, _create_fake_database -======= from fixtures import create_temp_activities from fixtures import create_temp_time_entries from fixtures import _time_entry_factory ->>>>>>> 42cfc46... feat: TT-401 validated request create time entry diff --git a/V2/tests/integration/daos/time_entries_dao.test.py b/V2/tests/integration/daos/time_entries_dao.test.py deleted file mode 100644 index 49191e62..00000000 --- a/V2/tests/integration/daos/time_entries_dao.test.py +++ /dev/null @@ -1,45 +0,0 @@ -import json -import pytest -import typing - -from faker import Faker - -from time_tracker.time_entries._infrastructure import TimeEntriesJsonDao -from time_tracker.time_entries._domain import TimeEntry - - -@pytest.fixture(name="create_fake_time_entries") -def _create_fake_time_entries(mocker) -> typing.List[TimeEntry]: - def _creator(time_entries): - read_data = json.dumps(time_entries) - mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) - return [TimeEntry(**time_entry) for time_entry in time_entries] - - return _creator - - -def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( - create_fake_time_entries, time_entry_factory -): - time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - time_entries = create_fake_time_entries( - [ - time_entry_factory().__dict__ - ] - ) - - time_entry_dto = time_entries[0] - result = time_entries_json_dao.delete(time_entry_dto.id) - - assert result.deleted is True - - -def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( - create_fake_time_entries, -): - time_entries_json_dao = TimeEntriesJsonDao(Faker().file_path()) - create_fake_time_entries([]) - - result = time_entries_json_dao.delete(Faker().pyint()) - - assert result is None diff --git a/V2/tests/integration/daos/time_entries_dao_test.py b/V2/tests/integration/daos/time_entries_dao_test.py new file mode 100644 index 00000000..1f689b66 --- /dev/null +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -0,0 +1,47 @@ +import json +import pytest +import typing + +from faker import Faker + +from time_tracker.time_entries._domain import TimeEntry, TimeEntriesDao +from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao +from time_tracker.activities._domain import Activity, ActivitiesDao + + +@pytest.fixture(name='create_fake_time_entries') +def _create_fake_time_entries() -> TimeEntry: + def _new_time_entry(time_entry: TimeEntry, dao: TimeEntriesDao): + new_time_entry = dao.create(time_entry) + return new_time_entry + return _new_time_entry + + +def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( + create_fake_dao, create_fake_time_entries, time_entry_factory, activity_factory, insert_activity +): + dao = create_fake_dao + + existent_time_entry = time_entry_factory() + inserted_time_entry = create_fake_time_entries(existent_time_entry, dao) + + + expected_description = Faker().sentence() + time_entries_json_dao = TimeEntriesSQLDao(Faker().file_path()) + time_entries = create_fake_time_entries([time_entry_factory().__dict__]) + + time_entry_dto = time_entries[0] + result = time_entries_json_dao.delete(time_entry_dto.id) + + assert result.deleted is True + + +def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( + create_fake_time_entries, +): + time_entries_json_dao = TimeEntriesSQLDao(Faker().file_path()) + create_fake_time_entries([]) + + result = time_entries_json_dao.delete(Faker().pyint()) + + assert result is None \ No newline at end of file diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index 8fe5cef1..6f3a9f9a 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -14,7 +14,7 @@ def __init__(self, conn_string: str = conn_string): self.engine = sqlalchemy.create_engine(conn_string) def get_session(self): + self.metadata.create_all(self.engine) if self.connection is None: - self.metadata.create_all(self.engine) self.connection = self.engine.connect() return self.connection diff --git a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py index 5664b1e6..f2c6e295 100644 --- a/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py +++ b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py @@ -4,14 +4,13 @@ from ... import _domain from ... import _infrastructure +from time_tracker._infrastructure import DB -_JSON_PATH = ( - 'time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json' -) +DATABASE = DB() def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse: - time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) + time_entry_dao = _infrastructure.TimeEntriesSQLDao(DATABASE) time_entry_service = _domain.TimeEntryService(time_entry_dao) use_case = _domain._use_cases.DeleteTimeEntryUseCase(time_entry_service) @@ -21,20 +20,20 @@ def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse: deleted_time_entry = use_case.delete_time_entry(time_entry_id) if not deleted_time_entry: return func.HttpResponse( - body='Not found', - status_code=404, - mimetype="application/json" + body="Not found", + status_code=404, + mimetype="application/json" ) return func.HttpResponse( - body=json.dumps(deleted_time_entry.__dict__), - status_code=200, - mimetype="application/json" + body=json.dumps(deleted_time_entry.__dict__), + status_code=200, + mimetype="application/json", ) except ValueError: return func.HttpResponse( - body=b'Invalid Format ID', - status_code=400, + body=b"Invalid Format ID", + status_code=400, mimetype="application/json" ) diff --git a/V2/time_tracker/time_entries/_infrastructure/__init__.py b/V2/time_tracker/time_entries/_infrastructure/__init__.py index 0e90c78a..1c7a7d6d 100644 --- a/V2/time_tracker/time_entries/_infrastructure/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -1,2 +1,2 @@ # flake8: noqa -from ._data_persistence import TimeEntriesJsonDao +from ._data_persistence import TimeEntriesSQLDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py index 91ebd7cf..b999febe 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -1,2 +1,2 @@ # flake8: noqa -from ._time_entries_dao import TimeEntriesJsonDao +from ._time_entries_dao import TimeEntriesSQLDao diff --git a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py index e5a74516..9d0cd142 100644 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -1,58 +1,52 @@ import json import dataclasses import typing +import sqlalchemy +import sqlalchemy.sql as sql from time_tracker.time_entries._domain import TimeEntriesDao, TimeEntry - - -class TimeEntriesJsonDao(TimeEntriesDao): - - def __init__(self, json_data_file_path: str): - self.json_data_file_path = json_data_file_path - self.time_entry_key = [field.name for field in dataclasses.fields(TimeEntry)] - - def __get_time_entries_from_file(self) -> typing.List[dict]: - try: - file = open(self.json_data_file_path) - time_entries = json.load(file) - file.close() - - return time_entries - - except FileNotFoundError: - return [] +import time_tracker.time_entries._domain as domain +from time_tracker._infrastructure import _db + + +class TimeEntriesSQLDao(TimeEntriesDao): + def __init__(self, database: _db.DB): + self.time_entry_keys = [ + field.name for field in dataclasses.fields(domain.TimeEntry) + ] + self.db = database + self.time_entry = sqlalchemy.Table( + "time_entry", + self.db.metadata, + sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, autoincrement=True), + sqlalchemy.Column("start_date", sqlalchemy.DateTime), + sqlalchemy.Column("owner_id", sqlalchemy.Integer), + sqlalchemy.Column("description", sqlalchemy.String), + sqlalchemy.Column("activity_id", sqlalchemy.Integer, sqlalchemy.ForeignKey("activity.id")), + sqlalchemy.Column("uri", sqlalchemy.String), + sqlalchemy.Column("technologies", sqlalchemy.ARRAY(sqlalchemy.String).with_variant(sqlalchemy.String,"sqlite")), + sqlalchemy.Column("end_date", sqlalchemy.DateTime), + sqlalchemy.Column("deleted", sqlalchemy.Boolean), + sqlalchemy.Column("timezone_offset", sqlalchemy.String), + sqlalchemy.Column("project_id", sqlalchemy.Integer), + extend_existing=True, + ) def __create_time_entry_dto(self, time_entry: dict) -> TimeEntry: - time_entry = {key: time_entry.get(key) for key in self.time_entry_key} + time_entry = {key: time_entry.get(key) for key in self.time_entry_keys} return TimeEntry(**time_entry) - def delete(self, time_entry_id: int) -> TimeEntry: - time_entry = { - time_entry.get('id'): time_entry - for time_entry in self.__get_time_entries_from_file() - }.get(int(time_entry_id)) - - if time_entry: - time_entry_deleted = {**time_entry, 'deleted': True} - - time_entries_updated = list( - map( - lambda time_entry: time_entry - if time_entry.get('id') != time_entry_id - else time_entry_deleted, - self.__get_time_entries_from_file(), - ) - ) + def get_by_id(self, time_entry_id: int) -> domain.TimeEntry: + query = sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) + time_entry = self.db.get_session().execute(query).one_or_none() + return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None - try: - file = open(self.json_data_file_path, 'w') - json.dump(time_entries_updated, file) - file.close() - - return self.__create_time_entry_dto(time_entry_deleted) - - except FileNotFoundError: - return None + def delete(self, time_entry_id: int) -> TimeEntry: - else: - return None + query = ( + self.time_entry.update() + .where(self.time_entry.c.id == time_entry_id) + .values({"deleted": True}) + ) + self.db.get_session().execute(query) + return self.get_by_id(time_entry_id)