From 713559afa795bd15113905826db35ae64ea02b6f Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 11 Nov 2021 13:54:42 -0500 Subject: [PATCH 1/6] feat: TT-357 Change Json Implementation for SQL --- V2/Makefile | 3 +- V2/docker-compose.yml | 10 ++ V2/requirements.txt | 7 +- V2/tests/api/api_fixtures.py | 54 ++---- .../azure/activity_azure_endpoints_test.py | 160 +++++++++++------- V2/tests/conftest.py | 3 +- .../daos/activities_json_dao_test.py | 152 ----------------- .../daos/activities_sql_dao_test.py | 131 ++++++++++++++ .../unit/services/activity_service_test.py | 7 +- .../use_cases/activities_use_case_test.py | 35 ++-- V2/time_tracker/_infrastructure/__init__.py | 2 + V2/time_tracker/_infrastructure/_config.py | 12 ++ V2/time_tracker/_infrastructure/_db.py | 22 +++ .../activities/_application/__init__.py | 1 - .../_application/_activities/__init__.py | 1 - .../_activities/_create_activity.py | 16 +- .../_activities/_delete_activity.py | 24 +-- .../_activities/_get_activities.py | 28 +-- .../_activities/_update_activity.py | 26 +-- .../activities/_domain/__init__.py | 1 - .../activities/_domain/_entities/__init__.py | 1 - .../activities/_domain/_entities/_activity.py | 7 +- .../_persistence_contracts/__init__.py | 1 - .../_persistence_contracts/_activities_dao.py | 4 +- .../activities/_domain/_services/__init__.py | 1 - .../activities/_domain/_services/_activity.py | 4 +- .../activities/_domain/_use_cases/__init__.py | 1 - .../_use_cases/_create_activity_use_case.py | 6 +- .../activities/_infrastructure/__init__.py | 3 +- .../_data_persistence/__init__.py | 3 +- .../_data_persistence/_activities_json_dao.py | 105 ------------ .../_data_persistence/_activities_sql_dao.py | 60 +++++++ .../_data_persistence/activities_data.json | 65 ------- V2/time_tracker/activities/interface.py | 1 - 34 files changed, 445 insertions(+), 512 deletions(-) create mode 100644 V2/docker-compose.yml delete mode 100644 V2/tests/integration/daos/activities_json_dao_test.py create mode 100644 V2/tests/integration/daos/activities_sql_dao_test.py create mode 100644 V2/time_tracker/_infrastructure/__init__.py create mode 100644 V2/time_tracker/_infrastructure/_config.py create mode 100644 V2/time_tracker/_infrastructure/_db.py delete mode 100644 V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_json_dao.py create mode 100644 V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py delete mode 100644 V2/time_tracker/activities/_infrastructure/_data_persistence/activities_data.json diff --git a/V2/Makefile b/V2/Makefile index 9a0956ba..62570676 100644 --- a/V2/Makefile +++ b/V2/Makefile @@ -4,4 +4,5 @@ install: pip install --upgrade pip pip install -r requirements.txt @echo "Completed! " - +start-local: + docker compose up \ No newline at end of file diff --git a/V2/docker-compose.yml b/V2/docker-compose.yml new file mode 100644 index 00000000..a89f5250 --- /dev/null +++ b/V2/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.9' +services: + database: + image: postgres:14 + ports: + - "5433:5432" + environment: + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASS} + - POSTGRES_DB=${DB_NAME} \ No newline at end of file diff --git a/V2/requirements.txt b/V2/requirements.txt index c651bb35..4036bb92 100644 --- a/V2/requirements.txt +++ b/V2/requirements.txt @@ -4,10 +4,13 @@ azure-functions-worker # Tests pytest -flake8==4.0.1 # Mocking pytest-mock # To create sample content in tests and API documentation -Faker==4.0.2 \ No newline at end of file +Faker==4.0.2 + +#SQL ALCHEMY +SQLAlchemy==1.4.24 +psycopg2==2.9.1 \ No newline at end of file diff --git a/V2/tests/api/api_fixtures.py b/V2/tests/api/api_fixtures.py index 21b58021..301c9d1f 100644 --- a/V2/tests/api/api_fixtures.py +++ b/V2/tests/api/api_fixtures.py @@ -1,41 +1,23 @@ -import json import pytest -import shutil +import time_tracker.activities._domain as domain +import time_tracker.activities._infrastructure as infrastructure +from time_tracker._infrastructure import DB -@pytest.fixture -def create_temp_activities(tmpdir_factory): - temporary_directory = tmpdir_factory.mktemp("tmp") - json_file = temporary_directory.join("activities.json") - activities = [ - { - 'id': 'c61a4a49-3364-49a3-a7f7-0c5f2d15072b', - 'name': 'Development', - 'description': 'Development', - 'deleted': 'b4327ba6-9f96-49ee-a9ac-3c1edf525172', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - { - 'id': '94ec92e2-a500-4700-a9f6-e41eb7b5507c', - 'name': 'Management', - 'description': 'Description of management', - 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - { - 'id': 'd45c770a-b1a0-4bd8-a713-22c01a23e41b', - 'name': 'Operations', - 'description': 'Operation activities performed.', - 'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a', - 'status': 'active', - 'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05', - }, - ] +@pytest.fixture(name='activity_factory') +def _activity_factory() -> domain.Activity: + def _make_activity(data: dict): + activity = domain.Activity(**data) + return activity + return _make_activity - with open(json_file, 'w') as outfile: - json.dump(activities, outfile) +@pytest.fixture(name='create_fake_dao') +def _create_fake_dao() -> domain.ActivitiesDao: + db_fake = DB('sqlite:///:memory:') + dao = infrastructure.ActivitiesSQLDao(db_fake) + return dao - yield activities, json_file - shutil.rmtree(temporary_directory) +@pytest.fixture(name='create_fake_database') +def _create_fake_database() -> domain.ActivitiesDao: + db_fake = DB('sqlite:///:memory:') + return db_fake \ 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 e3bf4ffe..3917543c 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -1,108 +1,144 @@ -from time_tracker.activities._application import _activities as activities +import pytest +import json from faker import Faker import azure.functions as func -import json - - -ACTIVITY_URL = "/api/activities/" - +import sqlalchemy +from sqlalchemy.sql.sqltypes import Integer + +import time_tracker.activities._application._activities as azure_activities +import time_tracker.activities._domain as domain +import time_tracker.activities._infrastructure as infrastructure +from time_tracker._infrastructure import DB + +ACTIVITY_URL = '/api/activities/' + +DEMO_DATA = [ + { + 'id': 1, + 'name': 'Activity Demo create', + 'description': 'test demo create an new activity', + 'deleted': None, + 'status': None, + }, + { + 'id': 2, + 'name': 'Activity Demo create', + 'description': 'test demo create an new activity', + 'deleted': None, + 'status': None, + }, + ] + + +@pytest.fixture(name='insert_activity') +def _insert_activity() -> domain.Activity: + def _new_activity(activity: dict, database: DB): + dao = infrastructure.ActivitiesSQLDao(database) + new_activity = dao.create(activity) + return new_activity + return _new_activity def test__activity_azure_endpoint__returns_all_activities( - create_temp_activities, + create_fake_database, activity_factory, insert_activity ): - activities_json, tmp_directory = create_temp_activities - activities._get_activities.JSON_PATH = tmp_directory - req = func.HttpRequest(method="GET", body=None, url=ACTIVITY_URL) + fake_database = create_fake_database + existent_activities= [activity_factory(DEMO_DATA[0]), activity_factory(DEMO_DATA[1])] + inserted_activities = [insert_activity(existent_activities[0].__dict__, fake_database).__dict__, insert_activity(existent_activities[1].__dict__, fake_database).__dict__] - response = activities.get_activities(req) + azure_activities._get_activities.DATABASE = fake_database + 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") - + assert response.status_code == 200 - assert activities_json_data == json.dumps(activities_json) + assert activities_json_data == json.dumps(inserted_activities) def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_its_id( - create_temp_activities, + create_fake_database, activity_factory, insert_activity ): - activities_json, tmp_directory = create_temp_activities - activities._get_activities.JSON_PATH = tmp_directory + fake_database = create_fake_database + existent_activity= activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + + azure_activities._get_activities.DATABASE = fake_database req = func.HttpRequest( - method="GET", + method='GET', body=None, url=ACTIVITY_URL, - route_params={"id": activities_json[0]["id"]}, + route_params={"id": inserted_activity["id"]}, ) - response = activities.get_activities(req) + response = azure_activities._get_activities.get_activities(req) activitiy_json_data = response.get_body().decode("utf-8") assert response.status_code == 200 - assert activitiy_json_data == json.dumps(activities_json[0]) + assert activitiy_json_data == json.dumps(inserted_activity) def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found( - create_temp_activities, -): - activities_json, tmp_directory = create_temp_activities - activities._delete_activity.JSON_PATH = tmp_directory + create_fake_database,activity_factory, insert_activity +): + fake_database = create_fake_database + existent_activity= activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + + azure_activities._delete_activity.DATABASE = fake_database req = func.HttpRequest( - method="DELETE", + method='DELETE', body=None, url=ACTIVITY_URL, - route_params={"id": activities_json[0]["id"]}, + route_params={"id": inserted_activity["id"]}, ) - response = activities.delete_activity(req) + response = azure_activities._delete_activity.delete_activity(req) activity_json_data = json.loads(response.get_body().decode("utf-8")) assert response.status_code == 200 - assert activity_json_data["status"] == "inactive" + assert activity_json_data['status'] == 0 + assert activity_json_data['deleted'] == True def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update( - create_temp_activities, + create_fake_database, activity_factory, insert_activity ): - activities_json, tmp_directory = create_temp_activities - activities._update_activity.JSON_PATH = tmp_directory - activity_data = {"description": Faker().sentence()} + fake_database = create_fake_database + existent_activity= activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + + azure_activities._update_activity.DATABASE = fake_database + activity_body = {"description": Faker().sentence()} req = func.HttpRequest( - method="PUT", - body=json.dumps(activity_data).encode("utf-8"), + method='PUT', + body=json.dumps(activity_body).encode("utf-8"), url=ACTIVITY_URL, - route_params={"id": activities_json[0]["id"]}, + route_params={"id": inserted_activity["id"]}, ) - response = activities.update_activity(req) + response = azure_activities._update_activity.update_activity(req) activitiy_json_data = response.get_body().decode("utf-8") - new_activity = {**activities_json[0], **activity_data} + inserted_activity.update(activity_body) assert response.status_code == 200 - assert activitiy_json_data == json.dumps(new_activity) + assert activitiy_json_data == json.dumps(inserted_activity) def test__activity_azure_endpoint__creates_an_activity__when_activity_has_all_attributes( - create_temp_activities, -): - activities_json, tmp_directory = create_temp_activities - activities._create_activity._JSON_PATH = tmp_directory - - activity_body = { - "id": None, - "name": Faker().user_name(), - "description": Faker().sentence(), - "deleted": Faker().uuid4(), - "status": "active", - "tenant_id": Faker().uuid4(), - } - body = json.dumps(activity_body).encode("utf-8") - req = func.HttpRequest( - method="POST", - body=body, - url=ACTIVITY_URL, - ) - - response = activities.create_activity(req) - activitiy_json_data = response.get_body() - assert response.status_code == 201 - assert activitiy_json_data == body + create_fake_database, + ): + azure_activities._create_activity.DATABASE = create_fake_database + activity_body = {'id': None, 'name': Faker().user_name(), 'description': Faker().sentence(),'deleted': False ,'status': 1} + body = json.dumps(activity_body).encode("utf-8") + req = func.HttpRequest( + method='POST', + body= body, + url=ACTIVITY_URL, + ) + + response = azure_activities._create_activity.create_activity(req) + activitiy_json_data = json.loads(response.get_body()) + activity_body['id'] = activitiy_json_data['id'] + + assert response.status_code == 201 + assert activitiy_json_data == activity_body \ No newline at end of file diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 2741ce95..943219a7 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,2 +1 @@ -# flake8: noqa -from tests.api.api_fixtures import create_temp_activities +from tests.api.api_fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file diff --git a/V2/tests/integration/daos/activities_json_dao_test.py b/V2/tests/integration/daos/activities_json_dao_test.py deleted file mode 100644 index 8eff9609..00000000 --- a/V2/tests/integration/daos/activities_json_dao_test.py +++ /dev/null @@ -1,152 +0,0 @@ -from time_tracker.activities._infrastructure import ActivitiesJsonDao -from time_tracker.activities._domain import Activity -from faker import Faker -import json -import pytest -import typing - - -fake_activities = [ - { - "id": Faker().uuid4(), - "name": Faker().user_name(), - "description": Faker().sentence(), - "deleted": Faker().uuid4(), - "status": "active", - "tenant_id": Faker().uuid4(), - } -] - - -@pytest.fixture(name="create_fake_activities") -def _create_fake_activities(mocker) -> typing.List[Activity]: - def _creator(activities): - read_data = json.dumps(activities) - mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) - return [Activity(**activity) for activity in activities] - - return _creator - - -def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - activities = create_fake_activities(fake_activities) - activity_dto = activities.pop() - - result = activities_json_dao.get_by_id(activity_dto.id) - - assert result == activity_dto - - -def test__get_by_id__returns_none__when_no_activity_matches_its_id( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - create_fake_activities([]) - - result = activities_json_dao.get_by_id(Faker().uuid4()) - - assert result is None - - -def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - number_of_activities = 3 - activities = create_fake_activities(fake_activities * number_of_activities) - - result = activities_json_dao.get_all() - - assert result == activities - - -def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - activities = create_fake_activities([]) - - result = activities_json_dao.get_all() - - assert result == activities - - -def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - activities = create_fake_activities( - [ - { - "name": "test_name", - "description": "test_description", - "tenant_id": "test_tenant_id", - "id": "test_id", - "deleted": "test_deleted", - "status": "test_status", - } - ] - ) - - activity_dto = activities.pop() - result = activities_json_dao.delete(activity_dto.id) - - assert result.status == "inactive" - - -def test_delete__returns_none__when_no_activity_matching_its_id_is_found( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - create_fake_activities([]) - - result = activities_json_dao.delete(Faker().uuid4()) - - assert result is None - - -def test_update__returns_an_activity_dto__when_found_one_activity_to_update( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - activities = create_fake_activities(fake_activities) - activity_dto = activities.pop() - activity_data = {"description": Faker().sentence()} - - result = activities_json_dao.update(activity_dto.id, activity_data) - new_activity = {**activity_dto.__dict__, **activity_data} - - assert result == Activity(**new_activity) - - -def test_update__returns_none__when_doesnt_found_one_activity_to_update( - create_fake_activities, -): - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - create_fake_activities([]) - activity_data = {"description": Faker().sentence()} - - result = activities_json_dao.update("", activity_data) - - assert result is None - - -def test_create_activity__returns_an_activity_dto__when_create_an_activity_that_matches_attributes( - create_fake_activities, -): - create_fake_activities([]) - - activities_json_dao = ActivitiesJsonDao(Faker().file_path()) - activity_data = { - "name": "test_name", - "description": "test_description", - "tenant_id": "test_tenant_id", - "id": "test_id", - "deleted": "test_deleted", - "status": "test_status", - } - result = activities_json_dao.create_activity(activity_data) - assert result == Activity(**activity_data) diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py new file mode 100644 index 00000000..3a119390 --- /dev/null +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -0,0 +1,131 @@ +import pytest +import typing + +from sqlalchemy.sql.expression import update + +import time_tracker.activities._domain as domain +import time_tracker.activities._infrastructure as infrastructure +from time_tracker._infrastructure import DB + +DEMO_DATA = [ + { + 'id': 1, + 'name': 'Activity Demo create', + 'description': 'test demo create an new activity', + 'deleted': None, + 'status': None, + }, + { + 'id': 2, + 'name': 'Activity Demo create', + 'description': 'test demo create an new activity', + 'deleted': None, + 'status': None, + }, + ] + +@pytest.fixture(name='insert_activity') +def _insert_activity() -> domain.Activity: + def _new_activity(activity: dict, dao: domain.ActivitiesDao): + new_activity = dao.create(activity) + return new_activity + return _new_activity + +@pytest.fixture(name='clean_database', autouse=True) +def _clean_database(): + yield + db_fake = DB('sqlite:///:memory:') + dao = infrastructure.ActivitiesSQLDao(db_fake) + query = dao.activity.delete() + dao.db.get_session().execute(query) + +def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql_database(create_fake_dao, activity_factory): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + + inserted_activity = dao.create(existent_activity.__dict__) + + assert isinstance(inserted_activity, domain.Activity) + assert inserted_activity == existent_activity + + +def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, dao) + + updated_activity = dao.update(inserted_activity.id, {'description': 'test demo 2 create an new activity'}) + + assert isinstance(updated_activity, domain.Activity) + assert updated_activity.id == inserted_activity.id + assert updated_activity.description == 'test demo 2 create an new activity' + + +def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + + results = dao.update(existent_activity.id,{'description': 'test demo'}) + + assert results == None + + +def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): + dao = create_fake_dao + existent_activities= [activity_factory(DEMO_DATA[0]), activity_factory(DEMO_DATA[1])] + inserted_activities = [insert_activity(existent_activities[0].__dict__, dao), insert_activity(existent_activities[1].__dict__, dao)] + + activities = dao.get_all() + + assert isinstance(activities, typing.List) + assert activities == inserted_activities + + +def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id_with_sql_database(create_fake_dao, activity_factory, insert_activity): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, dao) + + activity = dao.get_by_id(inserted_activity.id) + + assert isinstance(activity, domain.Activity) + assert activity.id == inserted_activity.id + assert activity == inserted_activity + + +def test__get_by_id__returns_none__when_no_activity_matches_its_id_with_sql_database(create_fake_dao, activity_factory): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + + activity = dao.get_by_id(existent_activity.id) + + assert activity == None + + +def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities_with_sql_database(create_fake_dao): + activities = create_fake_dao.get_all() + + assert isinstance(activities, typing.List) + assert activities == [] + + +def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + inserted_activity = insert_activity(existent_activity.__dict__, dao) + + activity = dao.delete(inserted_activity.id) + + assert isinstance(activity, domain.Activity) + assert activity.id == inserted_activity.id + assert activity.status == 0 + assert activity.deleted == True + + +def test_delete__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): + dao = create_fake_dao + existent_activity = activity_factory(DEMO_DATA[0]) + + results = dao.delete(existent_activity.id) + + assert results == None \ No newline at end of file diff --git a/V2/tests/unit/services/activity_service_test.py b/V2/tests/unit/services/activity_service_test.py index befdb1fb..ca035f2e 100644 --- a/V2/tests/unit/services/activity_service_test.py +++ b/V2/tests/unit/services/activity_service_test.py @@ -59,15 +59,14 @@ def test__update_activity__uses_the_activity_dao__to_update_one_activity( assert activity_dao.update.called assert expected_activity == updated_activity - def test__create_activity__uses_the_activity_dao__to_create_an_activity(mocker): expected_activity = mocker.Mock() activity_dao = mocker.Mock( - create_activity=mocker.Mock(return_value=expected_activity) + create=mocker.Mock(return_value=expected_activity) ) activity_service = ActivityService(activity_dao) - actual_activity = activity_service.create_activity(Faker().pydict()) + actual_activity = activity_service.create(Faker().pydict()) - assert activity_dao.create_activity.called + assert activity_dao.create.called assert expected_activity == actual_activity diff --git a/V2/tests/unit/use_cases/activities_use_case_test.py b/V2/tests/unit/use_cases/activities_use_case_test.py index 334c7489..928fa980 100644 --- a/V2/tests/unit/use_cases/activities_use_case_test.py +++ b/V2/tests/unit/use_cases/activities_use_case_test.py @@ -1,3 +1,4 @@ +from time_tracker.activities._domain import ActivityService from time_tracker.activities._domain import _use_cases from pytest_mock import MockFixture from faker import Faker @@ -36,25 +37,26 @@ def test__get_activity_by_id_function__uses_the_activity_service__to_retrieve_ac def test__create_activity_function__uses_the_activities_service__to_create_activity( - mocker: MockFixture, -): - expected_activity = mocker.Mock() - activity_service = mocker.Mock( - create_activity=mocker.Mock(return_value=expected_activity) - ) - - activity_use_case = _use_cases.CreateActivityUseCase(activity_service) - actual_activity = activity_use_case.create_activity(fake.pydict()) + mocker: MockFixture, + ): + expected_activity = mocker.Mock() + activity_service = mocker.Mock( + create=mocker.Mock(return_value=expected_activity) + ) - assert activity_service.create_activity.called - assert expected_activity == actual_activity + activity_use_case = _use_cases.CreateActivityUseCase(activity_service) + actual_activity = activity_use_case.create_activity(fake.pydict()) + assert activity_service.create.called + assert expected_activity == actual_activity def test__delete_activity_function__uses_the_activity_service__to_change_activity_status( mocker: MockFixture, ): expected_activity = mocker.Mock() - activity_service = mocker.Mock(delete=mocker.Mock(return_value=expected_activity)) + activity_service = mocker.Mock( + delete=mocker.Mock(return_value=expected_activity) + ) activity_use_case = _use_cases.DeleteActivityUseCase(activity_service) deleted_activity = activity_use_case.delete_activity(fake.uuid4()) @@ -62,15 +64,18 @@ def test__delete_activity_function__uses_the_activity_service__to_change_activit assert activity_service.delete.called assert expected_activity == deleted_activity - def test__update_activity_function__uses_the_activities_service__to_update_an_activity( mocker: MockFixture, ): expected_activity = mocker.Mock() - activity_service = mocker.Mock(update=mocker.Mock(return_value=expected_activity)) + activity_service = mocker.Mock( + update=mocker.Mock(return_value=expected_activity) + ) activity_use_case = _use_cases.UpdateActivityUseCase(activity_service) - updated_activity = activity_use_case.update_activity(fake.uuid4(), fake.pydict()) + updated_activity = activity_use_case.update_activity( + fake.uuid4(), fake.pydict() + ) assert activity_service.update.called assert expected_activity == updated_activity diff --git a/V2/time_tracker/_infrastructure/__init__.py b/V2/time_tracker/_infrastructure/__init__.py new file mode 100644 index 00000000..30cbea81 --- /dev/null +++ b/V2/time_tracker/_infrastructure/__init__.py @@ -0,0 +1,2 @@ +from ._db import DB +from ._config import Config \ No newline at end of file diff --git a/V2/time_tracker/_infrastructure/_config.py b/V2/time_tracker/_infrastructure/_config.py new file mode 100644 index 00000000..63fedaef --- /dev/null +++ b/V2/time_tracker/_infrastructure/_config.py @@ -0,0 +1,12 @@ +import typing +import os + +class Config(typing.NamedTuple): + DB_CONNECTION_STRING: str + DB_USER: str + DB_PASS: str + DB_NAME: str + + +def load_config(): + return Config(os.environ.get("DB_CONNECTION_STRING"),os.environ.get("DB_USER"),os.environ.get("DB_PASS"),os.environ.get("DB_NAME")) \ No newline at end of file diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py new file mode 100644 index 00000000..fc60041e --- /dev/null +++ b/V2/time_tracker/_infrastructure/_db.py @@ -0,0 +1,22 @@ +import uuid + +import sqlalchemy + +from . import _config + +DIALECT = 'postgresql' +class DB(): + config = _config.load_config() + connection = None + engine = None + conn_string = config.DB_CONNECTION_STRING + metadata = sqlalchemy.MetaData() + + def __init__(self, conn_string: str = conn_string): + self.engine = sqlalchemy.create_engine(conn_string) + + def get_session(self): + if self.connection == None: + self.metadata.create_all(self.engine) + self.connection = self.engine.connect() + return self.connection \ No newline at end of file diff --git a/V2/time_tracker/activities/_application/__init__.py b/V2/time_tracker/activities/_application/__init__.py index 6c34669a..c8f26492 100644 --- a/V2/time_tracker/activities/_application/__init__.py +++ b/V2/time_tracker/activities/_application/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa from ._activities import get_activities from ._activities import delete_activity from ._activities import update_activity diff --git a/V2/time_tracker/activities/_application/_activities/__init__.py b/V2/time_tracker/activities/_application/_activities/__init__.py index ef38ae00..ab7d3844 100644 --- a/V2/time_tracker/activities/_application/_activities/__init__.py +++ b/V2/time_tracker/activities/_application/_activities/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa from ._get_activities import get_activities from ._delete_activity import delete_activity from ._update_activity import update_activity diff --git a/V2/time_tracker/activities/_application/_activities/_create_activity.py b/V2/time_tracker/activities/_application/_activities/_create_activity.py index be53815a..3799fb3e 100644 --- a/V2/time_tracker/activities/_application/_activities/_create_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_create_activity.py @@ -6,34 +6,36 @@ from ... import _domain from ... import _infrastructure +from time_tracker._infrastructure import DB -_JSON_PATH = ( - 'activities/_infrastructure/_data_persistence/activities_data.json' -) +DATABASE = DB() def create_activity(req: func.HttpRequest) -> func.HttpResponse: - activity_dao = _infrastructure.ActivitiesJsonDao(_JSON_PATH) + activity_dao = _infrastructure.ActivitiesSQLDao(DATABASE) activity_service = _domain.ActivityService(activity_dao) use_case = _domain._use_cases.CreateActivityUseCase(activity_service) + activity_data = req.get_json() + validation_errors = _validate_activity(activity_data) if validation_errors: return func.HttpResponse( body=json.dumps(validation_errors), status_code=400, mimetype="application/json" ) + activity_to_create = _domain.Activity( - id=None, + id= None, name=activity_data['name'], description=activity_data['description'], status=activity_data['status'], - deleted=activity_data['deleted'], - tenant_id=activity_data['tenant_id'] + deleted=activity_data['deleted'] ) + created_activity = use_case.create_activity(activity_to_create.__dict__) if not create_activity: return func.HttpResponse( diff --git a/V2/time_tracker/activities/_application/_activities/_delete_activity.py b/V2/time_tracker/activities/_application/_activities/_delete_activity.py index 80d55446..65121934 100644 --- a/V2/time_tracker/activities/_application/_activities/_delete_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_delete_activity.py @@ -1,13 +1,13 @@ -from time_tracker.activities._infrastructure import ActivitiesJsonDao -from time_tracker.activities._domain import ActivityService, _use_cases - -import azure.functions as func import json import logging -JSON_PATH = ( - 'activities/_infrastructure/_data_persistence/activities_data.json' -) +import azure.functions as func + +from ... import _domain +from ... import _infrastructure +from time_tracker._infrastructure import DB + +DATABASE = DB() def delete_activity(req: func.HttpRequest) -> func.HttpResponse: @@ -24,13 +24,13 @@ def delete_activity(req: func.HttpRequest) -> func.HttpResponse: def _delete(activity_id: str) -> str: - activity_use_case = _use_cases.DeleteActivityUseCase( - _create_activity_service(JSON_PATH) + activity_use_case = _domain._use_cases.DeleteActivityUseCase( + _create_activity_service(DATABASE) ) activity = activity_use_case.delete_activity(activity_id) return json.dumps(activity.__dict__) if activity else b'Not found' -def _create_activity_service(path: str): - activity_json = ActivitiesJsonDao(path) - return ActivityService(activity_json) +def _create_activity_service(db: DB) -> _domain.ActivityService: + activity_sql = _infrastructure.ActivitiesSQLDao(db) + return _domain.ActivityService(activity_sql) diff --git a/V2/time_tracker/activities/_application/_activities/_get_activities.py b/V2/time_tracker/activities/_application/_activities/_get_activities.py index 9f52069d..ce71d8b9 100644 --- a/V2/time_tracker/activities/_application/_activities/_get_activities.py +++ b/V2/time_tracker/activities/_application/_activities/_get_activities.py @@ -1,13 +1,13 @@ -from time_tracker.activities._infrastructure import ActivitiesJsonDao -from time_tracker.activities._domain import ActivityService, _use_cases - -import azure.functions as func import json import logging -JSON_PATH = ( - 'activities/_infrastructure/_data_persistence/activities_data.json' -) +import azure.functions as func + +from ... import _domain +from ... import _infrastructure +from time_tracker._infrastructure import DB + +DATABASE = DB() def get_activities(req: func.HttpRequest) -> func.HttpResponse: @@ -30,8 +30,8 @@ def get_activities(req: func.HttpRequest) -> func.HttpResponse: def _get_by_id(activity_id: str) -> str: - activity_use_case = _use_cases.GetActivityUseCase( - _create_activity_service(JSON_PATH) + activity_use_case = _domain._use_cases.GetActivityUseCase( + _create_activity_service(DATABASE) ) activity = activity_use_case.get_activity_by_id(activity_id) @@ -39,8 +39,8 @@ def _get_by_id(activity_id: str) -> str: def _get_all() -> str: - activities_use_case = _use_cases.GetActivitiesUseCase( - _create_activity_service(JSON_PATH) + activities_use_case = _domain._use_cases.GetActivitiesUseCase( + _create_activity_service(DATABASE) ) return json.dumps( [ @@ -50,6 +50,6 @@ def _get_all() -> str: ) -def _create_activity_service(path: str): - activity_json = ActivitiesJsonDao(path) - return ActivityService(activity_json) +def _create_activity_service(db: DB) -> _domain.ActivityService: + activity_sql = _infrastructure.ActivitiesSQLDao(db) + return _domain.ActivityService(activity_sql) diff --git a/V2/time_tracker/activities/_application/_activities/_update_activity.py b/V2/time_tracker/activities/_application/_activities/_update_activity.py index 1709f77a..5d7cf90a 100644 --- a/V2/time_tracker/activities/_application/_activities/_update_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_update_activity.py @@ -1,14 +1,14 @@ -from time_tracker.activities._infrastructure import ActivitiesJsonDao -from time_tracker.activities._domain import ActivityService, Activity, _use_cases - -import azure.functions as func import dataclasses import json import logging -JSON_PATH = ( - 'activities/_infrastructure/_data_persistence/activities_data.json' -) +import azure.functions as func + +from ... import _domain +from ... import _infrastructure +from time_tracker._infrastructure import DB + +DATABASE = DB() def update_activity(req: func.HttpRequest) -> func.HttpResponse: @@ -17,7 +17,7 @@ def update_activity(req: func.HttpRequest) -> func.HttpResponse: ) activity_id = req.route_params.get('id') activity_data = req.get_json() if req.get_body() else {} - activity_keys = [field.name for field in dataclasses.fields(Activity)] + activity_keys = [field.name for field in dataclasses.fields(_domain.Activity)] if all(key in activity_keys for key in activity_data.keys()): response = _update(activity_id, activity_data) @@ -32,13 +32,13 @@ def update_activity(req: func.HttpRequest) -> func.HttpResponse: def _update(activity_id: str, activity_data: dict) -> str: - activity_use_case = _use_cases.UpdateActivityUseCase( - _create_activity_service(JSON_PATH) + activity_use_case = _domain._use_cases.UpdateActivityUseCase( + _create_activity_service(DATABASE) ) activity = activity_use_case.update_activity(activity_id, activity_data) return json.dumps(activity.__dict__) if activity else b'Not Found' -def _create_activity_service(path: str): - activity_json = ActivitiesJsonDao(path) - return ActivityService(activity_json) +def _create_activity_service(db: DB) -> _domain.ActivityService: + activity_sql = _infrastructure.ActivitiesSQLDao(db) + return _domain.ActivityService(activity_sql) diff --git a/V2/time_tracker/activities/_domain/__init__.py b/V2/time_tracker/activities/_domain/__init__.py index 66eb2eec..f1a97246 100644 --- a/V2/time_tracker/activities/_domain/__init__.py +++ b/V2/time_tracker/activities/_domain/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa from ._entities import Activity from ._persistence_contracts import ActivitiesDao from ._services import ActivityService diff --git a/V2/time_tracker/activities/_domain/_entities/__init__.py b/V2/time_tracker/activities/_domain/_entities/__init__.py index a8cf9289..bf3eb08e 100644 --- a/V2/time_tracker/activities/_domain/_entities/__init__.py +++ b/V2/time_tracker/activities/_domain/_entities/__init__.py @@ -1,2 +1 @@ -# flake8: noqa from ._activity import Activity diff --git a/V2/time_tracker/activities/_domain/_entities/_activity.py b/V2/time_tracker/activities/_domain/_entities/_activity.py index 86f56ee9..cf574054 100644 --- a/V2/time_tracker/activities/_domain/_entities/_activity.py +++ b/V2/time_tracker/activities/_domain/_entities/_activity.py @@ -3,9 +3,8 @@ @dataclass(frozen=True) class Activity: - id: str + id: int name: str description: str - deleted: str - status: str - tenant_id: str + deleted: bool + status: int diff --git a/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py index 2401254d..3495445e 100644 --- a/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py +++ b/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py @@ -1,2 +1 @@ -# flake8: noqa from ._activities_dao import ActivitiesDao diff --git a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py index 80b8c711..7ee1d53d 100644 --- a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py +++ b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py @@ -21,5 +21,5 @@ def update(self, id: str, new_activity: dict) -> Activity: pass @abc.abstractmethod - def create_activity(self, activity_data: dict) -> Activity: - pass + def create(self, activity_data: dict) -> Activity: + pass \ No newline at end of file diff --git a/V2/time_tracker/activities/_domain/_services/__init__.py b/V2/time_tracker/activities/_domain/_services/__init__.py index fb9a65cc..1a9befa8 100644 --- a/V2/time_tracker/activities/_domain/_services/__init__.py +++ b/V2/time_tracker/activities/_domain/_services/__init__.py @@ -1,2 +1 @@ -# flake8: noqa from ._activity import ActivityService diff --git a/V2/time_tracker/activities/_domain/_services/_activity.py b/V2/time_tracker/activities/_domain/_services/_activity.py index a564577a..43af499b 100644 --- a/V2/time_tracker/activities/_domain/_services/_activity.py +++ b/V2/time_tracker/activities/_domain/_services/_activity.py @@ -18,5 +18,5 @@ def delete(self, activity_id: str) -> Activity: def update(self, activity_id: str, new_activity: dict) -> Activity: return self.activities_dao.update(activity_id, new_activity) - def create_activity(self, activity_data: dict) -> Activity: - return self.activities_dao.create_activity(activity_data) + def create(self, activity_data: dict) -> Activity: + return self.activities_dao.create(activity_data) diff --git a/V2/time_tracker/activities/_domain/_use_cases/__init__.py b/V2/time_tracker/activities/_domain/_use_cases/__init__.py index 6b330e07..642d2425 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/activities/_domain/_use_cases/__init__.py @@ -1,4 +1,3 @@ -# flake8: noqa from ._get_activities_use_case import GetActivitiesUseCase from ._get_activity_by_id_use_case import GetActivityUseCase from ._delete_activity_use_case import DeleteActivityUseCase diff --git a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py index 241718db..afd39627 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py @@ -1,9 +1,11 @@ from time_tracker.activities._domain import ActivityService, Activity +import typing class CreateActivityUseCase: def __init__(self, activity_service: ActivityService): self.activity_service = activity_service - def create_activity(self, activity_data: dict) -> Activity: - return self.activity_service.create_activity(activity_data) + def create_activity(self, activity_data: dict ) -> Activity: + return self.activity_service.create(activity_data) + diff --git a/V2/time_tracker/activities/_infrastructure/__init__.py b/V2/time_tracker/activities/_infrastructure/__init__.py index 1734e5b8..add89b22 100644 --- a/V2/time_tracker/activities/_infrastructure/__init__.py +++ b/V2/time_tracker/activities/_infrastructure/__init__.py @@ -1,2 +1 @@ -# flake8: noqa -from ._data_persistence import ActivitiesJsonDao +from ._data_persistence import ActivitiesSQLDao \ No newline at end of file diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py index d2a77fc4..07db700f 100644 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py @@ -1,2 +1 @@ -# flake8: noqa -from ._activities_json_dao import ActivitiesJsonDao +from ._activities_sql_dao import ActivitiesSQLDao diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_json_dao.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_json_dao.py deleted file mode 100644 index 60859a15..00000000 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_json_dao.py +++ /dev/null @@ -1,105 +0,0 @@ -from time_tracker.activities._domain import ActivitiesDao, Activity -import dataclasses -import json -import typing - - -class ActivitiesJsonDao(ActivitiesDao): - def __init__(self, json_data_file_path: str): - self.json_data_file_path = json_data_file_path - self.activity_keys = [ - field.name for field in dataclasses.fields(Activity) - ] - - def get_by_id(self, activity_id: str) -> Activity: - activity = { - activity.get('id'): activity - for activity in self.__get_activities_from_file() - }.get(activity_id) - - return self.__create_activity_dto(activity) if activity else None - - def get_all(self) -> typing.List[Activity]: - return [ - self.__create_activity_dto(activity) - for activity in self.__get_activities_from_file() - ] - - def delete(self, activity_id: str) -> Activity: - activity = self.get_by_id(activity_id) - if activity: - activity_deleted = {**activity.__dict__, 'status': 'inactive'} - activities_updated = list( - map( - lambda activity: activity - if activity.get('id') != activity_id - else activity_deleted, - self.__get_activities_from_file(), - ) - ) - - try: - file = open(self.json_data_file_path, 'w') - json.dump(activities_updated, file) - file.close() - - return self.__create_activity_dto(activity_deleted) - - except FileNotFoundError: - return None - - else: - return None - - def update(self, activity_id: str, new_activity: dict) -> Activity: - activity = self.get_by_id(activity_id) - if not activity: - return None - - new_activity = {**activity.__dict__, **new_activity} - - activities_updated = list( - map( - lambda activity: activity - if activity.get('id') != activity_id - else new_activity, - self.__get_activities_from_file(), - ) - ) - - try: - file = open(self.json_data_file_path, 'w') - json.dump(activities_updated, file) - file.close() - - return self.__create_activity_dto(new_activity) - - except FileNotFoundError: - return None - - def create_activity(self, activity_data: dict) -> Activity: - activities = self.__get_activities_from_file() - activities.append(activity_data) - - try: - with open(self.json_data_file_path, 'w') as outfile: - json.dump(activities, outfile) - - return self.__create_activity_dto(activity_data) - except FileNotFoundError: - print("Can not create activity") - - def __get_activities_from_file(self) -> typing.List[dict]: - try: - file = open(self.json_data_file_path) - activities = json.load(file) - file.close() - - return activities - - except FileNotFoundError: - return [] - - def __create_activity_dto(self, activity: dict) -> Activity: - activity = {key: activity.get(key) for key in self.activity_keys} - return Activity(**activity) diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py new file mode 100644 index 00000000..3035e029 --- /dev/null +++ b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py @@ -0,0 +1,60 @@ +import dataclasses +import typing + +import sqlalchemy +import sqlalchemy.sql as sql + +import time_tracker.activities._domain as domain +from time_tracker._infrastructure import _db + +class ActivitiesSQLDao(domain.ActivitiesDao): + + def __init__(self, database: _db.DB): + self.activity_keys = [ + field.name for field in dataclasses.fields(domain.Activity) + ] + self.db = database + self.activity = sqlalchemy.Table('activity', self.db.metadata, + sqlalchemy.Column('id',sqlalchemy.Integer , primary_key=True,autoincrement=True), + sqlalchemy.Column('name', sqlalchemy.String), + sqlalchemy.Column('description', sqlalchemy.String), + sqlalchemy.Column('deleted', sqlalchemy.Boolean), + sqlalchemy.Column('status', sqlalchemy.SmallInteger), + extend_existing=True, + ) + + def get_by_id(self, activity_id: str) -> domain.Activity: + query = sql.select(self.activity).where(self.activity.c.id == activity_id) + activity = self.db.get_session().execute(query).one_or_none() + return self.__create_activity_dto(dict(activity)) if activity else None + + def get_all(self) -> typing.List[domain.Activity]: + query = sql.select(self.activity) + result = self.db.get_session().execute(query) + return [ + self.__create_activity_dto(dict(activity)) + for activity in result + ] + + def create(self, activity_data: dict) -> domain.Activity: + activity_data.pop('id', None) + activity_data.update({"status":1, "deleted":False}) + query = self.activity.insert().values(activity_data).return_defaults() + activity = self.db.get_session().execute(query) + activity_data.update({"id":activity.inserted_primary_key[0]}) + return self.__create_activity_dto(activity_data) + + def delete(self, activity_id: str) -> domain.Activity: + query = self.activity.update().where(self.activity.c.id == activity_id).values({"status":0, "deleted":True}) + self.db.get_session().execute(query) + return self.get_by_id(activity_id); + + def update(self, activity_id: str, new_activity: dict) -> domain.Activity: + new_activity.pop('id', None) + query = self.activity.update().where(self.activity.c.id==activity_id).values(new_activity) + self.db.get_session().execute(query) + return self.get_by_id(activity_id) + + def __create_activity_dto(self, activity: dict) -> domain.Activity: + activity = {key: activity.get(key)for key in self.activity_keys} + return domain.Activity(**activity) \ No newline at end of file diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/activities_data.json b/V2/time_tracker/activities/_infrastructure/_data_persistence/activities_data.json deleted file mode 100644 index 961251db..00000000 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/activities_data.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "name": "Development", - "description": "Development", - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "id": "c61a4a49-3364-49a3-a7f7-0c5f2d15072b", - "_rid": "QUwFAPuumiRhAAAAAAAAAA==", - "_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRhAAAAAAAAAA==/", - "_etag": "\"4e006cc9-0000-0500-0000-607dcc0d0000\"", - "_attachments": "attachments/", - "_last_event_ctx": { - "user_id": "dd76e5d6-3949-46fd-b418-f15bf7c354fa", - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "action": "delete", - "description": null, - "container_id": "activity", - "session_id": null - }, - "deleted": "b4327ba6-9f96-49ee-a9ac-3c1edf525172", - "status": null, - "_ts": 1618856973 - }, - { - "name": "Management", - "description": null, - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "id": "94ec92e2-a500-4700-a9f6-e41eb7b5507c", - "_last_event_ctx": { - "user_id": "dd76e5d6-3949-46fd-b418-f15bf7c354fa", - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "action": "delete", - "description": null, - "container_id": "activity", - "session_id": null - }, - "_rid": "QUwFAPuumiRfAAAAAAAAAA==", - "_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRfAAAAAAAAAA==/", - "_etag": "\"4e0069c9-0000-0500-0000-607dcc0d0000\"", - "_attachments": "attachments/", - "deleted": "7cf6efe5-a221-4fe4-b94f-8945127a489a", - "status": null, - "_ts": 1618856973 - }, - { - "name": "Operations", - "description": "Operation activities performed.", - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "id": "d45c770a-b1a0-4bd8-a713-22c01a23e41b", - "_rid": "QUwFAPuumiRjAAAAAAAAAA==", - "_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRjAAAAAAAAAA==/", - "_etag": "\"09009a4d-0000-0500-0000-614b66fb0000\"", - "_attachments": "attachments/", - "_last_event_ctx": { - "user_id": "82ed0f65-051c-4898-890f-870805900e21", - "tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05", - "action": "update", - "description": null, - "container_id": "activity", - "session_id": null - }, - "deleted": "7cf6efe5-a221-4fe4-b94f-8945127a489a", - "status": "active", - "_ts": 1632331515 - } -] diff --git a/V2/time_tracker/activities/interface.py b/V2/time_tracker/activities/interface.py index 877b631e..1f1fc805 100644 --- a/V2/time_tracker/activities/interface.py +++ b/V2/time_tracker/activities/interface.py @@ -1,4 +1,3 @@ -# flake8: noqa from ._application import get_activities from ._application import delete_activity from ._application import update_activity From f17ea3cb48fe4a8af2c5530737762fa3623513fc Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 11 Nov 2021 16:10:14 -0500 Subject: [PATCH 2/6] fix: TT-357 Resolution of comments --- .../azure/activity_azure_endpoints_test.py | 41 +++++-------------- V2/tests/{api => }/api_fixtures.py | 11 ++++- V2/tests/conftest.py | 2 +- .../daos/activities_sql_dao_test.py | 34 ++++----------- .../use_cases/activities_use_case_test.py | 1 - V2/time_tracker/_infrastructure/_db.py | 3 -- .../_activities/_create_activity.py | 4 -- 7 files changed, 28 insertions(+), 68 deletions(-) rename V2/tests/{api => }/api_fixtures.py (65%) diff --git a/V2/tests/api/azure/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 3917543c..4334e431 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -3,48 +3,27 @@ from faker import Faker import azure.functions as func -import sqlalchemy -from sqlalchemy.sql.sqltypes import Integer import time_tracker.activities._application._activities as azure_activities -import time_tracker.activities._domain as domain import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB ACTIVITY_URL = '/api/activities/' -DEMO_DATA = [ - { - 'id': 1, - 'name': 'Activity Demo create', - 'description': 'test demo create an new activity', - 'deleted': None, - 'status': None, - }, - { - 'id': 2, - 'name': 'Activity Demo create', - 'description': 'test demo create an new activity', - 'deleted': None, - 'status': None, - }, - ] - - @pytest.fixture(name='insert_activity') -def _insert_activity() -> domain.Activity: +def _insert_activity() -> dict: def _new_activity(activity: dict, database: DB): dao = infrastructure.ActivitiesSQLDao(database) new_activity = dao.create(activity) - return new_activity + return new_activity.__dict__ return _new_activity def test__activity_azure_endpoint__returns_all_activities( create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activities= [activity_factory(DEMO_DATA[0]), activity_factory(DEMO_DATA[1])] - inserted_activities = [insert_activity(existent_activities[0].__dict__, fake_database).__dict__, insert_activity(existent_activities[1].__dict__, fake_database).__dict__] + existent_activities= [activity_factory(), activity_factory()] + inserted_activities = [insert_activity(existent_activities[0].__dict__, fake_database), insert_activity(existent_activities[1].__dict__, fake_database)] azure_activities._get_activities.DATABASE = fake_database req = func.HttpRequest(method='GET', body=None, url=ACTIVITY_URL) @@ -59,8 +38,8 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activity= activity_factory(DEMO_DATA[0]) - inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + existent_activity= activity_factory() + inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._get_activities.DATABASE = fake_database req = func.HttpRequest( @@ -81,8 +60,8 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe create_fake_database,activity_factory, insert_activity ): fake_database = create_fake_database - existent_activity= activity_factory(DEMO_DATA[0]) - inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + existent_activity= activity_factory() + inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._delete_activity.DATABASE = fake_database req = func.HttpRequest( @@ -104,8 +83,8 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activity= activity_factory(DEMO_DATA[0]) - inserted_activity = insert_activity(existent_activity.__dict__, fake_database).__dict__ + existent_activity= activity_factory() + inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._update_activity.DATABASE = fake_database activity_body = {"description": Faker().sentence()} diff --git a/V2/tests/api/api_fixtures.py b/V2/tests/api_fixtures.py similarity index 65% rename from V2/tests/api/api_fixtures.py rename to V2/tests/api_fixtures.py index 301c9d1f..39598db7 100644 --- a/V2/tests/api/api_fixtures.py +++ b/V2/tests/api_fixtures.py @@ -3,11 +3,18 @@ import time_tracker.activities._domain as domain import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB +from faker import Faker @pytest.fixture(name='activity_factory') def _activity_factory() -> domain.Activity: - def _make_activity(data: dict): - activity = domain.Activity(**data) + def _make_activity(name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1): + activity = domain.Activity( + id = None, + name = name, + description = description, + deleted = deleted, + status = status + ) return activity return _make_activity diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 943219a7..c6620169 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1 +1 @@ -from tests.api.api_fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file +from api_fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py index 3a119390..655dc422 100644 --- a/V2/tests/integration/daos/activities_sql_dao_test.py +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -1,28 +1,10 @@ import pytest import typing -from sqlalchemy.sql.expression import update - import time_tracker.activities._domain as domain import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB -DEMO_DATA = [ - { - 'id': 1, - 'name': 'Activity Demo create', - 'description': 'test demo create an new activity', - 'deleted': None, - 'status': None, - }, - { - 'id': 2, - 'name': 'Activity Demo create', - 'description': 'test demo create an new activity', - 'deleted': None, - 'status': None, - }, - ] @pytest.fixture(name='insert_activity') def _insert_activity() -> domain.Activity: @@ -41,7 +23,7 @@ def _clean_database(): def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql_database(create_fake_dao, activity_factory): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() inserted_activity = dao.create(existent_activity.__dict__) @@ -51,7 +33,7 @@ def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) updated_activity = dao.update(inserted_activity.id, {'description': 'test demo 2 create an new activity'}) @@ -63,7 +45,7 @@ def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() results = dao.update(existent_activity.id,{'description': 'test demo'}) @@ -72,7 +54,7 @@ def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sq def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): dao = create_fake_dao - existent_activities= [activity_factory(DEMO_DATA[0]), activity_factory(DEMO_DATA[1])] + existent_activities= [activity_factory(), activity_factory()] inserted_activities = [insert_activity(existent_activities[0].__dict__, dao), insert_activity(existent_activities[1].__dict__, dao)] activities = dao.get_all() @@ -83,7 +65,7 @@ def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_acti def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id_with_sql_database(create_fake_dao, activity_factory, insert_activity): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) activity = dao.get_by_id(inserted_activity.id) @@ -95,7 +77,7 @@ def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matche def test__get_by_id__returns_none__when_no_activity_matches_its_id_with_sql_database(create_fake_dao, activity_factory): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() activity = dao.get_by_id(existent_activity.id) @@ -111,7 +93,7 @@ def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities_with_s def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) activity = dao.delete(inserted_activity.id) @@ -124,7 +106,7 @@ def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matc def test_delete__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): dao = create_fake_dao - existent_activity = activity_factory(DEMO_DATA[0]) + existent_activity = activity_factory() results = dao.delete(existent_activity.id) diff --git a/V2/tests/unit/use_cases/activities_use_case_test.py b/V2/tests/unit/use_cases/activities_use_case_test.py index 928fa980..55a77890 100644 --- a/V2/tests/unit/use_cases/activities_use_case_test.py +++ b/V2/tests/unit/use_cases/activities_use_case_test.py @@ -1,4 +1,3 @@ -from time_tracker.activities._domain import ActivityService from time_tracker.activities._domain import _use_cases from pytest_mock import MockFixture from faker import Faker diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index fc60041e..49eb6e4c 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -1,10 +1,7 @@ -import uuid - import sqlalchemy from . import _config -DIALECT = 'postgresql' class DB(): config = _config.load_config() connection = None diff --git a/V2/time_tracker/activities/_application/_activities/_create_activity.py b/V2/time_tracker/activities/_application/_activities/_create_activity.py index 3799fb3e..83cd69a9 100644 --- a/V2/time_tracker/activities/_application/_activities/_create_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_create_activity.py @@ -16,17 +16,14 @@ def create_activity(req: func.HttpRequest) -> func.HttpResponse: activity_service = _domain.ActivityService(activity_dao) use_case = _domain._use_cases.CreateActivityUseCase(activity_service) - activity_data = req.get_json() - validation_errors = _validate_activity(activity_data) if validation_errors: return func.HttpResponse( body=json.dumps(validation_errors), status_code=400, mimetype="application/json" ) - activity_to_create = _domain.Activity( id= None, name=activity_data['name'], @@ -35,7 +32,6 @@ def create_activity(req: func.HttpRequest) -> func.HttpResponse: deleted=activity_data['deleted'] ) - created_activity = use_case.create_activity(activity_to_create.__dict__) if not create_activity: return func.HttpResponse( From 3b0ed1a788e57fe40aedd13ed455f60434d341d2 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 11 Nov 2021 16:24:29 -0500 Subject: [PATCH 3/6] fix: TT-357 Update requirements --- V2/Makefile | 2 +- V2/requirements.txt | 1 + V2/tests/api_fixtures.py | 21 ++++++++++++--------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/V2/Makefile b/V2/Makefile index 62570676..45080238 100644 --- a/V2/Makefile +++ b/V2/Makefile @@ -5,4 +5,4 @@ install: pip install -r requirements.txt @echo "Completed! " start-local: - docker compose up \ No newline at end of file + docker compose up \ No newline at end of file diff --git a/V2/requirements.txt b/V2/requirements.txt index 4036bb92..8be0a2a8 100644 --- a/V2/requirements.txt +++ b/V2/requirements.txt @@ -4,6 +4,7 @@ azure-functions-worker # Tests pytest +flake8==4.0.1 # Mocking pytest-mock diff --git a/V2/tests/api_fixtures.py b/V2/tests/api_fixtures.py index 39598db7..a51ab053 100644 --- a/V2/tests/api_fixtures.py +++ b/V2/tests/api_fixtures.py @@ -1,22 +1,24 @@ import pytest -import time_tracker.activities._domain as domain -import time_tracker.activities._infrastructure as infrastructure +import time_tracker.activities._domain as domain +import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB from faker import Faker + @pytest.fixture(name='activity_factory') def _activity_factory() -> domain.Activity: def _make_activity(name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1): activity = domain.Activity( - id = None, - name = name, - description = description, - deleted = deleted, - status = status + id=None, + name=name, + description=description, + deleted=deleted, + status=status ) return activity - return _make_activity + return _make_activity + @pytest.fixture(name='create_fake_dao') def _create_fake_dao() -> domain.ActivitiesDao: @@ -24,7 +26,8 @@ def _create_fake_dao() -> domain.ActivitiesDao: dao = infrastructure.ActivitiesSQLDao(db_fake) return dao + @pytest.fixture(name='create_fake_database') def _create_fake_database() -> domain.ActivitiesDao: db_fake = DB('sqlite:///:memory:') - return db_fake \ No newline at end of file + return db_fake From badbce4289eeb0f25cad21e762d3c98cfef20281 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 11 Nov 2021 16:47:19 -0500 Subject: [PATCH 4/6] Refactor: TT-357 correction of FlakeV8 --- .../azure/activity_azure_endpoints_test.py | 57 ++++++++------ V2/tests/api_fixtures.py | 4 +- V2/tests/conftest.py | 1 + .../daos/activities_sql_dao_test.py | 75 ++++++++++++------- .../unit/services/activity_service_test.py | 1 + .../use_cases/activities_use_case_test.py | 16 ++-- V2/time_tracker/_infrastructure/__init__.py | 3 +- V2/time_tracker/_infrastructure/_config.py | 8 +- V2/time_tracker/_infrastructure/_db.py | 9 ++- .../activities/_application/__init__.py | 1 + .../_application/_activities/__init__.py | 1 + .../_activities/_create_activity.py | 2 +- .../activities/_domain/__init__.py | 1 + .../activities/_domain/_entities/__init__.py | 1 + .../_persistence_contracts/__init__.py | 1 + .../_persistence_contracts/_activities_dao.py | 2 +- .../activities/_domain/_services/__init__.py | 1 + .../activities/_domain/_use_cases/__init__.py | 1 + .../_use_cases/_create_activity_use_case.py | 4 +- .../activities/_infrastructure/__init__.py | 3 +- .../_data_persistence/__init__.py | 1 + .../_data_persistence/_activities_sql_dao.py | 37 ++++----- V2/time_tracker/activities/interface.py | 3 +- 23 files changed, 146 insertions(+), 87 deletions(-) diff --git a/V2/tests/api/azure/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 4334e431..258a6e2a 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -5,11 +5,12 @@ import azure.functions as func import time_tracker.activities._application._activities as azure_activities -import time_tracker.activities._infrastructure as infrastructure +import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB ACTIVITY_URL = '/api/activities/' + @pytest.fixture(name='insert_activity') def _insert_activity() -> dict: def _new_activity(activity: dict, database: DB): @@ -18,18 +19,22 @@ def _new_activity(activity: dict, database: DB): return new_activity.__dict__ return _new_activity + def test__activity_azure_endpoint__returns_all_activities( create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activities= [activity_factory(), activity_factory()] - inserted_activities = [insert_activity(existent_activities[0].__dict__, fake_database), insert_activity(existent_activities[1].__dict__, fake_database)] + existent_activities = [activity_factory(), activity_factory()] + inserted_activities = [ + insert_activity(existent_activities[0].__dict__, fake_database), + insert_activity(existent_activities[1].__dict__, fake_database) + ] azure_activities._get_activities.DATABASE = fake_database 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") - + assert response.status_code == 200 assert activities_json_data == json.dumps(inserted_activities) @@ -38,7 +43,7 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activity= activity_factory() + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._get_activities.DATABASE = fake_database @@ -57,10 +62,10 @@ 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 -): + create_fake_database, activity_factory, insert_activity +): fake_database = create_fake_database - existent_activity= activity_factory() + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._delete_activity.DATABASE = fake_database @@ -76,14 +81,14 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe assert response.status_code == 200 assert activity_json_data['status'] == 0 - assert activity_json_data['deleted'] == True + assert activity_json_data['deleted'] is True def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update( create_fake_database, activity_factory, insert_activity ): fake_database = create_fake_database - existent_activity= activity_factory() + existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, fake_database) azure_activities._update_activity.DATABASE = fake_database @@ -106,18 +111,24 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act def test__activity_azure_endpoint__creates_an_activity__when_activity_has_all_attributes( create_fake_database, ): - azure_activities._create_activity.DATABASE = create_fake_database - activity_body = {'id': None, 'name': Faker().user_name(), 'description': Faker().sentence(),'deleted': False ,'status': 1} - body = json.dumps(activity_body).encode("utf-8") - req = func.HttpRequest( + azure_activities._create_activity.DATABASE = create_fake_database + activity_body = { + 'id': None, + 'name': Faker().user_name(), + 'description': Faker().sentence(), + 'deleted': False, + 'status': 1 + } + body = json.dumps(activity_body).encode("utf-8") + req = func.HttpRequest( method='POST', - body= body, + body=body, url=ACTIVITY_URL, - ) - - response = azure_activities._create_activity.create_activity(req) - activitiy_json_data = json.loads(response.get_body()) - activity_body['id'] = activitiy_json_data['id'] - - assert response.status_code == 201 - assert activitiy_json_data == activity_body \ No newline at end of file + ) + + response = azure_activities._create_activity.create_activity(req) + activitiy_json_data = json.loads(response.get_body()) + activity_body['id'] = activitiy_json_data['id'] + + assert response.status_code == 201 + assert activitiy_json_data == activity_body diff --git a/V2/tests/api_fixtures.py b/V2/tests/api_fixtures.py index a51ab053..d9539035 100644 --- a/V2/tests/api_fixtures.py +++ b/V2/tests/api_fixtures.py @@ -8,7 +8,9 @@ @pytest.fixture(name='activity_factory') def _activity_factory() -> domain.Activity: - def _make_activity(name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1): + def _make_activity( + name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1 + ): activity = domain.Activity( id=None, name=name, diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index c6620169..91c2f729 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1 +1,2 @@ +# flake8: noqa from api_fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py index 655dc422..c72b0d87 100644 --- a/V2/tests/integration/daos/activities_sql_dao_test.py +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -1,8 +1,8 @@ import pytest import typing -import time_tracker.activities._domain as domain -import time_tracker.activities._infrastructure as infrastructure +import time_tracker.activities._domain as domain +import time_tracker.activities._infrastructure as infrastructure from time_tracker._infrastructure import DB @@ -13,6 +13,7 @@ def _new_activity(activity: dict, dao: domain.ActivitiesDao): return new_activity return _new_activity + @pytest.fixture(name='clean_database', autouse=True) def _clean_database(): yield @@ -21,41 +22,53 @@ def _clean_database(): query = dao.activity.delete() dao.db.get_session().execute(query) -def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql_database(create_fake_dao, activity_factory): + +def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql_database( + create_fake_dao, activity_factory +): dao = create_fake_dao existent_activity = activity_factory() - + inserted_activity = dao.create(existent_activity.__dict__) - + assert isinstance(inserted_activity, domain.Activity) assert inserted_activity == existent_activity -def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): +def test_update__returns_an_update_activity__when_an_activity_matching_its_id_is_found_with_sql_database( + create_fake_dao, activity_factory, insert_activity +): dao = create_fake_dao existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) - + updated_activity = dao.update(inserted_activity.id, {'description': 'test demo 2 create an new activity'}) - + assert isinstance(updated_activity, domain.Activity) assert updated_activity.id == inserted_activity.id assert updated_activity.description == 'test demo 2 create an new activity' -def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): +def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database( + create_fake_dao, activity_factory +): dao = create_fake_dao existent_activity = activity_factory() - results = dao.update(existent_activity.id,{'description': 'test demo'}) - - assert results == None - + results = dao.update(existent_activity.id, {'description': 'test demo'}) + + assert results is None + -def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): +def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found_with_sql_database( + create_fake_dao, activity_factory, insert_activity +): dao = create_fake_dao - existent_activities= [activity_factory(), activity_factory()] - inserted_activities = [insert_activity(existent_activities[0].__dict__, dao), insert_activity(existent_activities[1].__dict__, dao)] + existent_activities = [activity_factory(), activity_factory()] + inserted_activities = [ + insert_activity(existent_activities[0].__dict__, dao), + insert_activity(existent_activities[1].__dict__, dao) + ] activities = dao.get_all() @@ -63,7 +76,9 @@ def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_acti assert activities == inserted_activities -def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id_with_sql_database(create_fake_dao, activity_factory, insert_activity): +def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id_with_sql_database( + create_fake_dao, activity_factory, insert_activity +): dao = create_fake_dao existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) @@ -75,23 +90,29 @@ def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matche assert activity == inserted_activity -def test__get_by_id__returns_none__when_no_activity_matches_its_id_with_sql_database(create_fake_dao, activity_factory): +def test__get_by_id__returns_none__when_no_activity_matches_its_id_with_sql_database( + create_fake_dao, activity_factory +): dao = create_fake_dao - existent_activity = activity_factory() + existent_activity = activity_factory() activity = dao.get_by_id(existent_activity.id) - assert activity == None + assert activity is None -def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities_with_sql_database(create_fake_dao): +def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities_with_sql_database( + create_fake_dao +): activities = create_fake_dao.get_all() assert isinstance(activities, typing.List) assert activities == [] -def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory, insert_activity): +def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matching_its_id_is_found_with_sql_database( + create_fake_dao, activity_factory, insert_activity +): dao = create_fake_dao existent_activity = activity_factory() inserted_activity = insert_activity(existent_activity.__dict__, dao) @@ -101,13 +122,15 @@ def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matc assert isinstance(activity, domain.Activity) assert activity.id == inserted_activity.id assert activity.status == 0 - assert activity.deleted == True + assert activity.deleted is True -def test_delete__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database(create_fake_dao, activity_factory): +def test_delete__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database( + create_fake_dao, activity_factory +): dao = create_fake_dao - existent_activity = activity_factory() + existent_activity = activity_factory() results = dao.delete(existent_activity.id) - assert results == None \ No newline at end of file + assert results is None diff --git a/V2/tests/unit/services/activity_service_test.py b/V2/tests/unit/services/activity_service_test.py index ca035f2e..ea8f8ff8 100644 --- a/V2/tests/unit/services/activity_service_test.py +++ b/V2/tests/unit/services/activity_service_test.py @@ -59,6 +59,7 @@ def test__update_activity__uses_the_activity_dao__to_update_one_activity( assert activity_dao.update.called assert expected_activity == updated_activity + def test__create_activity__uses_the_activity_dao__to_create_an_activity(mocker): expected_activity = mocker.Mock() activity_dao = mocker.Mock( diff --git a/V2/tests/unit/use_cases/activities_use_case_test.py b/V2/tests/unit/use_cases/activities_use_case_test.py index 55a77890..09c6c91c 100644 --- a/V2/tests/unit/use_cases/activities_use_case_test.py +++ b/V2/tests/unit/use_cases/activities_use_case_test.py @@ -38,16 +38,17 @@ def test__get_activity_by_id_function__uses_the_activity_service__to_retrieve_ac def test__create_activity_function__uses_the_activities_service__to_create_activity( mocker: MockFixture, ): - expected_activity = mocker.Mock() - activity_service = mocker.Mock( + expected_activity = mocker.Mock() + activity_service = mocker.Mock( create=mocker.Mock(return_value=expected_activity) - ) + ) - activity_use_case = _use_cases.CreateActivityUseCase(activity_service) - actual_activity = activity_use_case.create_activity(fake.pydict()) + activity_use_case = _use_cases.CreateActivityUseCase(activity_service) + actual_activity = activity_use_case.create_activity(fake.pydict()) + + assert activity_service.create.called + assert expected_activity == actual_activity - assert activity_service.create.called - assert expected_activity == actual_activity def test__delete_activity_function__uses_the_activity_service__to_change_activity_status( mocker: MockFixture, @@ -63,6 +64,7 @@ def test__delete_activity_function__uses_the_activity_service__to_change_activit assert activity_service.delete.called assert expected_activity == deleted_activity + def test__update_activity_function__uses_the_activities_service__to_update_an_activity( mocker: MockFixture, ): diff --git a/V2/time_tracker/_infrastructure/__init__.py b/V2/time_tracker/_infrastructure/__init__.py index 30cbea81..ab651958 100644 --- a/V2/time_tracker/_infrastructure/__init__.py +++ b/V2/time_tracker/_infrastructure/__init__.py @@ -1,2 +1,3 @@ +# flake8: noqa from ._db import DB -from ._config import Config \ No newline at end of file +from ._config import Config diff --git a/V2/time_tracker/_infrastructure/_config.py b/V2/time_tracker/_infrastructure/_config.py index 63fedaef..618747fe 100644 --- a/V2/time_tracker/_infrastructure/_config.py +++ b/V2/time_tracker/_infrastructure/_config.py @@ -1,6 +1,7 @@ import typing import os + class Config(typing.NamedTuple): DB_CONNECTION_STRING: str DB_USER: str @@ -9,4 +10,9 @@ class Config(typing.NamedTuple): def load_config(): - return Config(os.environ.get("DB_CONNECTION_STRING"),os.environ.get("DB_USER"),os.environ.get("DB_PASS"),os.environ.get("DB_NAME")) \ No newline at end of file + return Config( + os.environ.get("DB_CONNECTION_STRING"), + os.environ.get("DB_USER"), + os.environ.get("DB_PASS"), + os.environ.get("DB_NAME") + ) diff --git a/V2/time_tracker/_infrastructure/_db.py b/V2/time_tracker/_infrastructure/_db.py index 49eb6e4c..8fe5cef1 100644 --- a/V2/time_tracker/_infrastructure/_db.py +++ b/V2/time_tracker/_infrastructure/_db.py @@ -2,6 +2,7 @@ from . import _config + class DB(): config = _config.load_config() connection = None @@ -9,11 +10,11 @@ class DB(): conn_string = config.DB_CONNECTION_STRING metadata = sqlalchemy.MetaData() - def __init__(self, conn_string: str = conn_string): + def __init__(self, conn_string: str = conn_string): self.engine = sqlalchemy.create_engine(conn_string) - + def get_session(self): - if self.connection == None: + if self.connection is None: self.metadata.create_all(self.engine) self.connection = self.engine.connect() - return self.connection \ No newline at end of file + return self.connection diff --git a/V2/time_tracker/activities/_application/__init__.py b/V2/time_tracker/activities/_application/__init__.py index c8f26492..6c34669a 100644 --- a/V2/time_tracker/activities/_application/__init__.py +++ b/V2/time_tracker/activities/_application/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._activities import get_activities from ._activities import delete_activity from ._activities import update_activity diff --git a/V2/time_tracker/activities/_application/_activities/__init__.py b/V2/time_tracker/activities/_application/_activities/__init__.py index ab7d3844..ef38ae00 100644 --- a/V2/time_tracker/activities/_application/_activities/__init__.py +++ b/V2/time_tracker/activities/_application/_activities/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._get_activities import get_activities from ._delete_activity import delete_activity from ._update_activity import update_activity diff --git a/V2/time_tracker/activities/_application/_activities/_create_activity.py b/V2/time_tracker/activities/_application/_activities/_create_activity.py index 83cd69a9..d0bb4550 100644 --- a/V2/time_tracker/activities/_application/_activities/_create_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_create_activity.py @@ -25,7 +25,7 @@ def create_activity(req: func.HttpRequest) -> func.HttpResponse: ) activity_to_create = _domain.Activity( - id= None, + id=None, name=activity_data['name'], description=activity_data['description'], status=activity_data['status'], diff --git a/V2/time_tracker/activities/_domain/__init__.py b/V2/time_tracker/activities/_domain/__init__.py index f1a97246..66eb2eec 100644 --- a/V2/time_tracker/activities/_domain/__init__.py +++ b/V2/time_tracker/activities/_domain/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._entities import Activity from ._persistence_contracts import ActivitiesDao from ._services import ActivityService diff --git a/V2/time_tracker/activities/_domain/_entities/__init__.py b/V2/time_tracker/activities/_domain/_entities/__init__.py index bf3eb08e..a8cf9289 100644 --- a/V2/time_tracker/activities/_domain/_entities/__init__.py +++ b/V2/time_tracker/activities/_domain/_entities/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activity import Activity diff --git a/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py b/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py index 3495445e..2401254d 100644 --- a/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py +++ b/V2/time_tracker/activities/_domain/_persistence_contracts/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activities_dao import ActivitiesDao diff --git a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py index 7ee1d53d..7d4d89b3 100644 --- a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py +++ b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py @@ -22,4 +22,4 @@ def update(self, id: str, new_activity: dict) -> Activity: @abc.abstractmethod def create(self, activity_data: dict) -> Activity: - pass \ No newline at end of file + pass diff --git a/V2/time_tracker/activities/_domain/_services/__init__.py b/V2/time_tracker/activities/_domain/_services/__init__.py index 1a9befa8..fb9a65cc 100644 --- a/V2/time_tracker/activities/_domain/_services/__init__.py +++ b/V2/time_tracker/activities/_domain/_services/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activity import ActivityService diff --git a/V2/time_tracker/activities/_domain/_use_cases/__init__.py b/V2/time_tracker/activities/_domain/_use_cases/__init__.py index 642d2425..6b330e07 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/__init__.py +++ b/V2/time_tracker/activities/_domain/_use_cases/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._get_activities_use_case import GetActivitiesUseCase from ._get_activity_by_id_use_case import GetActivityUseCase from ._delete_activity_use_case import DeleteActivityUseCase diff --git a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py index afd39627..b5d32ab7 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py @@ -1,11 +1,9 @@ from time_tracker.activities._domain import ActivityService, Activity -import typing class CreateActivityUseCase: def __init__(self, activity_service: ActivityService): self.activity_service = activity_service - def create_activity(self, activity_data: dict ) -> Activity: + def create_activity(self, activity_data: dict) -> Activity: return self.activity_service.create(activity_data) - diff --git a/V2/time_tracker/activities/_infrastructure/__init__.py b/V2/time_tracker/activities/_infrastructure/__init__.py index add89b22..b3896baf 100644 --- a/V2/time_tracker/activities/_infrastructure/__init__.py +++ b/V2/time_tracker/activities/_infrastructure/__init__.py @@ -1 +1,2 @@ -from ._data_persistence import ActivitiesSQLDao \ No newline at end of file +# flake8: noqa +from ._data_persistence import ActivitiesSQLDao diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py index 07db700f..1e7220c5 100644 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_tracker/activities/_infrastructure/_data_persistence/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activities_sql_dao import ActivitiesSQLDao diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py index 3035e029..f464d0e6 100644 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py +++ b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py @@ -7,6 +7,7 @@ import time_tracker.activities._domain as domain from time_tracker._infrastructure import _db + class ActivitiesSQLDao(domain.ActivitiesDao): def __init__(self, database: _db.DB): @@ -14,17 +15,19 @@ def __init__(self, database: _db.DB): field.name for field in dataclasses.fields(domain.Activity) ] self.db = database - self.activity = sqlalchemy.Table('activity', self.db.metadata, - sqlalchemy.Column('id',sqlalchemy.Integer , primary_key=True,autoincrement=True), - sqlalchemy.Column('name', sqlalchemy.String), - sqlalchemy.Column('description', sqlalchemy.String), - sqlalchemy.Column('deleted', sqlalchemy.Boolean), - sqlalchemy.Column('status', sqlalchemy.SmallInteger), - extend_existing=True, - ) - + self.activity = sqlalchemy.Table( + 'activity', + self.db.metadata, + sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True, autoincrement=True), + sqlalchemy.Column('name', sqlalchemy.String), + sqlalchemy.Column('description', sqlalchemy.String), + sqlalchemy.Column('deleted', sqlalchemy.Boolean), + sqlalchemy.Column('status', sqlalchemy.SmallInteger), + extend_existing=True, + ) + def get_by_id(self, activity_id: str) -> domain.Activity: - query = sql.select(self.activity).where(self.activity.c.id == activity_id) + query = sql.select(self.activity).where(self.activity.c.id == activity_id) activity = self.db.get_session().execute(query).one_or_none() return self.__create_activity_dto(dict(activity)) if activity else None @@ -38,23 +41,23 @@ def get_all(self) -> typing.List[domain.Activity]: def create(self, activity_data: dict) -> domain.Activity: activity_data.pop('id', None) - activity_data.update({"status":1, "deleted":False}) + activity_data.update({"status": 1, "deleted": False}) query = self.activity.insert().values(activity_data).return_defaults() activity = self.db.get_session().execute(query) - activity_data.update({"id":activity.inserted_primary_key[0]}) + activity_data.update({"id": activity.inserted_primary_key[0]}) return self.__create_activity_dto(activity_data) - + def delete(self, activity_id: str) -> domain.Activity: - query = self.activity.update().where(self.activity.c.id == activity_id).values({"status":0, "deleted":True}) + query = self.activity.update().where(self.activity.c.id == activity_id).values({"status": 0, "deleted": True}) self.db.get_session().execute(query) - return self.get_by_id(activity_id); + return self.get_by_id(activity_id) def update(self, activity_id: str, new_activity: dict) -> domain.Activity: new_activity.pop('id', None) - query = self.activity.update().where(self.activity.c.id==activity_id).values(new_activity) + query = self.activity.update().where(self.activity.c.id == activity_id).values(new_activity) self.db.get_session().execute(query) return self.get_by_id(activity_id) def __create_activity_dto(self, activity: dict) -> domain.Activity: activity = {key: activity.get(key)for key in self.activity_keys} - return domain.Activity(**activity) \ No newline at end of file + return domain.Activity(**activity) diff --git a/V2/time_tracker/activities/interface.py b/V2/time_tracker/activities/interface.py index 1f1fc805..24c888ad 100644 --- a/V2/time_tracker/activities/interface.py +++ b/V2/time_tracker/activities/interface.py @@ -1,4 +1,5 @@ +# flake8: noqa from ._application import get_activities from ._application import delete_activity from ._application import update_activity -from ._application import create_activity \ No newline at end of file +from ._application import create_activity From f8f633e487b5523e7f4affca1b498e1abf0b79b8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 12 Nov 2021 09:26:31 -0500 Subject: [PATCH 5/6] fix: TT-357 change of an environment variable to a constant --- V2/time_tracker/_infrastructure/_config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/V2/time_tracker/_infrastructure/_config.py b/V2/time_tracker/_infrastructure/_config.py index 618747fe..7f8c8fa7 100644 --- a/V2/time_tracker/_infrastructure/_config.py +++ b/V2/time_tracker/_infrastructure/_config.py @@ -1,6 +1,8 @@ import typing import os +CONNECTION_STRING = 'postgresql://root:root@localhost:5433/timetracker' + class Config(typing.NamedTuple): DB_CONNECTION_STRING: str @@ -11,7 +13,7 @@ class Config(typing.NamedTuple): def load_config(): return Config( - os.environ.get("DB_CONNECTION_STRING"), + 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") From dcf14dfd1aa749987d71fedae7cc0da36bc09c1f Mon Sep 17 00:00:00 2001 From: Daniela Garcia Date: Fri, 12 Nov 2021 18:22:11 -0500 Subject: [PATCH 6/6] refactor: TT-357 Refactor update and create activity --- V2/.flake8 | 2 +- V2/create_activity/function.json | 22 +++++++++ V2/delete_activity/function.json | 22 +++++++++ V2/get_activities/function.json | 22 +++++++++ .../azure/activity_azure_endpoints_test.py | 13 +++--- V2/tests/conftest.py | 2 +- V2/tests/{api_fixtures.py => fixtures.py} | 0 .../daos/activities_sql_dao_test.py | 22 +++++---- .../unit/services/activity_service_test.py | 2 +- .../use_cases/activities_use_case_test.py | 14 +++--- .../_activities/_create_activity.py | 2 +- .../_activities/_delete_activity.py | 19 +++++--- .../_activities/_get_activities.py | 31 +++++++------ .../_activities/_update_activity.py | 46 +++++++++++-------- .../_persistence_contracts/_activities_dao.py | 8 ++-- .../activities/_domain/_services/_activity.py | 10 ++-- .../_use_cases/_create_activity_use_case.py | 2 +- .../_use_cases/_delete_activity_use_case.py | 2 +- .../_get_activity_by_id_use_case.py | 2 +- .../_use_cases/_update_activity_use_case.py | 4 +- .../_data_persistence/_activities_sql_dao.py | 26 ++++++----- V2/update_activity/function.json | 22 +++++++++ 22 files changed, 206 insertions(+), 89 deletions(-) create mode 100644 V2/create_activity/function.json create mode 100644 V2/delete_activity/function.json create mode 100644 V2/get_activities/function.json rename V2/tests/{api_fixtures.py => fixtures.py} (100%) create mode 100644 V2/update_activity/function.json diff --git a/V2/.flake8 b/V2/.flake8 index cb282cae..ecba83ba 100644 --- a/V2/.flake8 +++ b/V2/.flake8 @@ -1,4 +1,4 @@ [flake8] -exclude = .git,__pycache__,./node_modules, +exclude = .git,__pycache__,./node_modules,.venv max-complexity = 10 max_line_length = 120 \ No newline at end of file diff --git a/V2/create_activity/function.json b/V2/create_activity/function.json new file mode 100644 index 00000000..ed3454a9 --- /dev/null +++ b/V2/create_activity/function.json @@ -0,0 +1,22 @@ +{ + "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 new file mode 100644 index 00000000..d51170fd --- /dev/null +++ b/V2/delete_activity/function.json @@ -0,0 +1,22 @@ +{ + "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 new file mode 100644 index 00000000..ee1efe53 --- /dev/null +++ b/V2/get_activities/function.json @@ -0,0 +1,22 @@ +{ + "disabled": false, + "bindings": [ + { + "type": "httpTrigger", + "direction": "in", + "name": "req", + "route": "activities/{id:?}", + "authLevel": "anonymous", + "methods": [ + "GET" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ], + "entryPoint": "get_activities", + "scriptFile": "../time_tracker/activities/interface.py" +} \ No newline at end of file diff --git a/V2/tests/api/azure/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 258a6e2a..9b2618a8 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -7,13 +7,14 @@ 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: dict, database: DB): + def _new_activity(activity: _domain.Activity, database: DB): dao = infrastructure.ActivitiesSQLDao(database) new_activity = dao.create(activity) return new_activity.__dict__ @@ -26,8 +27,8 @@ def test__activity_azure_endpoint__returns_all_activities( fake_database = create_fake_database existent_activities = [activity_factory(), activity_factory()] inserted_activities = [ - insert_activity(existent_activities[0].__dict__, fake_database), - insert_activity(existent_activities[1].__dict__, fake_database) + insert_activity(existent_activities[0], fake_database), + insert_activity(existent_activities[1], fake_database) ] azure_activities._get_activities.DATABASE = fake_database @@ -44,7 +45,7 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it ): fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity.__dict__, fake_database) + inserted_activity = insert_activity(existent_activity, fake_database) azure_activities._get_activities.DATABASE = fake_database req = func.HttpRequest( @@ -66,7 +67,7 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe ): fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity.__dict__, fake_database) + inserted_activity = insert_activity(existent_activity, fake_database) azure_activities._delete_activity.DATABASE = fake_database req = func.HttpRequest( @@ -89,7 +90,7 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act ): fake_database = create_fake_database existent_activity = activity_factory() - inserted_activity = insert_activity(existent_activity.__dict__, fake_database) + inserted_activity = insert_activity(existent_activity, fake_database) azure_activities._update_activity.DATABASE = fake_database activity_body = {"description": Faker().sentence()} diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 91c2f729..d1c4928f 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1,2 +1,2 @@ # flake8: noqa -from api_fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file +from fixtures import _activity_factory, _create_fake_dao, _create_fake_database \ No newline at end of file diff --git a/V2/tests/api_fixtures.py b/V2/tests/fixtures.py similarity index 100% rename from V2/tests/api_fixtures.py rename to V2/tests/fixtures.py diff --git a/V2/tests/integration/daos/activities_sql_dao_test.py b/V2/tests/integration/daos/activities_sql_dao_test.py index c72b0d87..25f62500 100644 --- a/V2/tests/integration/daos/activities_sql_dao_test.py +++ b/V2/tests/integration/daos/activities_sql_dao_test.py @@ -1,5 +1,6 @@ import pytest import typing +from faker import Faker import time_tracker.activities._domain as domain import time_tracker.activities._infrastructure as infrastructure @@ -8,7 +9,7 @@ @pytest.fixture(name='insert_activity') def _insert_activity() -> domain.Activity: - def _new_activity(activity: dict, dao: domain.ActivitiesDao): + def _new_activity(activity: domain.Activity, dao: domain.ActivitiesDao): new_activity = dao.create(activity) return new_activity return _new_activity @@ -29,7 +30,7 @@ def test__create_activity__returns_a_activity_dto__when_saves_correctly_with_sql dao = create_fake_dao existent_activity = activity_factory() - inserted_activity = dao.create(existent_activity.__dict__) + inserted_activity = dao.create(existent_activity) assert isinstance(inserted_activity, domain.Activity) assert inserted_activity == existent_activity @@ -40,13 +41,14 @@ 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.__dict__, dao) + inserted_activity = insert_activity(existent_activity, dao) - updated_activity = dao.update(inserted_activity.id, {'description': 'test demo 2 create an new activity'}) + expected_description = Faker().sentence() + updated_activity = dao.update(inserted_activity.id, None, expected_description, None, None) assert isinstance(updated_activity, domain.Activity) assert updated_activity.id == inserted_activity.id - assert updated_activity.description == 'test demo 2 create an new activity' + assert updated_activity.description == expected_description def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sql_database( @@ -55,7 +57,7 @@ def test_update__returns_none__when_no_activity_matching_its_id_is_found_with_sq dao = create_fake_dao existent_activity = activity_factory() - results = dao.update(existent_activity.id, {'description': 'test demo'}) + results = dao.update(existent_activity.id, Faker().name(), None, None, None) assert results is None @@ -66,8 +68,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].__dict__, dao), - insert_activity(existent_activities[1].__dict__, dao) + insert_activity(existent_activities[0], dao), + insert_activity(existent_activities[1], dao) ] activities = dao.get_all() @@ -81,7 +83,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.__dict__, dao) + inserted_activity = insert_activity(existent_activity, dao) activity = dao.get_by_id(inserted_activity.id) @@ -115,7 +117,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.__dict__, dao) + inserted_activity = insert_activity(existent_activity, dao) activity = dao.delete(inserted_activity.id) diff --git a/V2/tests/unit/services/activity_service_test.py b/V2/tests/unit/services/activity_service_test.py index ea8f8ff8..e8816d42 100644 --- a/V2/tests/unit/services/activity_service_test.py +++ b/V2/tests/unit/services/activity_service_test.py @@ -53,7 +53,7 @@ def test__update_activity__uses_the_activity_dao__to_update_one_activity( activity_service = ActivityService(activity_dao) updated_activity = activity_service.update( - Faker().uuid4(), Faker().pydict() + Faker().uuid4(), Faker().name(), Faker().sentence(), Faker().pyint(), Faker().pybool() ) assert activity_dao.update.called diff --git a/V2/tests/unit/use_cases/activities_use_case_test.py b/V2/tests/unit/use_cases/activities_use_case_test.py index 09c6c91c..ca711019 100644 --- a/V2/tests/unit/use_cases/activities_use_case_test.py +++ b/V2/tests/unit/use_cases/activities_use_case_test.py @@ -1,6 +1,7 @@ -from time_tracker.activities._domain import _use_cases -from pytest_mock import MockFixture from faker import Faker +from pytest_mock import MockFixture + +from time_tracker.activities._domain import _use_cases fake = Faker() @@ -36,7 +37,7 @@ def test__get_activity_by_id_function__uses_the_activity_service__to_retrieve_ac def test__create_activity_function__uses_the_activities_service__to_create_activity( - mocker: MockFixture, + mocker: MockFixture, activity_factory ): expected_activity = mocker.Mock() activity_service = mocker.Mock( @@ -44,7 +45,7 @@ def test__create_activity_function__uses_the_activities_service__to_create_activ ) activity_use_case = _use_cases.CreateActivityUseCase(activity_service) - actual_activity = activity_use_case.create_activity(fake.pydict()) + actual_activity = activity_use_case.create_activity(activity_factory()) assert activity_service.create.called assert expected_activity == actual_activity @@ -66,16 +67,17 @@ def test__delete_activity_function__uses_the_activity_service__to_change_activit def test__update_activity_function__uses_the_activities_service__to_update_an_activity( - mocker: MockFixture, + mocker: MockFixture, activity_factory ): expected_activity = mocker.Mock() activity_service = mocker.Mock( update=mocker.Mock(return_value=expected_activity) ) + new_activity = activity_factory() activity_use_case = _use_cases.UpdateActivityUseCase(activity_service) updated_activity = activity_use_case.update_activity( - fake.uuid4(), fake.pydict() + fake.uuid4(), new_activity.name, new_activity.description, new_activity.status, new_activity.deleted ) assert activity_service.update.called diff --git a/V2/time_tracker/activities/_application/_activities/_create_activity.py b/V2/time_tracker/activities/_application/_activities/_create_activity.py index d0bb4550..94f3701d 100644 --- a/V2/time_tracker/activities/_application/_activities/_create_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_create_activity.py @@ -32,7 +32,7 @@ def create_activity(req: func.HttpRequest) -> func.HttpResponse: deleted=activity_data['deleted'] ) - created_activity = use_case.create_activity(activity_to_create.__dict__) + created_activity = use_case.create_activity(activity_to_create) if not create_activity: return func.HttpResponse( body={'error': 'activity could not be created'}, diff --git a/V2/time_tracker/activities/_application/_activities/_delete_activity.py b/V2/time_tracker/activities/_application/_activities/_delete_activity.py index 65121934..14ada8ab 100644 --- a/V2/time_tracker/activities/_application/_activities/_delete_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_delete_activity.py @@ -14,16 +14,21 @@ def delete_activity(req: func.HttpRequest) -> func.HttpResponse: logging.info( 'Python HTTP trigger function processed a request to delete an activity.' ) - activity_id = req.route_params.get('id') - response = _delete(activity_id) - status_code = 200 if response != b'Not found' else 404 + try: + activity_id = int(req.route_params.get('id')) + response = _delete(activity_id) + status_code = 200 if response != b'Not found' else 404 - return func.HttpResponse( - body=response, status_code=status_code, mimetype="application/json" - ) + return func.HttpResponse( + body=response, status_code=status_code, mimetype="application/json" + ) + except ValueError: + return func.HttpResponse( + body=b"Invalid format id", status_code=400, mimetype="application/json" + ) -def _delete(activity_id: str) -> str: +def _delete(activity_id: int) -> str: activity_use_case = _domain._use_cases.DeleteActivityUseCase( _create_activity_service(DATABASE) ) diff --git a/V2/time_tracker/activities/_application/_activities/_get_activities.py b/V2/time_tracker/activities/_application/_activities/_get_activities.py index ce71d8b9..d92503dd 100644 --- a/V2/time_tracker/activities/_application/_activities/_get_activities.py +++ b/V2/time_tracker/activities/_application/_activities/_get_activities.py @@ -17,19 +17,24 @@ def get_activities(req: func.HttpRequest) -> func.HttpResponse: activity_id = req.route_params.get('id') status_code = 200 - if activity_id: - response = _get_by_id(activity_id) - if response == b'Not Found': - status_code = 404 - else: - response = _get_all() - - return func.HttpResponse( - body=response, status_code=status_code, mimetype="application/json" - ) - - -def _get_by_id(activity_id: str) -> str: + try: + if activity_id: + response = _get_by_id(int(activity_id)) + if response == b'Not Found': + status_code = 404 + else: + response = _get_all() + + return func.HttpResponse( + body=response, status_code=status_code, mimetype="application/json" + ) + except ValueError: + return func.HttpResponse( + body=b"Invalid format id", status_code=400, mimetype="application/json" + ) + + +def _get_by_id(activity_id: int) -> str: activity_use_case = _domain._use_cases.GetActivityUseCase( _create_activity_service(DATABASE) ) diff --git a/V2/time_tracker/activities/_application/_activities/_update_activity.py b/V2/time_tracker/activities/_application/_activities/_update_activity.py index 5d7cf90a..0933fd72 100644 --- a/V2/time_tracker/activities/_application/_activities/_update_activity.py +++ b/V2/time_tracker/activities/_application/_activities/_update_activity.py @@ -15,27 +15,37 @@ def update_activity(req: func.HttpRequest) -> func.HttpResponse: logging.info( 'Python HTTP trigger function processed a request to update an activity.' ) - activity_id = req.route_params.get('id') - activity_data = req.get_json() if req.get_body() else {} - activity_keys = [field.name for field in dataclasses.fields(_domain.Activity)] - - if all(key in activity_keys for key in activity_data.keys()): - response = _update(activity_id, activity_data) - status_code = 200 - else: - response = b'Incorrect activity body' - status_code = 400 - - return func.HttpResponse( - body=response, status_code=status_code, mimetype="application/json" - ) - - -def _update(activity_id: str, activity_data: dict) -> str: + try: + activity_id = int(req.route_params.get('id')) + activity_data = req.get_json() if req.get_body() else {} + activity_keys = [field.name for field in dataclasses.fields(_domain.Activity)] + + if all(key in activity_keys for key in activity_data.keys()): + response = _update(activity_id, activity_data) + status_code = 200 + else: + response = b'Incorrect activity body' + status_code = 400 + + return func.HttpResponse( + body=response, status_code=status_code, mimetype="application/json" + ) + except ValueError: + return func.HttpResponse( + body=b"Invalid format id", status_code=400, mimetype="application/json" + ) + + +def _update(activity_id: int, activity_data: dict) -> str: activity_use_case = _domain._use_cases.UpdateActivityUseCase( _create_activity_service(DATABASE) ) - activity = activity_use_case.update_activity(activity_id, activity_data) + activity = activity_use_case.update_activity( + activity_id, activity_data.get("name"), + activity_data.get("description"), + activity_data.get("status"), + activity_data.get("deleted") + ) return json.dumps(activity.__dict__) if activity else b'Not Found' diff --git a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py index 7d4d89b3..e079ed6a 100644 --- a/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py +++ b/V2/time_tracker/activities/_domain/_persistence_contracts/_activities_dao.py @@ -5,7 +5,7 @@ class ActivitiesDao(abc.ABC): @abc.abstractmethod - def get_by_id(self, id: str) -> Activity: + def get_by_id(self, id: int) -> Activity: pass @abc.abstractmethod @@ -13,13 +13,13 @@ def get_all(self) -> typing.List[Activity]: pass @abc.abstractmethod - def delete(self, id: str) -> Activity: + def delete(self, id: int) -> Activity: pass @abc.abstractmethod - def update(self, id: str, new_activity: dict) -> Activity: + def update(self, id: int, name: str, description: str, status: int, deleted: bool) -> Activity: pass @abc.abstractmethod - def create(self, activity_data: dict) -> Activity: + def create(self, activity_data: Activity) -> Activity: pass diff --git a/V2/time_tracker/activities/_domain/_services/_activity.py b/V2/time_tracker/activities/_domain/_services/_activity.py index 43af499b..a2c45e54 100644 --- a/V2/time_tracker/activities/_domain/_services/_activity.py +++ b/V2/time_tracker/activities/_domain/_services/_activity.py @@ -6,17 +6,17 @@ class ActivityService: def __init__(self, activities_dao: ActivitiesDao): self.activities_dao = activities_dao - def get_by_id(self, activity_id: str) -> Activity: + def get_by_id(self, activity_id: int) -> Activity: return self.activities_dao.get_by_id(activity_id) def get_all(self) -> typing.List[Activity]: return self.activities_dao.get_all() - def delete(self, activity_id: str) -> Activity: + def delete(self, activity_id: int) -> Activity: return self.activities_dao.delete(activity_id) - def update(self, activity_id: str, new_activity: dict) -> Activity: - return self.activities_dao.update(activity_id, new_activity) + def update(self, activity_id: int, name: str, description: str, status: int, deleted: bool) -> Activity: + return self.activities_dao.update(activity_id, name, description, status, deleted) - def create(self, activity_data: dict) -> Activity: + def create(self, activity_data: Activity) -> Activity: return self.activities_dao.create(activity_data) diff --git a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py index b5d32ab7..26d0f475 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_create_activity_use_case.py @@ -5,5 +5,5 @@ class CreateActivityUseCase: def __init__(self, activity_service: ActivityService): self.activity_service = activity_service - def create_activity(self, activity_data: dict) -> Activity: + def create_activity(self, activity_data: Activity) -> Activity: return self.activity_service.create(activity_data) diff --git a/V2/time_tracker/activities/_domain/_use_cases/_delete_activity_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_delete_activity_use_case.py index 5af54ee8..67fcf31c 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_delete_activity_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_delete_activity_use_case.py @@ -5,5 +5,5 @@ class DeleteActivityUseCase: def __init__(self, activity_service: ActivityService): self.activity_service = activity_service - def delete_activity(self, id: str) -> Activity: + def delete_activity(self, id: int) -> Activity: return self.activity_service.delete(id) diff --git a/V2/time_tracker/activities/_domain/_use_cases/_get_activity_by_id_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_get_activity_by_id_use_case.py index 04ca442e..45dbbad0 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_get_activity_by_id_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_get_activity_by_id_use_case.py @@ -5,5 +5,5 @@ class GetActivityUseCase: def __init__(self, activity_service: ActivityService): self.activity_service = activity_service - def get_activity_by_id(self, id: str) -> Activity: + def get_activity_by_id(self, id: int) -> Activity: return self.activity_service.get_by_id(id) diff --git a/V2/time_tracker/activities/_domain/_use_cases/_update_activity_use_case.py b/V2/time_tracker/activities/_domain/_use_cases/_update_activity_use_case.py index a890d85f..c270f465 100644 --- a/V2/time_tracker/activities/_domain/_use_cases/_update_activity_use_case.py +++ b/V2/time_tracker/activities/_domain/_use_cases/_update_activity_use_case.py @@ -6,6 +6,6 @@ def __init__(self, activity_service: ActivityService): self.activity_service = activity_service def update_activity( - self, activity_id: str, new_activity: dict + self, activity_id: int, name: str, description: str, status: int, deleted: bool ) -> Activity: - return self.activity_service.update(activity_id, new_activity) + return self.activity_service.update(activity_id, name, description, status, deleted) diff --git a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py index f464d0e6..e69dd1a4 100644 --- a/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py +++ b/V2/time_tracker/activities/_infrastructure/_data_persistence/_activities_sql_dao.py @@ -26,7 +26,7 @@ def __init__(self, database: _db.DB): extend_existing=True, ) - def get_by_id(self, activity_id: str) -> domain.Activity: + def get_by_id(self, activity_id: int) -> domain.Activity: query = sql.select(self.activity).where(self.activity.c.id == activity_id) activity = self.db.get_session().execute(query).one_or_none() return self.__create_activity_dto(dict(activity)) if activity else None @@ -39,22 +39,26 @@ def get_all(self) -> typing.List[domain.Activity]: for activity in result ] - def create(self, activity_data: dict) -> domain.Activity: - activity_data.pop('id', None) - activity_data.update({"status": 1, "deleted": False}) - query = self.activity.insert().values(activity_data).return_defaults() + def create(self, activity_data: domain.Activity) -> domain.Activity: + new_activity = activity_data.__dict__ + new_activity.pop('id', None) + new_activity.update({"status": 1, "deleted": False}) + + query = self.activity.insert().values(new_activity).return_defaults() activity = self.db.get_session().execute(query) - activity_data.update({"id": activity.inserted_primary_key[0]}) - return self.__create_activity_dto(activity_data) + new_activity.update({"id": activity.inserted_primary_key[0]}) + return self.__create_activity_dto(new_activity) - def delete(self, activity_id: str) -> domain.Activity: + def delete(self, activity_id: int) -> domain.Activity: query = self.activity.update().where(self.activity.c.id == activity_id).values({"status": 0, "deleted": True}) self.db.get_session().execute(query) return self.get_by_id(activity_id) - def update(self, activity_id: str, new_activity: dict) -> domain.Activity: - new_activity.pop('id', None) - query = self.activity.update().where(self.activity.c.id == activity_id).values(new_activity) + def update(self, activity_id: int, name: str, description: str, status: int, deleted: bool) -> domain.Activity: + new_activity = {"name": name, "description": description, "status": status, "deleted": deleted} + activity_validated = {key: value for (key, value) in new_activity.items() if value is not None} + + query = self.activity.update().where(self.activity.c.id == activity_id).values(activity_validated) self.db.get_session().execute(query) return self.get_by_id(activity_id) diff --git a/V2/update_activity/function.json b/V2/update_activity/function.json new file mode 100644 index 00000000..97c9fb49 --- /dev/null +++ b/V2/update_activity/function.json @@ -0,0 +1,22 @@ +{ + "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