Skip to content

Commit adab8f4

Browse files
committed
feat: TT-402 add put time entries
1 parent d6c4c4d commit adab8f4

File tree

19 files changed

+225
-11
lines changed

19 files changed

+225
-11
lines changed

V2/serverless.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,25 @@ functions:
8989

9090
delete_time_entry:
9191
handler: time_tracker/time_entries/interface.delete_time_entry
92-
events:
92+
events:
9393
- http: true
9494
x-azure-settings:
9595
methods:
9696
- DELETE
9797
route: time-entries/{id}
9898
authLevel: anonymous
9999

100+
101+
update_time_entry:
102+
handler: time_tracker/time_entries/interface.update_time_entry
103+
events:
104+
- http: true
105+
x-azure-settings:
106+
methods:
107+
- PUT
108+
route: time-entries/{id}
109+
authLevel: anonymous
110+
100111
create_customer:
101112
handler: time_tracker/customers/interface.create_customer
102113
events:
@@ -106,3 +117,4 @@ functions:
106117
- POST
107118
route: customers/
108119
authLevel: anonymous
120+

V2/tests/api/azure/time_entry_azure_endpoints_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pytest
22
import json
3+
import pytest
4+
from faker import Faker
35

46
import azure.functions as func
57

@@ -76,3 +78,43 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti
7678

7779
assert response.status_code == 400
7880
assert response.get_body() == b'Invalid Format ID'
81+
82+
83+
def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update(
84+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
85+
):
86+
inserted_activity = insert_activity(activity_factory(), test_db)
87+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
88+
inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__
89+
90+
time_entry_body = {"description": Faker().sentence()}
91+
92+
req = func.HttpRequest(
93+
method='PUT',
94+
body=json.dumps(time_entry_body).encode("utf-8"),
95+
url=TIME_ENTRY_URL,
96+
route_params={"id": inserted_time_entries["id"]},
97+
)
98+
99+
response = azure_time_entries._update_time_entry.update_time_entry(req)
100+
activitiy_json_data = response.get_body().decode("utf-8")
101+
inserted_time_entries.update(time_entry_body)
102+
103+
assert response.status_code == 200
104+
assert activitiy_json_data == json.dumps(inserted_time_entries)
105+
106+
107+
def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id():
108+
time_entry_body = {"description": Faker().sentence()}
109+
110+
req = func.HttpRequest(
111+
method="PUT",
112+
body=json.dumps(time_entry_body).encode("utf-8"),
113+
url=TIME_ENTRY_URL,
114+
route_params={"id": "invalid id"},
115+
)
116+
117+
response = azure_time_entries._update_time_entry.update_time_entry(req)
118+
119+
assert response.status_code == 400
120+
assert response.get_body() == b'Invalid ID'
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pytest
2+
3+
from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao
4+
5+
from time_tracker._infrastructure import DB
6+
7+
8+
@pytest.fixture(name='create_time_entry_fake_dao')
9+
def _create_fake_dao() -> TimeEntriesSQLDao:
10+
db_fake = DB('sqlite:///:memory:')
11+
dao = TimeEntriesSQLDao(db_fake)
12+
return dao
13+
14+
15+
def test_update__returns_an_time_entry_dto__when_found_one_time_entry_to_update(
16+
create_time_entry_fake_dao, time_entry_factory, insert_activity, activity_factory
17+
):
18+
dao = create_time_entry_fake_dao
19+
inserted_activity = insert_activity(activity_factory(), dao.db)
20+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
21+
inserted_time_entries = dao.create(existent_time_entries).__dict__
22+
time_entry_id = inserted_time_entries["id"]
23+
inserted_time_entries.update({"description": "description updated"})
24+
25+
time_entry = dao.update(time_entry_id=time_entry_id, time_entry_data=inserted_time_entries)
26+
27+
assert time_entry.id == time_entry_id
28+
assert time_entry.description == "description updated"
29+
30+
31+
def test_update__returns_none__when_doesnt_found_one_time_entry_to_update(
32+
create_time_entry_fake_dao, time_entry_factory, insert_activity, activity_factory
33+
):
34+
dao = create_time_entry_fake_dao
35+
inserted_activity = insert_activity(activity_factory(), dao.db)
36+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
37+
inserted_time_entries = dao.create(existent_time_entries).__dict__
38+
39+
time_entry = dao.update(0, inserted_time_entries)
40+
41+
assert time_entry is None

V2/tests/unit/services/time_entry_service_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,20 @@ def test__delete_time_entry__uses_the_time_entry_dao__to_delete_time_entry_selec
2929

