Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: TT-402 add put time entries
  • Loading branch information
Jobzi committed Nov 24, 2021
commit adab8f40ab6744e693ce45a61755540df21b472f
14 changes: 13 additions & 1 deletion V2/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,25 @@ functions:

delete_time_entry:
handler: time_tracker/time_entries/interface.delete_time_entry
events:
events:
- http: true
x-azure-settings:
methods:
- DELETE
route: time-entries/{id}
authLevel: anonymous


update_time_entry:
handler: time_tracker/time_entries/interface.update_time_entry
events:
- http: true
x-azure-settings:
methods:
- PUT
route: time-entries/{id}
authLevel: anonymous

create_customer:
handler: time_tracker/customers/interface.create_customer
events:
Expand All @@ -106,3 +117,4 @@ functions:
- POST
route: customers/
authLevel: anonymous

42 changes: 42 additions & 0 deletions V2/tests/api/azure/time_entry_azure_endpoints_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest
import json
import pytest
from faker import Faker

import azure.functions as func

Expand Down Expand Up @@ -76,3 +78,43 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti

assert response.status_code == 400
assert response.get_body() == b'Invalid Format ID'


def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update(
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
):
inserted_activity = insert_activity(activity_factory(), test_db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__

time_entry_body = {"description": Faker().sentence()}

req = func.HttpRequest(
method='PUT',
body=json.dumps(time_entry_body).encode("utf-8"),
url=TIME_ENTRY_URL,
route_params={"id": inserted_time_entries["id"]},
)

response = azure_time_entries._update_time_entry.update_time_entry(req)
activitiy_json_data = response.get_body().decode("utf-8")
inserted_time_entries.update(time_entry_body)

assert response.status_code == 200
assert activitiy_json_data == json.dumps(inserted_time_entries)


def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id():
time_entry_body = {"description": Faker().sentence()}

req = func.HttpRequest(
method="PUT",
body=json.dumps(time_entry_body).encode("utf-8"),
url=TIME_ENTRY_URL,
route_params={"id": "invalid id"},
)

response = azure_time_entries._update_time_entry.update_time_entry(req)

assert response.status_code == 400
assert response.get_body() == b'Invalid ID'
41 changes: 41 additions & 0 deletions V2/tests/integration/daos/time_entries_sql_dao_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao

from time_tracker._infrastructure import DB


@pytest.fixture(name='create_time_entry_fake_dao')
def _create_fake_dao() -> TimeEntriesSQLDao:
db_fake = DB('sqlite:///:memory:')
dao = TimeEntriesSQLDao(db_fake)
return dao


def test_update__returns_an_time_entry_dto__when_found_one_time_entry_to_update(
create_time_entry_fake_dao, time_entry_factory, insert_activity, activity_factory
):
dao = create_time_entry_fake_dao
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
inserted_time_entries = dao.create(existent_time_entries).__dict__
time_entry_id = inserted_time_entries["id"]
inserted_time_entries.update({"description": "description updated"})

time_entry = dao.update(time_entry_id=time_entry_id, time_entry_data=inserted_time_entries)

assert time_entry.id == time_entry_id
assert time_entry.description == "description updated"


def test_update__returns_none__when_doesnt_found_one_time_entry_to_update(
create_time_entry_fake_dao, time_entry_factory, insert_activity, activity_factory
):
dao = create_time_entry_fake_dao
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
inserted_time_entries = dao.create(existent_time_entries).__dict__

time_entry = dao.update(0, inserted_time_entries)

assert time_entry is None
17 changes: 17 additions & 0 deletions V2/tests/unit/services/time_entry_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,20 @@ def test__delete_time_entry__uses_the_time_entry_dao__to_delete_time_entry_selec

assert time_entry_dao.delete.called
assert expected_time_entry == deleted_time_entry


def test__update_time_entry__uses_the_time_entry_dao__to_update_one_time_entry(
mocker,
):
expected_time_entry = mocker.Mock()
time_entry_dao = mocker.Mock(
update=mocker.Mock(return_value=expected_time_entry)
)
time_entry_service = TimeEntryService(time_entry_dao)

updated_time_entry = time_entry_service.update(
Faker().pyint(), Faker().pydict()
)

assert time_entry_dao.update.called
assert expected_time_entry == updated_time_entry
14 changes: 14 additions & 0 deletions V2/tests/unit/use_cases/time_entries_use_case_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from faker import Faker
from pytest_mock import MockFixture
from faker import Faker

Expand Down Expand Up @@ -30,3 +31,16 @@ def test__delete_time_entry_function__uses_the_time_entry_service__to_delete_tim

assert time_entry_service.delete.called
assert expected_time_entry == deleted_time_entry


def test__update_time_entries_function__uses_the_time_entry_service__to_update_an_time_entry(
mocker: MockFixture,
):
expected_time_entry = mocker.Mock()
time_entry_service = mocker.Mock(update=mocker.Mock(return_value=expected_time_entry))

time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(time_entry_service)
updated_time_entry = time_entry_use_case.update_time_entry(Faker().uuid4(), Faker().pydict())

assert time_entry_service.update.called
assert expected_time_entry == updated_time_entry
4 changes: 4 additions & 0 deletions V2/time_tracker/_infrastructure/_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from . import _config

_TEST_DIALECT = "sqlite"


class DB():
config = _config.load_config()
Expand All @@ -17,4 +19,6 @@ def get_session(self):
self.metadata.create_all(self.engine)
if self.connection is None:
self.connection = self.engine.connect()
if self.engine.dialect.name == _TEST_DIALECT:
self.connection.execute("pragma foreign_keys=ON")
return self.connection
3 changes: 2 additions & 1 deletion V2/time_tracker/time_entries/_application/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# flake8: noqa
from ._time_entries import create_time_entry, delete_time_entry
from ._time_entries import create_time_entry, delete_time_entry
from ._time_entries import update_time_entry
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
from ._create_time_entry import create_time_entry
from ._delete_time_entry import delete_time_entry
from ._delete_time_entry import delete_time_entry
from ._update_time_entry import update_time_entry
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import dataclasses
import json
import logging

import azure.functions as func

from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao
from time_tracker.time_entries._domain import TimeEntryService, TimeEntry, _use_cases
from time_tracker._infrastructure import DB



def update_time_entry(req: func.HttpRequest) -> func.HttpResponse:
logging.info(
'Python HTTP trigger function processed a request to update an time entry.'
)
time_entry_id = req.route_params.get('id')
time_entry_data = req.get_json() if req.get_body() else {}
time_entry_keys = [field.name for field in dataclasses.fields(TimeEntry)]

if all(key in time_entry_keys for key in time_entry_data.keys()):
try:
response = _update(int(time_entry_id), time_entry_data)
status_code = 200
except ValueError:
response = b'Invalid ID'
status_code = 400
else:
response = b'Incorrect time entry body'
status_code = 400

return func.HttpResponse(
body=response, status_code=status_code, mimetype="application/json"
)


def _update(time_entry_id: int, time_entry_data: dict) -> str:
database = DB()
time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(
_create_time_entry_service(database)
)
time_entry = time_entry_use_case.update_time_entry(time_entry_id, time_entry_data)
return json.dumps(time_entry.__dict__) if time_entry else b'Not Found'


def _create_time_entry_service(db: DB):
time_entry_sql = TimeEntriesSQLDao(db)
return TimeEntryService(time_entry_sql)
3 changes: 2 additions & 1 deletion V2/time_tracker/time_entries/_domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
from ._services import TimeEntryService
from ._use_cases import (
CreateTimeEntryUseCase,
DeleteTimeEntryUseCase
DeleteTimeEntryUseCase,
UpdateTimeEntryUseCase
)
2 changes: 1 addition & 1 deletion V2/time_tracker/time_entries/_domain/_entities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# flake8: noqa
from ._time_entry import TimeEntry
from ._time_entry import TimeEntry
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# flake8: noqa
from ._time_entries_dao import TimeEntriesDao
from ._time_entries_dao import TimeEntriesDao
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ def create(self, time_entry_data: TimeEntry) -> TimeEntry:
@abc.abstractmethod
def delete(self, id: int) -> TimeEntry:
pass

@abc.abstractmethod
def update(self, id: int, new_time_entry: dict) -> TimeEntry:
pass
2 changes: 1 addition & 1 deletion V2/time_tracker/time_entries/_domain/_services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# flake8: noqa
from ._time_entry import TimeEntryService
from ._time_entry import TimeEntryService
3 changes: 3 additions & 0 deletions V2/time_tracker/time_entries/_domain/_services/_time_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ def create(self, time_entry_data: TimeEntry) -> TimeEntry:

def delete(self, id: int) -> TimeEntry:
return self.time_entry_dao.delete(id)

def update(self, time_entry_id: int, new_time_entry: dict) -> TimeEntry:
return self.time_entry_dao.update(time_entry_id, new_time_entry)
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
from ._create_time_entry_use_case import CreateTimeEntryUseCase
from ._delete_time_entry_use_case import DeleteTimeEntryUseCase
from ._update_time_entry_use_case import UpdateTimeEntryUseCase
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from time_tracker.time_entries._domain import TimeEntryService, TimeEntry


class UpdateTimeEntryUseCase:
def __init__(self, time_entry_service: TimeEntryService):
self.time_entry_service = time_entry_service

def update_time_entry(
self, time_entry_id: int, new_time_entry: dict
) -> TimeEntry:
return self.time_entry_service.update(time_entry_id, new_time_entry)
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,14 @@ def create(self, time_entry_data: domain.TimeEntry) -> domain.TimeEntry:
except sqlalchemy.exc.SQLAlchemyError:
return None

def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry:
time_entry = {key: time_entry.get(key) for key in self.time_entry_key}
return domain.TimeEntry(**time_entry)
def update(self, time_entry_id: int, time_entry_data: dict) -> domain.TimeEntry:

query = self.time_entry.update().where(self.time_entry.c.id == time_entry_id).values(time_entry_data)
self.db.get_session().execute(query)
query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id)
time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none()

return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None

def delete(self, time_entry_id: int) -> domain.TimeEntry:
query = (
Expand All @@ -58,3 +63,11 @@ def delete(self, time_entry_id: int) -> domain.TimeEntry:
query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id)
time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none()
return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None


def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry:
time_entry.update({
"start_date": str(time_entry.get("start_date")),
"end_date": str(time_entry.get("end_date"))})
time_entry = {key: time_entry.get(key) for key in self.time_entry_key}
return domain.TimeEntry(**time_entry)
3 changes: 2 additions & 1 deletion V2/time_tracker/time_entries/interface.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
from ._application import create_time_entry
from ._application import delete_time_entry
from ._application import delete_time_entry
from ._application import update_time_entry