Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions V2/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ functions:
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

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

import azure.functions as func

Expand All @@ -25,7 +26,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_
test_db, time_entry_factory, activity_factory, insert_activity
):
inserted_activity = insert_activity(activity_factory(), test_db)
time_entry_body = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]").__dict__
time_entry_body = time_entry_factory(activity_id=inserted_activity.id).__dict__

body = json.dumps(time_entry_body).encode("utf-8")
req = func.HttpRequest(
Expand All @@ -46,7 +47,7 @@ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_de
test_db, time_entry_factory, insert_time_entry, insert_activity, activity_factory,
):
inserted_activity = insert_activity(activity_factory(), test_db).__dict__
time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]")
time_entry_body = time_entry_factory(activity_id=inserted_activity["id"])
inserted_time_entry = insert_time_entry(time_entry_body, test_db)

req = func.HttpRequest(
Expand Down Expand Up @@ -76,3 +77,75 @@ 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_time_entry_azure_endpoint__returns_an_time_entry__when_found_an_time_entry_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)
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_format_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": Faker().sentence()},
)

response = azure_time_entries._update_time_entry.update_time_entry(req)

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


def test__update_time_entries_azure_endpoint__returns_a_status_code_404__when_not_found_an_time_entry_to_update():
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": Faker().pyint()},
)

response = azure_time_entries._update_time_entry.update_time_entry(req)

assert response.status_code == 404
assert response.get_body() == b'Not found'


def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_body():

time_entry_body = Faker().pydict(5, True, str)
req = func.HttpRequest(
method="PUT",
body=json.dumps(time_entry_body).encode("utf-8"),
url=TIME_ENTRY_URL,
route_params={"id": Faker().pyint()},
)

response = azure_time_entries._update_time_entry.update_time_entry(req)

assert response.status_code == 400
assert response.get_body() == b'Incorrect time entry body'
2 changes: 1 addition & 1 deletion V2/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _make_time_entry(
description=Faker().sentence(),
activity_id=Faker().random_int(),
uri=Faker().domain_name(),
technologies=["jira", "git"],
technologies=str(Faker().pylist()),
end_date=str(Faker().date_time()),
deleted=False,
timezone_offset="300",
Expand Down
35 changes: 32 additions & 3 deletions V2/tests/integration/daos/time_entries_dao_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test__time_entry__returns_a_time_entry_dto__when_saves_correctly_with_sql_da
dao = create_fake_dao(test_db)
inserted_activity = insert_activity(activity_factory(), dao.db)

time_entry_to_insert = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
time_entry_to_insert = time_entry_factory(activity_id=inserted_activity.id)

inserted_time_entry = dao.create(time_entry_to_insert)

Expand All @@ -41,7 +41,7 @@ def test__time_entry__returns_None__when_not_saves_correctly(
time_entry_factory, create_fake_dao, test_db
):
dao = create_fake_dao(test_db)
time_entry_to_insert = time_entry_factory(activity_id=1203, technologies="[jira,sql]")
time_entry_to_insert = time_entry_factory(activity_id=1203)

inserted_time_entry = dao.create(time_entry_to_insert)

Expand All @@ -53,7 +53,7 @@ def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_mat
):
dao = create_fake_dao(test_db)
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entry = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
existent_time_entry = time_entry_factory(activity_id=inserted_activity.id)
inserted_time_entry = dao.create(existent_time_entry)

result = dao.delete(inserted_time_entry.id)
Expand All @@ -69,3 +69,32 @@ def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found(
result = dao.delete(Faker().pyint())

assert result is None


def test_update__returns_an_time_entry_dto__when_found_one_time_entry_to_update(
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
):
dao = create_fake_dao(test_db)
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id)
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 == inserted_time_entries.get("description")


def test_update__returns_none__when_doesnt_found_one_time_entry_to_update(
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
):
dao = create_fake_dao(test_db)
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id)
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
13 changes: 13 additions & 0 deletions V2/tests/unit/use_cases/time_entries_use_case_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,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: 3 additions & 1 deletion V2/time_tracker/time_entries/_application/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# flake8: noqa
from ._time_entries import create_time_entry, delete_time_entry
from ._time_entries import create_time_entry
from ._time_entries import 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,46 @@
import dataclasses
import json

import azure.functions as func

from ... import _domain
from ... import _infrastructure
from time_tracker._infrastructure import DB


def update_time_entry(req: func.HttpRequest) -> func.HttpResponse:
database = DB()
time_entry_dao = _infrastructure.TimeEntriesSQLDao(database)
time_entry_service = _domain.TimeEntryService(time_entry_dao)
use_case = _domain._use_cases.UpdateTimeEntryUseCase(time_entry_service)

try:
time_entry_id = int(req.route_params.get("id"))
time_entry_data = req.get_json()

if not _validate_time_entry(time_entry_data):
status_code = 400
response = b"Incorrect time entry body"
else:
updated_time_entry = use_case.update_time_entry(time_entry_id, time_entry_data)
status_code, response = [
404, b"Not found"
] if not updated_time_entry else [200, json.dumps(updated_time_entry.__dict__)]

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 _validate_time_entry(time_entry_data: dict) -> bool:
time_entry_keys = [field.name for field in dataclasses.fields(_domain.TimeEntry)]
return all(key in time_entry_keys for key in time_entry_data.keys())
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,19 @@ 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:
try:
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_updated_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_updated_time_entry).one_or_none()

return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None
except sqlalchemy.exc.SQLAlchemyError:
return None

def delete(self, time_entry_id: int) -> domain.TimeEntry:
query = (
Expand All @@ -58,3 +68,10 @@ 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