3030
assert time_entry_dao.delete.called
3131
assert expected_time_entry == deleted_time_entry
32+
33+
34+
def test__update_time_entry__uses_the_time_entry_dao__to_update_one_time_entry(
35+
mocker,
36+
):
37+
expected_time_entry = mocker.Mock()
38+
time_entry_dao = mocker.Mock(
39+
update=mocker.Mock(return_value=expected_time_entry)
40+
)
41+
time_entry_service = TimeEntryService(time_entry_dao)
42+
43+
updated_time_entry = time_entry_service.update(
44+
Faker().pyint(), Faker().pydict()
45+
)
46+
47+
assert time_entry_dao.update.called
48+
assert expected_time_entry == updated_time_entry

V2/tests/unit/use_cases/time_entries_use_case_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from faker import Faker
12
from pytest_mock import MockFixture
23
from faker import Faker
34

@@ -30,3 +31,16 @@ def test__delete_time_entry_function__uses_the_time_entry_service__to_delete_tim
3031

3132
assert time_entry_service.delete.called
3233
assert expected_time_entry == deleted_time_entry
34+
35+
36+
def test__update_time_entries_function__uses_the_time_entry_service__to_update_an_time_entry(
37+
mocker: MockFixture,
38+
):
39+
expected_time_entry = mocker.Mock()
40+
time_entry_service = mocker.Mock(update=mocker.Mock(return_value=expected_time_entry))
41+
42+
time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(time_entry_service)
43+
updated_time_entry = time_entry_use_case.update_time_entry(Faker().uuid4(), Faker().pydict())
44+
45+
assert time_entry_service.update.called
46+
assert expected_time_entry == updated_time_entry

V2/time_tracker/_infrastructure/_db.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from . import _config
44

5+
_TEST_DIALECT = "sqlite"
6+
57

68
class DB():
79
config = _config.load_config()
@@ -17,4 +19,6 @@ def get_session(self):
1719
self.metadata.create_all(self.engine)
1820
if self.connection is None:
1921
self.connection = self.engine.connect()
22+
if self.engine.dialect.name == _TEST_DIALECT:
23+
self.connection.execute("pragma foreign_keys=ON")
2024
return self.connection
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# flake8: noqa
2-
from ._time_entries import create_time_entry, delete_time_entry
2+
from ._time_entries import create_time_entry, delete_time_entry
3+
from ._time_entries import update_time_entry
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# flake8: noqa
22
from ._create_time_entry import create_time_entry
3-
from ._delete_time_entry import delete_time_entry
3+
from ._delete_time_entry import delete_time_entry
4+
from ._update_time_entry import update_time_entry
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import dataclasses
2+
import json
3+
import logging
4+
5+
import azure.functions as func
6+
7+
from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao
8+
from time_tracker.time_entries._domain import TimeEntryService, TimeEntry, _use_cases
9+
from time_tracker._infrastructure import DB
10+
11+
12+
13+
def update_time_entry(req: func.HttpRequest) -> func.HttpResponse:
14+
logging.info(
15+
'Python HTTP trigger function processed a request to update an time entry.'
16+
)
17+
time_entry_id = req.route_params.get('id')
18+
time_entry_data = req.get_json() if req.get_body() else {}
19+
time_entry_keys = [field.name for field in dataclasses.fields(TimeEntry)]
20+
21+
if all(key in time_entry_keys for key in time_entry_data.keys()):
22+
try:
23+
response = _update(int(time_entry_id), time_entry_data)
24+
status_code = 200
25+
except ValueError:
26+
response = b'Invalid ID'
27+
status_code = 400
28+
else:
29+
response = b'Incorrect time entry body'
30+
status_code = 400
31+
32+
return func.HttpResponse(
33+
body=response, status_code=status_code, mimetype="application/json"
34+
)
35+
36+
37+
def _update(time_entry_id: int, time_entry_data: dict) -> str:
38+
database = DB()
39+
time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(
40+
_create_time_entry_service(database)
41+
)
42+
time_entry = time_entry_use_case.update_time_entry(time_entry_id, time_entry_data)
43+
return json.dumps(time_entry.__dict__) if time_entry else b'Not Found'
44+
45+
46+
def _create_time_entry_service(db: DB):
47+
time_entry_sql = TimeEntriesSQLDao(db)
48+
return TimeEntryService(time_entry_sql)

V2/time_tracker/time_entries/_domain/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
from ._services import TimeEntryService
55
from ._use_cases import (
66
CreateTimeEntryUseCase,
7-
DeleteTimeEntryUseCase
7+
DeleteTimeEntryUseCase,
8+
UpdateTimeEntryUseCase
89
)

0 commit comments

Comments
 (0)