From 1448fc2bc8dce7d8f50c758a910182d7fe9c011a Mon Sep 17 00:00:00 2001 From: Sandro Castillo Date: Wed, 17 Nov 2021 16:00:01 -0500 Subject: [PATCH 01/14] ci: TT-411 inject secrets environment and test_db_connection (#351) --- .github/workflows/python-package.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 36bff27a..2f64bc87 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -29,6 +29,14 @@ jobs: run: | pip install -r V2/requirements.txt + - name: Inject Secrets + env: + ENVIRONMENT: ${{ secrets.environment }} + TEST_DB_CONNECTION: ${{ secrets.test_db_connection }} + run: | + echo $ENVIRONMENT + echo $TEST_DB_CONNECTION + - name: Lint with flake8 run: | cd V2 From 32ee36f39e81866c2f0767cf243c61afde6841c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Cobe=C3=B1a=20Cede=C3=B1o?= Date: Thu, 18 Nov 2021 10:05:00 -0500 Subject: [PATCH 02/14] feat: TT-399 Config use makefile to executing tests (#350) * feat: TT-399 Config use makefile to executing tests * feat: TT-399 quit comment on line for pip upgrade * fix: TT-399 inject environment variable for tests Co-authored-by: Alexander --- V2/Makefile | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/V2/Makefile b/V2/Makefile index 45080238..135e96d0 100644 --- a/V2/Makefile +++ b/V2/Makefile @@ -1,8 +1,33 @@ +.PHONY: help +help: + @echo "---------------HELP-----------------" + @echo "To install the dependencies type make install" + @echo "To test the project type make test" + @echo "To run the local database type make start-local" + @echo "To run all comands type make ci" + @echo "------------------------------------" + +.PHONY: install install: - @echo "Installing Time Tracker" + @echo "=========================================Installing dependencies Time Tracker=========================================" npm install pip install --upgrade pip pip install -r requirements.txt @echo "Completed! " + +.PHONY: test +test: export ENVIRONMENT = test +test: export TEST_DB_CONNECTION = sqlite:///:memory: +test: + @echo "=========================================Lint with flake8=========================================" + flake8 . --show-source --statistics + @echo "Completed flake8!" + @echo "=========================================Test with pytest=========================================" + python -m pytest -v + @echo "Completed test!" + start-local: - docker compose up \ No newline at end of file + docker compose up + +.PHONY: ci +ci: install test \ No newline at end of file From 10cc4269e4e60c6eff77bf1cf02cdf0d31dac86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Cobe=C3=B1a=20Cede=C3=B1o?= Date: Thu, 18 Nov 2021 17:49:50 -0500 Subject: [PATCH 03/14] docs: TT-399 Readme update how to use makefile (#354) * docs: TT-399 Readme update how to use makefile * docs: TT-399 Readme update reqs to use makefile * docs: TT-399 Text correction --- V2/README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/V2/README.md b/V2/README.md index e84c0268..f414079d 100644 --- a/V2/README.md +++ b/V2/README.md @@ -1,3 +1,23 @@ -# Azure Functions +# time-tracker-api V2 Refer to [Serverless docs](https://serverless.com/framework/docs/providers/azure/guide/intro/) for more information. + +## Requirements to use makefile + +- Python version 3.6 or 3.7. + +- Use an environment to install requirements (pyenv). + +## How to use makefile + +Execute the next command to show makefile help: + +```shell +make help +``` + +- To install the dependencies type the command ```make install``` + +- To test the project type the command ```make test``` + +- To run the local database type the command ```make start-local``` From 5f107f33cb640f7fa8e498db2157efb2d11f401d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rafael=20Arcos=20G=C3=B3mez?= <37599693+ararcos@users.noreply.github.com> Date: Fri, 19 Nov 2021 09:37:54 -0500 Subject: [PATCH 04/14] feat: TT-401-Post-v2-time-entries (#344) * feat: TT-401 Implemented service, end-point, dao, test- time entries * feat: TT-401 validated request create time entry * fix: TT-401 implemented faker url * refactor: TT-401 changed the variable name * fix: implementation of the database connection * refactor: TT-401 fixtures changed * fix: TT-401 solution of comments, change of config * ci: TT-401 fix inject secrets * refactor: TT-401 rename of functions and imports * fix: TT-401 changed test db storage --- .github/workflows/python-package.yml | 11 +--- V2/create_activity/function.json | 22 ------- V2/delete_activity/function.json | 22 ------- V2/get_activities/function.json | 22 ------- V2/serverless.yml | 10 +++ .../azure/activity_azure_endpoints_test.py | 49 +++++--------- .../azure/time_entry_azure_endpoints_test.py | 28 ++++++++ V2/tests/conftest.py | 3 +- V2/tests/fixtures.py | 64 +++++++++++++++---- .../daos/activities_sql_dao_test.py | 21 +++--- .../integration/daos/time_entries_dao_test.py | 48 ++++++++++++++ .../unit/services/time_entry_service_test.py | 14 ++++ .../use_cases/time_entries_use_case_test.py | 18 ++++++ V2/time_tracker/_infrastructure/_config.py | 15 ++--- V2/time_tracker/_infrastructure/_db.py | 2 +- .../time_entries/_application/__init__.py | 2 + .../_application/_time_entries/__init__.py | 2 + .../_time_entries/_create_time_entry.py | 63 ++++++++++++++++++ .../time_entries/_domain/__init__.py | 7 ++ .../_domain/_entities/__init__.py | 2 + .../_domain/_entities/_time_entry.py | 17 +++++ .../_persistence_contracts/__init__.py | 2 + .../_time_entries_dao.py | 9 +++ .../_domain/_services/__init__.py | 2 + .../_domain/_services/_time_entry.py | 10 +++ .../_domain/_use_cases/__init__.py | 2 + .../_use_cases/_create_time_entry_use_case.py | 10 +++ .../time_entries/_infrastructure/__init__.py | 2 + .../_data_persistence/__init__.py | 2 + .../_data_persistence/_time_entries_dao.py | 49 ++++++++++++++ V2/time_tracker/time_entries/interface.py | 2 + V2/update_activity/function.json | 22 ------- 32 files changed, 388 insertions(+), 166 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 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/interface.py delete mode 100644 V2/update_activity/function.json diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2f64bc87..1c700563 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,6 +14,9 @@ jobs: runs-on: ubuntu-latest strategy: max-parallel: 5 + env: + ENVIRONMENT: ${{ secrets.ENVIRONMENT }} + TEST_DB_CONNECTION: ${{ secrets.TEST_DB_CONNECTION }} steps: - uses: actions/checkout@v2 - name: Set up Python 3.10.0 @@ -29,14 +32,6 @@ jobs: run: | pip install -r V2/requirements.txt - - name: Inject Secrets - env: - ENVIRONMENT: ${{ secrets.environment }} - TEST_DB_CONNECTION: ${{ secrets.test_db_connection }} - run: | - echo $ENVIRONMENT - echo $TEST_DB_CONNECTION - - name: Lint with flake8 run: | cd V2 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/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/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 9b2618a8..994c74c7 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -1,37 +1,24 @@ -import pytest import json from faker import Faker import azure.functions as func import time_tracker.activities._application._activities as azure_activities -import time_tracker.activities._infrastructure as infrastructure -from time_tracker._infrastructure import DB -from time_tracker.activities import _domain ACTIVITY_URL = '/api/activities/' -@pytest.fixture(name='insert_activity') -def _insert_activity() -> dict: - def _new_activity(activity: _domain.Activity, database: DB): - dao = infrastructure.ActivitiesSQLDao(database) - new_activity = dao.create(activity) - return new_activity.__dict__ - return _new_activity - - def test__activity_azure_endpoint__returns_all_activities( - create_fake_database, activity_factory, insert_activity + test_db, activity_factory, insert_activity ): - fake_database = create_fake_database existent_activities = [activity_factory(), activity_factory()] inserted_activities = [ - insert_activity(existent_activities[0], fake_database), - insert_activity(existent_activities[1], fake_database) + insert_activity(existent_activities[0], test_db).__dict__, + insert_activity(existent_activities[1], test_db).__dict__ ] - azure_activities._get_activities.DATABASE = fake_database + azure_activities._get_activities.DATABASE = test_db + req = func.HttpRequest(method='GET', body=None, url=ACTIVITY_URL) response = azure_activities._get_activities.get_activities(req) activities_json_data = response.get_body().decode("utf-8") @@ -41,13 +28,12 @@ def test__activity_azure_endpoint__returns_all_activities( def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_its_id( - create_fake_database, activity_factory, insert_activity + test_db, activity_factory, insert_activity ): - fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, fake_database) + inserted_activity = insert_activity(existent_activity, test_db).__dict__ - azure_activities._get_activities.DATABASE = fake_database + azure_activities._get_activities.DATABASE = test_db req = func.HttpRequest( method='GET', body=None, @@ -63,13 +49,12 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found( - create_fake_database, activity_factory, insert_activity + test_db, activity_factory, insert_activity ): - fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, fake_database) + inserted_activity = insert_activity(existent_activity, test_db).__dict__ - azure_activities._delete_activity.DATABASE = fake_database + azure_activities._delete_activity.DATABASE = test_db req = func.HttpRequest( method='DELETE', body=None, @@ -86,13 +71,12 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update( - create_fake_database, activity_factory, insert_activity + test_db, activity_factory, insert_activity ): - fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, fake_database) + inserted_activity = insert_activity(existent_activity, test_db).__dict__ - azure_activities._update_activity.DATABASE = fake_database + azure_activities._update_activity.DATABASE = test_db activity_body = {"description": Faker().sentence()} req = func.HttpRequest( method='PUT', @@ -109,10 +93,7 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act assert activitiy_json_data == json.dumps(inserted_activity) -def test__activity_azure_endpoint__creates_an_activity__when_activity_has_all_attributes( - create_fake_database, - ): - azure_activities._create_activity.DATABASE = create_fake_database +def test__activity_azure_endpoint__creates_an_activity__when_activity_has_all_attributes(): activity_body = { 'id': None, 'name': Faker().user_name(), 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..f801dad9 --- /dev/null +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -0,0 +1,28 @@ +import json + +import azure.functions as func + +import time_tracker.time_entries._application._time_entries as azure_time_entries + +TIME_ENTRY_URL = "/api/time-entries/" + + +def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( + test_db, time_entry_factory, activity_factory, insert_activity +): + inserted_activity = insert_activity(activity_factory(), test_db) + time_entry_body = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]").__dict__ + + body = json.dumps(time_entry_body).encode("utf-8") + req = func.HttpRequest( + method='POST', + body=body, + url=TIME_ENTRY_URL, + ) + + 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 == time_entry_body diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index d1c4928f..cf6e362f 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 \ No newline at end of file +from fixtures import _activity_factory, _test_db, _insert_activity +from fixtures import _time_entry_factory diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index d9539035..51ee5e5d 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -1,17 +1,18 @@ 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 activities_domain +import time_tracker.activities._infrastructure as activities_infrastructure +import time_tracker.time_entries._domain as time_entries_domain from time_tracker._infrastructure import DB -from faker import Faker @pytest.fixture(name='activity_factory') -def _activity_factory() -> domain.Activity: +def _activity_factory() -> activities_domain.Activity: def _make_activity( name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1 ): - activity = domain.Activity( + activity = activities_domain.Activity( id=None, name=name, description=description, @@ -22,14 +23,49 @@ 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='test_db') +def _test_db() -> DB: + db_fake = DB() + db_fake.get_session().execute("pragma foreign_keys=ON") + return db_fake -@pytest.fixture(name='create_fake_database') -def _create_fake_database() -> domain.ActivitiesDao: - db_fake = DB('sqlite:///:memory:') - return db_fake +@pytest.fixture(name='time_entry_factory') +def _time_entry_factory() -> time_entries_domain.TimeEntry: + def _make_time_entry( + id=Faker().random_int(), + 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=str(Faker().date_time()), + deleted=False, + timezone_offset="300", + project_id=Faker().random_int(), + ): + time_entry = time_entries_domain.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 + + +@pytest.fixture(name='insert_activity') +def _insert_activity() -> dict: + def _new_activity(activity: activities_domain.Activity, database: DB): + dao = activities_infrastructure.ActivitiesSQLDao(database) + new_activity = dao.create(activity) + return new_activity + return _new_activity diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py index 25f62500..0f0170af 100644 --- a/V2/tests/integration/daos/activities_sql_dao_test.py +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -7,12 +7,11 @@ from time_tracker._infrastructure import DB -@pytest.fixture(name='insert_activity') -def _insert_activity() -> domain.Activity: - def _new_activity(activity: domain.Activity, dao: domain.ActivitiesDao): - new_activity = dao.create(activity) - return new_activity - 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) @@ -41,7 +40,7 @@ def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is ): dao = create_fake_dao existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, dao) + inserted_activity = insert_activity(existent_activity, dao.db) expected_description = Faker().sentence() updated_activity = dao.update(inserted_activity.id, None, expected_description, None, None) @@ -68,8 +67,8 @@ def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_acti dao = create_fake_dao existent_activities = [activity_factory(), activity_factory()] inserted_activities = [ - insert_activity(existent_activities[0], dao), - insert_activity(existent_activities[1], dao) + insert_activity(existent_activities[0], dao.db), + insert_activity(existent_activities[1], dao.db) ] activities = dao.get_all() @@ -83,7 +82,7 @@ def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matche ): dao = create_fake_dao existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, dao) + inserted_activity = insert_activity(existent_activity, dao.db) activity = dao.get_by_id(inserted_activity.id) @@ -117,7 +116,7 @@ def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matc ): dao = create_fake_dao existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity, dao) + inserted_activity = insert_activity(existent_activity, dao.db) activity = dao.delete(inserted_activity.id) 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..403f80c6 --- /dev/null +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -0,0 +1,48 @@ +import pytest + + +import time_tracker.time_entries._domain as domain +import time_tracker.time_entries._infrastructure as infrastructure +from time_tracker._infrastructure import DB + + +@pytest.fixture(name='create_fake_dao') +def _fake_dao() -> domain.TimeEntriesDao: + def _create_fake_dao(db_fake: DB) -> domain.TimeEntriesDao: + dao = infrastructure.TimeEntriesSQLDao(db_fake) + return dao + return _create_fake_dao + + +@pytest.fixture(name='clean_database', autouse=True) +def _clean_database(): + yield + db_fake = DB() + dao = infrastructure.TimeEntriesSQLDao(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( + test_db, time_entry_factory, create_fake_dao, insert_activity, activity_factory +): + dao = create_fake_dao(test_db) + inserted_activity = insert_activity(activity_factory(), dao.db) + + time_entry_to_insert = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]") + + inserted_time_entry = dao.create(time_entry_to_insert) + + assert isinstance(inserted_time_entry, domain.TimeEntry) + assert inserted_time_entry == time_entry_to_insert + + +def test__time_entry__returns_None__when_not_saves_correctly( + time_entry_factory, create_fake_dao, test_db +): + dao = create_fake_dao(test_db) + time_entry_to_insert = time_entry_factory(activity_id=1203, technologies="[jira,sql]") + + inserted_time_entry = dao.create(time_entry_to_insert) + + 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 new file mode 100644 index 00000000..bd5ce085 --- /dev/null +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -0,0 +1,14 @@ +from time_tracker.time_entries._domain import TimeEntryService + + +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(time_entry_factory()) + + assert time_entry_dao.create.called + 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 new file mode 100644 index 00000000..d2a31eb7 --- /dev/null +++ b/V2/tests/unit/use_cases/time_entries_use_case_test.py @@ -0,0 +1,18 @@ +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, 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 diff --git a/V2/time_tracker/_infrastructure/_config.py b/V2/time_tracker/_infrastructure/_config.py index 7f8c8fa7..cf4f19bf 100644 --- a/V2/time_tracker/_infrastructure/_config.py +++ b/V2/time_tracker/_infrastructure/_config.py @@ -1,20 +1,17 @@ import typing import os -CONNECTION_STRING = 'postgresql://root:root@localhost:5433/timetracker' - class Config(typing.NamedTuple): DB_CONNECTION_STRING: str - DB_USER: str - DB_PASS: str - DB_NAME: str def load_config(): + if os.environ.get("ENVIRONMENT") == "development": + connection: str = os.environ.get("DB_CONNECTION") + else: + connection: str = os.environ.get("TEST_DB_CONNECTION") + return Config( - CONNECTION_STRING if os.environ.get("DB_CONNECTION_STRING") is None else os.environ.get("DB_CONNECTION_STRING"), - os.environ.get("DB_USER"), - os.environ.get("DB_PASS"), - os.environ.get("DB_NAME") + connection ) 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/__init__.py b/V2/time_tracker/time_entries/_application/__init__.py new file mode 100644 index 00000000..6e4ba9c3 --- /dev/null +++ b/V2/time_tracker/time_entries/_application/__init__.py @@ -0,0 +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 new file mode 100644 index 00000000..b46cddce --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/__init__.py @@ -0,0 +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 new file mode 100644 index 00000000..a06c212c --- /dev/null +++ b/V2/time_tracker/time_entries/_application/_time_entries/_create_time_entry.py @@ -0,0 +1,63 @@ +import dataclasses +import json +import typing + +import azure.functions as func + +from ... import _domain +from ... import _infrastructure +from time_tracker._infrastructure import DB + + +def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: + database = DB() + time_entry_dao = _infrastructure.TimeEntriesSQLDao(database) + 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 new file mode 100644 index 00000000..a8b2081c --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/__init__.py @@ -0,0 +1,7 @@ +# flake8: noqa +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..88b4a739 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/__init__.py @@ -0,0 +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 new file mode 100644 index 00000000..aa73a879 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_entities/_time_entry.py @@ -0,0 +1,17 @@ +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: bool + timezone_offset: str + 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 new file mode 100644 index 00000000..e10700ce --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/__init__.py @@ -0,0 +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 new file mode 100644 index 00000000..5d04c861 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py @@ -0,0 +1,9 @@ +import abc + +from time_tracker.time_entries._domain import TimeEntry + + +class TimeEntriesDao(abc.ABC): + @abc.abstractmethod + def create(self, time_entry_data: TimeEntry) -> TimeEntry: + pass 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..e5e6ba1b --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/__init__.py @@ -0,0 +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 new file mode 100644 index 00000000..d7aaf3ba --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_services/_time_entry.py @@ -0,0 +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: TimeEntry) -> 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 new file mode 100644 index 00000000..41aca738 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/__init__.py @@ -0,0 +1,2 @@ +# 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 new file mode 100644 index 00000000..f2258468 --- /dev/null +++ b/V2/time_tracker/time_entries/_domain/_use_cases/_create_time_entry_use_case.py @@ -0,0 +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: TimeEntry) -> TimeEntry: + return self.time_entry_service.create(time_entry_data) 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..1c7a7d6d --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa +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 new file mode 100644 index 00000000..b999febe --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa +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 new file mode 100644 index 00000000..d233f3e9 --- /dev/null +++ b/V2/time_tracker/time_entries/_infrastructure/_data_persistence/_time_entries_dao.py @@ -0,0 +1,49 @@ +import dataclasses + +import sqlalchemy + +import time_tracker.time_entries._domain as domain +from time_tracker._infrastructure import _db + + +class TimeEntriesSQLDao(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: + new_time_entry = time_entry_data.__dict__ + new_time_entry.pop('id', None) + + 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 sqlalchemy.exc.SQLAlchemyError: + return None + + 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 domain.TimeEntry(**time_entry) diff --git a/V2/time_tracker/time_entries/interface.py b/V2/time_tracker/time_entries/interface.py new file mode 100644 index 00000000..d0182780 --- /dev/null +++ b/V2/time_tracker/time_entries/interface.py @@ -0,0 +1,2 @@ +# flake8: noqa +from ._application import create_time_entry \ 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 4676dc46ee3769b4b235b7d5b2150fdb58a9feee Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 10 Nov 2021 18:37:28 -0500 Subject: [PATCH 05/14] feat: TT-401 Implemented service, end-point, dao, test- time entries --- .../integration/daos/time_entries_dao.test.py | 45 +++++++++ .../_data_persistence/time_entries_data.json | 92 +++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 V2/tests/integration/daos/time_entries_dao.test.py create mode 100644 V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json 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/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 From 39d2b243437ca05a4944d5ee53eefa7e350b8e61 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:04:18 -0500 Subject: [PATCH 06/14] feat: TT-401 validated request create time entry --- V2/tests/fixtures.py | 6 +- .../integration/daos/time_entries_dao.test.py | 20 ++-- .../_time_entries/_create_time_entry.py | 1 + .../_data_persistence/time_entries_data.json | 93 +------------------ 4 files changed, 15 insertions(+), 105 deletions(-) diff --git a/V2/tests/fixtures.py b/V2/tests/fixtures.py index 51ee5e5d..8568bdb6 100644 --- a/V2/tests/fixtures.py +++ b/V2/tests/fixtures.py @@ -10,7 +10,10 @@ @pytest.fixture(name='activity_factory') def _activity_factory() -> activities_domain.Activity: def _make_activity( - name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1 + name: str = Faker().name(), + description: str = Faker().sentence(), + deleted: bool = False, + status: int = 1, ): activity = activities_domain.Activity( id=None, @@ -20,6 +23,7 @@ def _make_activity( status=status ) return activity + return _make_activity 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/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 a06c212c..c995ec8a 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 @@ -9,6 +9,7 @@ from time_tracker._infrastructure import DB + def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: database = DB() time_entry_dao = _infrastructure.TimeEntriesSQLDao(database) 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 From ddf305c935ea124881c9f1952ee32cd8869cd412 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 13:47:20 -0500 Subject: [PATCH 07/14] fix: TT-401 implemented faker url --- .../integration/daos/time_entries_dao.test.py | 18 ++------- .../_data_persistence/time_entries_data.json | 37 ++++++++++++++++++- 2 files changed, 39 insertions(+), 16 deletions(-) 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/_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 5c6d6697a61de033240fe6b9967a25f7123499ff Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 15:31:31 -0500 Subject: [PATCH 08/14] feat: TT-403 created end-point to DELETE of time_entries --- V2/serverless.yml | 10 +++++ .../azure/time_entry_azure_endpoints_test.py | 37 +++++++++++++++++ .../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 +- 15 files changed, 200 insertions(+), 3 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 f801dad9..374ee509 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -26,3 +26,40 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ assert response.status_code == 201 assert time_entry_json_data == time_entry_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/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 bd5ce085..7741bcd8 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -12,3 +12,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 5d04c861..e7d94608 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: TimeEntry) -> 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 d7aaf3ba..fe29859e 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: TimeEntry) -> 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 d233f3e9..7586d225 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 @@ -47,3 +47,34 @@ def create(self, time_entry_data: domain.TimeEntry) -> domain.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 domain.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 21ff57cebc56c9d55e544c1fbc5dd20ccc7308f5 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 16:15:05 -0500 Subject: [PATCH 09/14] 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 374ee509..c1a4a264 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -28,7 +28,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ assert time_entry_json_data == time_entry_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 @@ -62,4 +62,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 7741bcd8..d872f136 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -26,4 +26,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 4bc146ac5d26f98683f8b3cca638f6552be25017 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 16:27:42 -0500 Subject: [PATCH 10/14] fix: TT-403 remove method POST --- V2/serverless.yml | 10 ---------- .../integration/daos/time_entries_dao.test.py | 11 ----------- .../unit/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 | 8 +++++--- V2/time_tracker/time_entries/_domain/__init__.py | 1 - .../time_entries/_domain/_use_cases/__init__.py | 1 - .../_use_cases/_create_time_entry_use_case.py | 2 +- V2/time_tracker/time_entries/interface.py | 1 - 10 files changed, 6 insertions(+), 45 deletions(-) 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/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/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 index c995ec8a..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 @@ -6,13 +6,15 @@ 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' +) def create_time_entry(req: func.HttpRequest) -> func.HttpResponse: - database = DB() - time_entry_dao = _infrastructure.TimeEntriesSQLDao(database) + + time_entry_dao = _infrastructure.TimeEntriesJsonDao(_JSON_PATH) 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/__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/_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 index f2258468..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 @@ -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) + return self.time_entry_service.create(time_entry_data.__dict__) 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 93d8f16f95fac4e15c89608df2867387df95c9b6 Mon Sep 17 00:00:00 2001 From: mandres2015 Date: Mon, 15 Nov 2021 12:07:57 -0500 Subject: [PATCH 11/14] feat: TT-403 rebase with master --- V2/delete_time_entry/function.json | 22 +++++++++++++++++++ .../_data_persistence/time_entries_data.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 V2/delete_time_entry/function.json diff --git a/V2/delete_time_entry/function.json b/V2/delete_time_entry/function.json new file mode 100644 index 00000000..1c1c0720 --- /dev/null +++ b/V2/delete_time_entry/function.json @@ -0,0 +1,22 @@ +{ + "disabled": false, + "bindings": [ + { + "type": "httpTrigger", + "direction": "in", + "name": "req", + "route": "time-entries/{id}", + "authLevel": "anonymous", + "methods": [ + "DELETE" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ], + "entryPoint": "delete_time_entry", + "scriptFile": "../time_tracker/time_entries/interface.py" +} \ 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 index aa3bef88..4749ceda 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 @@ -[{"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 +[{"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": true, "timezone_offset": "300", "project_id": 1}] \ No newline at end of file From 486c997c1e1aac62e8b76f0f01133fbf6b99d9aa Mon Sep 17 00:00:00 2001 From: mandres2015 Date: Tue, 16 Nov 2021 15:25:54 -0500 Subject: [PATCH 12/14] feat: TT-403 tests added --- V2/delete_time_entry/function.json | 22 --------- .../azure/activity_azure_endpoints_test.py | 1 + .../azure/time_entry_azure_endpoints_test.py | 41 ++++++++++++----- .../integration/daos/time_entries_dao.test.py | 45 ------------------- .../integration/daos/time_entries_dao_test.py | 24 ++++++++++ V2/time_tracker/_infrastructure/_db.py | 3 ++ .../_time_entries/_delete_time_entry.py | 22 +++++---- .../_data_persistence/_time_entries_dao.py | 38 ++++------------ .../_data_persistence/time_entries_data.json | 1 - 9 files changed, 77 insertions(+), 120 deletions(-) delete mode 100644 V2/delete_time_entry/function.json delete 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 diff --git a/V2/delete_time_entry/function.json b/V2/delete_time_entry/function.json deleted file mode 100644 index 1c1c0720..00000000 --- a/V2/delete_time_entry/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "time-entries/{id}", - "authLevel": "anonymous", - "methods": [ - "DELETE" - ] - }, - { - "type": "http", - "direction": "out", - "name": "$return" - } - ], - "entryPoint": "delete_time_entry", - "scriptFile": "../time_tracker/time_entries/interface.py" -} \ No newline at end of file diff --git a/V2/tests/api/azure/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 994c74c7..dbe44176 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -5,6 +5,7 @@ import time_tracker.activities._application._activities as azure_activities + ACTIVITY_URL = '/api/activities/' 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 c1a4a264..b3938054 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -1,12 +1,26 @@ +import pytest import json import azure.functions as func import time_tracker.time_entries._application._time_entries as azure_time_entries +from time_tracker._infrastructure import DB +from time_tracker.time_entries import _domain as domain_time_entries +from time_tracker.time_entries import _infrastructure as infrastructure_time_entries + TIME_ENTRY_URL = "/api/time-entries/" +@pytest.fixture(name='insert_time_entry') +def _insert_time_entry() -> domain_time_entries.TimeEntry: + def _new_time_entry(time_entry: domain_time_entries.TimeEntry, database: DB): + dao = infrastructure_time_entries.TimeEntriesSQLDao(database) + new_time_entry = dao.create(time_entry) + return new_time_entry + return _new_time_entry + + def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes( test_db, time_entry_factory, activity_factory, insert_activity ): @@ -29,29 +43,34 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ 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, insert_activity, activity_factory, ): - time_entries_json, tmp_directory = create_temp_time_entries - time_entries.delete_time_entry.JSON_PATH = tmp_directory + db = create_fake_database + azure_time_entries._delete_time_entry.DATABASE = db + inserted_activity = insert_activity(activity_factory(), db).__dict__ + time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]") + inserted_time_entry = insert_time_entry(time_entry_body, db) + req = func.HttpRequest( - method="DELETE", + method='DELETE', body=None, url=TIME_ENTRY_URL, - route_params={"id": time_entries_json[0]["id"]}, + route_params={"id": inserted_time_entry.id}, ) - response = time_entries.delete_time_entry(req) + response = azure_time_entries._delete_time_entry.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 + 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, + create_fake_database, ): - tmp_directory = create_temp_time_entries - time_entries.delete_time_entry.JSON_PATH = tmp_directory + db = create_fake_database + azure_time_entries._delete_time_entry.DATABASE = db + req = func.HttpRequest( method="DELETE", body=None, @@ -59,7 +78,7 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti route_params={"id": "invalid id"}, ) - response = time_entries.delete_time_entry(req) + response = azure_time_entries._delete_time_entry.delete_time_entry(req) assert response.status_code == 400 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 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 index 403f80c6..3318311a 100644 --- a/V2/tests/integration/daos/time_entries_dao_test.py +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -46,3 +46,27 @@ def test__time_entry__returns_None__when_not_saves_correctly( inserted_time_entry = dao.create(time_entry_to_insert) assert inserted_time_entry is None + + + +def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( + create_fake_dao, time_entry_factory, 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) + + result = dao.delete(inserted_time_entry.id) + + assert result.deleted is True + + +def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found( + create_fake_dao, +): + dao = create_fake_dao + + result = dao.delete(Faker().pyint()) + + assert result is None diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index 6f3a9f9a..c9e62348 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -2,6 +2,7 @@ from . import _config +_TEST_DIALECT = "sqlite" class DB(): config = _config.load_config() @@ -17,4 +18,6 @@ def get_session(self): self.metadata.create_all(self.engine) if self.connection is None: 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/_delete_time_entry.py b/V2/time_tracker/time_entries/_application/_time_entries/_delete_time_entry.py index 5664b1e6..8a4be18c 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,37 +4,35 @@ 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) 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" + 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__, default=str), + status_code=200, + mimetype="application/json", ) except ValueError: return func.HttpResponse( - body=b'Invalid Format ID', + body=b"Invalid Format ID", status_code=400, mimetype="application/json" ) 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 7586d225..a7ff4508 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 @@ -49,32 +49,12 @@ def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry: return domain.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 + query = ( + self.time_entry.update() + .where(self.time_entry.c.id == time_entry_id) + .values({"deleted": True}) + ) + self.db.get_session().execute(query) + query_deleted_time_entry = sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) + time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none() + return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None 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 4749ceda..00000000 --- a/V2/time_tracker/time_entries/_infrastructure/_data_persistence/time_entries_data.json +++ /dev/null @@ -1 +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": true, "timezone_offset": "300", "project_id": 1}] \ No newline at end of file From b79d442b4f0cfd9bf8accac4f528fe4ada8bdbf6 Mon Sep 17 00:00:00 2001 From: mandres2015 Date: Tue, 16 Nov 2021 15:30:57 -0500 Subject: [PATCH 13/14] refactor: TT-403 correct flake8 lint syntax --- V2/time_tracker/_infrastructure/_db.py | 1 + 1 file changed, 1 insertion(+) diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index c9e62348..338c6b00 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -4,6 +4,7 @@ _TEST_DIALECT = "sqlite" + class DB(): config = _config.load_config() connection = None From db386a02a8058bea10d07cf15cb2c36fa0809ea0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 19 Nov 2021 12:22:56 -0500 Subject: [PATCH 14/14] fix: TT-403 rebase onto master --- V2/tests/api/azure/time_entry_azure_endpoints_test.py | 8 ++++---- V2/tests/integration/daos/time_entries_dao_test.py | 3 +-- V2/tests/unit/services/time_entry_service_test.py | 2 ++ .../_data_persistence/_time_entries_dao.py | 4 ++-- 4 files changed, 9 insertions(+), 8 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 b3938054..a9623b3c 100644 --- a/V2/tests/api/azure/time_entry_azure_endpoints_test.py +++ b/V2/tests/api/azure/time_entry_azure_endpoints_test.py @@ -43,9 +43,9 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found( - create_fake_database, time_entry_factory, insert_time_entry, insert_activity, activity_factory, + test_db, time_entry_factory, insert_time_entry, insert_activity, activity_factory, ): - db = create_fake_database + db = test_db azure_time_entries._delete_time_entry.DATABASE = db inserted_activity = insert_activity(activity_factory(), db).__dict__ time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]") @@ -66,9 +66,9 @@ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_de def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id( - create_fake_database, + test_db, ): - db = create_fake_database + db = test_db azure_time_entries._delete_time_entry.DATABASE = db req = func.HttpRequest( diff --git a/V2/tests/integration/daos/time_entries_dao_test.py b/V2/tests/integration/daos/time_entries_dao_test.py index 3318311a..86607479 100644 --- a/V2/tests/integration/daos/time_entries_dao_test.py +++ b/V2/tests/integration/daos/time_entries_dao_test.py @@ -1,5 +1,5 @@ import pytest - +from faker import Faker import time_tracker.time_entries._domain as domain import time_tracker.time_entries._infrastructure as infrastructure @@ -48,7 +48,6 @@ def test__time_entry__returns_None__when_not_saves_correctly( assert inserted_time_entry is None - def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found( create_fake_dao, time_entry_factory, insert_activity, activity_factory ): diff --git a/V2/tests/unit/services/time_entry_service_test.py b/V2/tests/unit/services/time_entry_service_test.py index d872f136..779eefa2 100644 --- a/V2/tests/unit/services/time_entry_service_test.py +++ b/V2/tests/unit/services/time_entry_service_test.py @@ -1,3 +1,5 @@ +from faker import Faker + from time_tracker.time_entries._domain import TimeEntryService 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 a7ff4508..6037af9f 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 @@ -48,13 +48,13 @@ 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 domain.TimeEntry(**time_entry) - def delete(self, time_entry_id: int) -> TimeEntry: + def delete(self, time_entry_id: int) -> domain.TimeEntry: query = ( self.time_entry.update() .where(self.time_entry.c.id == time_entry_id) .values({"deleted": True}) ) self.db.get_session().execute(query) - query_deleted_time_entry = sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) + query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id) time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none() return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None