diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..36bff27a --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,39 @@ +name: Time Tacker V2 CI + +on: + push: + branches: + - master + + pull_request: + branches: + - master + +jobs: + build-ci-time-tracker: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10.0 + uses: actions/setup-python@v2 + with: + python-version: 3.10.0 + + - name: Add conda to system path + run: | + echo $CONDA/bin >> $GITHUB_PATH + + - name: Install dependencies + run: | + pip install -r V2/requirements.txt + + - name: Lint with flake8 + run: | + cd V2 + flake8 . --show-source --statistics + - name: Test with pytest + run: | + cd V2 + python -m pytest -v \ No newline at end of file diff --git a/V2/.flake8 b/V2/.flake8 new file mode 100644 index 00000000..cb282cae --- /dev/null +++ b/V2/.flake8 @@ -0,0 +1,4 @@ +[flake8] +exclude = .git,__pycache__,./node_modules, +max-complexity = 10 +max_line_length = 120 \ No newline at end of file diff --git a/V2/requirements.txt b/V2/requirements.txt index 41acba1d..c651bb35 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/azure/activity_azure_endpoints_test.py b/V2/tests/api/azure/activity_azure_endpoints_test.py index 05b22801..de52712e 100644 --- a/V2/tests/api/azure/activity_azure_endpoints_test.py +++ b/V2/tests/api/azure/activity_azure_endpoints_test.py @@ -3,10 +3,9 @@ import azure.functions as func import json -import typing -ACTIVITY_URL = '/api/activities/' +ACTIVITY_URL = "/api/activities/" def test__activity_azure_endpoint__returns_all_activities( @@ -14,7 +13,7 @@ def test__activity_azure_endpoint__returns_all_activities( ): activities_json, tmp_directory = create_temp_activities activities._get_activities.JSON_PATH = tmp_directory - req = func.HttpRequest(method='GET', body=None, url=ACTIVITY_URL) + req = func.HttpRequest(method="GET", body=None, url=ACTIVITY_URL) response = activities.get_activities(req) activities_json_data = response.get_body().decode("utf-8") @@ -29,10 +28,10 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it activities_json, tmp_directory = create_temp_activities activities._get_activities.JSON_PATH = tmp_directory req = func.HttpRequest( - method='GET', + method="GET", body=None, url=ACTIVITY_URL, - route_params={"id": activities_json[0]['id']}, + route_params={"id": activities_json[0]["id"]}, ) response = activities.get_activities(req) @@ -48,17 +47,17 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe activities_json, tmp_directory = create_temp_activities activities._delete_activity.JSON_PATH = tmp_directory req = func.HttpRequest( - method='DELETE', + method="DELETE", body=None, url=ACTIVITY_URL, - route_params={"id": activities_json[0]['id']}, + route_params={"id": activities_json[0]["id"]}, ) response = activities.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"] == "inactive" def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update( @@ -68,10 +67,10 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act activities._update_activity.JSON_PATH = tmp_directory activity_data = {"description": Faker().sentence()} req = func.HttpRequest( - method='PUT', + method="PUT", body=json.dumps(activity_data).encode("utf-8"), url=ACTIVITY_URL, - route_params={"id": activities_json[0]['id']}, + route_params={"id": activities_json[0]["id"]}, ) response = activities.update_activity(req) @@ -81,21 +80,29 @@ def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_act assert response.status_code == 200 assert activitiy_json_data == json.dumps(new_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 \ No newline at end of file + 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 diff --git a/V2/tests/conftest.py b/V2/tests/conftest.py index 59065296..2741ce95 100644 --- a/V2/tests/conftest.py +++ b/V2/tests/conftest.py @@ -1 +1,2 @@ +# flake8: noqa from tests.api.api_fixtures import create_temp_activities diff --git a/V2/tests/integration/daos/activities_json_dao_test.py b/V2/tests/integration/daos/activities_json_dao_test.py index f2b0dacd..d5d3a180 100644 --- a/V2/tests/integration/daos/activities_json_dao_test.py +++ b/V2/tests/integration/daos/activities_json_dao_test.py @@ -8,21 +8,21 @@ fake_activities = [ { - 'id': Faker().uuid4(), - 'name': Faker().user_name(), - 'description': Faker().sentence(), - 'deleted': Faker().uuid4(), - 'status': 'active', - 'tenant_id': Faker().uuid4(), + "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') +@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)) + mocker.patch("builtins.open", mocker.mock_open(read_data=read_data)) return [Activity(**activity) for activity in activities] return _creator @@ -94,7 +94,7 @@ def test_delete__returns_an_activity_with_inactive_status__when_an_activity_matc activity_dto = activities.pop() result = activities_json_dao.delete(activity_dto.id) - assert result.status == 'inactive' + assert result.status == "inactive" def test_delete__returns_none__when_no_activity_matching_its_id_is_found( @@ -129,21 +129,24 @@ def test_update__returns_none__when_doesnt_found_one_activity_to_update( create_fake_activities([]) activity_data = {"description": Faker().sentence()} - result = activities_json_dao.update('', activity_data) + result = activities_json_dao.update("", activity_data) - assert result == None + 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) \ No newline at end of file +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/unit/services/activity_service_test.py b/V2/tests/unit/services/activity_service_test.py index 9315d24f..9fbed763 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 793c32d6..3a8d1301 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_entries._domain import ActivityService from time_entries._domain import _use_cases from pytest_mock import MockFixture from faker import Faker @@ -37,26 +36,25 @@ 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) - ) + 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()) - activity_use_case = _use_cases.CreateActivityUseCase(activity_service) - actual_activity = activity_use_case.create_activity(fake.pydict()) + assert activity_service.create_activity.called + assert expected_activity == actual_activity - assert activity_service.create_activity.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()) @@ -64,18 +62,15 @@ 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_entries/_application/__init__.py b/V2/time_entries/_application/__init__.py index c8f26492..6c34669a 100644 --- a/V2/time_entries/_application/__init__.py +++ b/V2/time_entries/_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_entries/_application/_activities/__init__.py b/V2/time_entries/_application/_activities/__init__.py index ab7d3844..ef38ae00 100644 --- a/V2/time_entries/_application/_activities/__init__.py +++ b/V2/time_entries/_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_entries/_application/_activities/_create_activity.py b/V2/time_entries/_application/_activities/_create_activity.py index c745a1cd..2a2a622c 100644 --- a/V2/time_entries/_application/_activities/_create_activity.py +++ b/V2/time_entries/_application/_activities/_create_activity.py @@ -1,5 +1,4 @@ import json -import logging import dataclasses import typing @@ -18,19 +17,16 @@ 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, + id=None, name=activity_data['name'], description=activity_data['description'], status=activity_data['status'], @@ -38,7 +34,6 @@ def create_activity(req: func.HttpRequest) -> func.HttpResponse: tenant_id=activity_data['tenant_id'] ) - created_activity = use_case.create_activity(activity_to_create.__dict__) if not create_activity: return func.HttpResponse( diff --git a/V2/time_entries/_domain/__init__.py b/V2/time_entries/_domain/__init__.py index f1a97246..66eb2eec 100644 --- a/V2/time_entries/_domain/__init__.py +++ b/V2/time_entries/_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_entries/_domain/_entities/__init__.py b/V2/time_entries/_domain/_entities/__init__.py index bf3eb08e..a8cf9289 100644 --- a/V2/time_entries/_domain/_entities/__init__.py +++ b/V2/time_entries/_domain/_entities/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activity import Activity diff --git a/V2/time_entries/_domain/_persistence_contracts/__init__.py b/V2/time_entries/_domain/_persistence_contracts/__init__.py index 3495445e..2401254d 100644 --- a/V2/time_entries/_domain/_persistence_contracts/__init__.py +++ b/V2/time_entries/_domain/_persistence_contracts/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activities_dao import ActivitiesDao diff --git a/V2/time_entries/_domain/_persistence_contracts/_activities_dao.py b/V2/time_entries/_domain/_persistence_contracts/_activities_dao.py index 2037841d..f7e7bac3 100644 --- a/V2/time_entries/_domain/_persistence_contracts/_activities_dao.py +++ b/V2/time_entries/_domain/_persistence_contracts/_activities_dao.py @@ -23,7 +23,3 @@ def update(self, id: str, new_activity: dict) -> Activity: @abc.abstractmethod def create_activity(self, activity_data: dict) -> Activity: pass - - @abc.abstractmethod - def delete(self, id: str) -> Activity: - pass diff --git a/V2/time_entries/_domain/_services/__init__.py b/V2/time_entries/_domain/_services/__init__.py index 1a9befa8..fb9a65cc 100644 --- a/V2/time_entries/_domain/_services/__init__.py +++ b/V2/time_entries/_domain/_services/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activity import ActivityService diff --git a/V2/time_entries/_domain/_use_cases/__init__.py b/V2/time_entries/_domain/_use_cases/__init__.py index 642d2425..6b330e07 100644 --- a/V2/time_entries/_domain/_use_cases/__init__.py +++ b/V2/time_entries/_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_entries/_domain/_use_cases/_create_activity_use_case.py b/V2/time_entries/_domain/_use_cases/_create_activity_use_case.py index a7f7a66e..fbd13aac 100644 --- a/V2/time_entries/_domain/_use_cases/_create_activity_use_case.py +++ b/V2/time_entries/_domain/_use_cases/_create_activity_use_case.py @@ -1,11 +1,9 @@ from time_entries._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(activity_data) - diff --git a/V2/time_entries/_infrastructure/__init__.py b/V2/time_entries/_infrastructure/__init__.py index df144da6..1734e5b8 100644 --- a/V2/time_entries/_infrastructure/__init__.py +++ b/V2/time_entries/_infrastructure/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._data_persistence import ActivitiesJsonDao diff --git a/V2/time_entries/_infrastructure/_data_persistence/__init__.py b/V2/time_entries/_infrastructure/_data_persistence/__init__.py index 802f35f4..d2a77fc4 100644 --- a/V2/time_entries/_infrastructure/_data_persistence/__init__.py +++ b/V2/time_entries/_infrastructure/_data_persistence/__init__.py @@ -1 +1,2 @@ +# flake8: noqa from ._activities_json_dao import ActivitiesJsonDao diff --git a/V2/time_entries/_infrastructure/_data_persistence/_activities_json_dao.py b/V2/time_entries/_infrastructure/_data_persistence/_activities_json_dao.py index ab8f5765..54418c45 100644 --- a/V2/time_entries/_infrastructure/_data_persistence/_activities_json_dao.py +++ b/V2/time_entries/_infrastructure/_data_persistence/_activities_json_dao.py @@ -3,6 +3,7 @@ import json import typing + class ActivitiesJsonDao(ActivitiesDao): def __init__(self, json_data_file_path: str): self.json_data_file_path = json_data_file_path @@ -88,7 +89,6 @@ def create_activity(self, activity_data: dict) -> Activity: except FileNotFoundError: print("Can not create activity") - def __get_activities_from_file(self) -> typing.List[dict]: try: file = open(self.json_data_file_path) diff --git a/V2/time_entries/interface.py b/V2/time_entries/interface.py index 1f1fc805..877b631e 100644 --- a/V2/time_entries/interface.py +++ b/V2/time_entries/interface.py @@ -1,3 +1,4 @@ +# flake8: noqa from ._application import get_activities from ._application import delete_activity from ._application import update_activity