Skip to content

Commit 48f6411

Browse files
authored
feat: TT-402 put v2 time entries (#347)
* feat: TT-402 add put time entries * refactor: TT-402 rebase post time entry * test: TT-402 add integration test of UPDATE * refactor: TT-402 delete time_entires_sql_dao_test * refactor: TT-404 revert changes _db.py * refactor: TT-402 Andres's resolve comments * fix: TT-402 refactor azure update endpoint * fix: TT-402 change name test
1 parent 6dd8505 commit 48f6411

File tree

19 files changed

+247
-16
lines changed

19 files changed

+247
-16
lines changed

V2/serverless.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ functions:
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: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
import json
3+
from faker import Faker
34

45
import azure.functions as func
56

@@ -25,7 +26,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_
2526
test_db, time_entry_factory, activity_factory, insert_activity
2627
):
2728
inserted_activity = insert_activity(activity_factory(), test_db)
28-
time_entry_body = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]").__dict__
29+
time_entry_body = time_entry_factory(activity_id=inserted_activity.id).__dict__
2930

3031
body = json.dumps(time_entry_body).encode("utf-8")
3132
req = func.HttpRequest(
@@ -46,7 +47,7 @@ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_de
4647
test_db, time_entry_factory, insert_time_entry, insert_activity, activity_factory,
4748
):
4849
inserted_activity = insert_activity(activity_factory(), test_db).__dict__
49-
time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]")
50+
time_entry_body = time_entry_factory(activity_id=inserted_activity["id"])
5051
inserted_time_entry = insert_time_entry(time_entry_body, test_db)
5152

5253
req = func.HttpRequest(
@@ -76,3 +77,75 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti
7677

7778
assert response.status_code == 400
7879
assert response.get_body() == b'Invalid Format ID'
80+
81+
82+
def test__update_time_entry_azure_endpoint__returns_an_time_entry__when_found_an_time_entry_to_update(
83+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
84+
):
85+
inserted_activity = insert_activity(activity_factory(), test_db)
86+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id)
87+
inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__
88+
89+
time_entry_body = {"description": Faker().sentence()}
90+
91+
req = func.HttpRequest(
92+
method='PUT',
93+
body=json.dumps(time_entry_body).encode("utf-8"),
94+
url=TIME_ENTRY_URL,
95+
route_params={"id": inserted_time_entries["id"]},
96+
)
97+
98+
response = azure_time_entries._update_time_entry.update_time_entry(req)
99+
activitiy_json_data = response.get_body().decode("utf-8")
100+
inserted_time_entries.update(time_entry_body)
101+
102+
assert response.status_code == 200
103+
assert activitiy_json_data == json.dumps(inserted_time_entries)
104+
105+
106+
def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_format_id():
107+
time_entry_body = {"description": Faker().sentence()}
108+
109+
req = func.HttpRequest(
110+
method="PUT",
111+
body=json.dumps(time_entry_body).encode("utf-8"),
112+
url=TIME_ENTRY_URL,
113+
route_params={"id": Faker().sentence()},
114+
)
115+
116+
response = azure_time_entries._update_time_entry.update_time_entry(req)
117+
118+
assert response.status_code == 400
119+
assert response.get_body() == b'Invalid Format ID'
120+
121+
122+
def test__update_time_entries_azure_endpoint__returns_a_status_code_404__when_not_found_an_time_entry_to_update():
123+
time_entry_body = {"description": Faker().sentence()}
124+
125+
req = func.HttpRequest(
126+
method="PUT",
127+
body=json.dumps(time_entry_body).encode("utf-8"),
128+
url=TIME_ENTRY_URL,
129+
route_params={"id": Faker().pyint()},
130+
)
131+
132+
response = azure_time_entries._update_time_entry.update_time_entry(req)
133+
134+
assert response.status_code == 404
135+
assert response.get_body() == b'Not found'
136+
137+
138+
def test__update_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_body():
139+
140+
time_entry_body = Faker().pydict(5, True, str)
141+
req = func.HttpRequest(
142+
method="PUT",
143+
body=json.dumps(time_entry_body).encode("utf-8"),
144+
url=TIME_ENTRY_URL,
145+
route_params={"id": Faker().pyint()},
146+
)
147+
148+
response = azure_time_entries._update_time_entry.update_time_entry(req)
149+
150+
assert response.status_code == 400
151+
assert response.get_body() == b'Incorrect time entry body'

V2/tests/fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def _make_time_entry(
4444
description=Faker().sentence(),
4545
activity_id=Faker().random_int(),
4646
uri=Faker().domain_name(),
47-
technologies=["jira", "git"],
47+
technologies=str(Faker().pylist()),
4848
end_date=str(Faker().date_time()),
4949
deleted=False,
5050
timezone_offset="300",

V2/tests/integration/daos/time_entries_dao_test.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def test__time_entry__returns_a_time_entry_dto__when_saves_correctly_with_sql_da
2929
dao = create_fake_dao(test_db)
3030
inserted_activity = insert_activity(activity_factory(), dao.db)
3131

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

3434
inserted_time_entry = dao.create(time_entry_to_insert)
3535

@@ -41,7 +41,7 @@ def test__time_entry__returns_None__when_not_saves_correctly(
4141
time_entry_factory, create_fake_dao, test_db
4242
):
4343
dao = create_fake_dao(test_db)
44-
time_entry_to_insert = time_entry_factory(activity_id=1203, technologies="[jira,sql]")
44+
time_entry_to_insert = time_entry_factory(activity_id=1203)
4545

4646
inserted_time_entry = dao.create(time_entry_to_insert)
4747

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

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

7171
assert result is None
72+
73+
74+
def test_update__returns_an_time_entry_dto__when_found_one_time_entry_to_update(
75+
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
76+
):
77+
dao = create_fake_dao(test_db)
78+
inserted_activity = insert_activity(activity_factory(), dao.db)
79+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id)
80+
inserted_time_entries = dao.create(existent_time_entries).__dict__
81+
time_entry_id = inserted_time_entries["id"]
82+
inserted_time_entries.update({"description": "description updated"})
83+
84+
time_entry = dao.update(time_entry_id=time_entry_id, time_entry_data=inserted_time_entries)
85+
86+
assert time_entry.id == time_entry_id
87+
assert time_entry.description == inserted_time_entries.get("description")
88+
89+
90+
def test_update__returns_none__when_doesnt_found_one_time_entry_to_update(
91+
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
92+
):
93+
dao = create_fake_dao(test_db)
94+
inserted_activity = insert_activity(activity_factory(), dao.db)
95+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id)
96+
inserted_time_entries = dao.create(existent_time_entries).__dict__
97+
98+
time_entry = dao.update(0, inserted_time_entries)
99+
100+
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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,16 @@ def test__delete_time_entry_function__uses_the_time_entry_service__to_delete_tim
3030

3131
assert time_entry_service.delete.called
3232
assert expected_time_entry == deleted_time_entry
33+
34+
35+
def test__update_time_entries_function__uses_the_time_entry_service__to_update_an_time_entry(
36+
mocker: MockFixture,
37+
):
38+
expected_time_entry = mocker.Mock()
39+
time_entry_service = mocker.Mock(update=mocker.Mock(return_value=expected_time_entry))
40+
41+
time_entry_use_case = _use_cases.UpdateTimeEntryUseCase(time_entry_service)
42+
updated_time_entry = time_entry_use_case.update_time_entry(Faker().uuid4(), Faker().pydict())
43+
44+
assert time_entry_service.update.called
45+
assert expected_time_entry == updated_time_entry
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# flake8: noqa
2-
from ._time_entries import create_time_entry, delete_time_entry
2+
from ._time_entries import create_time_entry
3+
from ._time_entries import delete_time_entry
4+
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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import dataclasses
2+
import json
3+
4+
import azure.functions as func
5+
6+
from ... import _domain
7+
from ... import _infrastructure
8+
from time_tracker._infrastructure import DB
9+
10+
11+
def update_time_entry(req: func.HttpRequest) -> func.HttpResponse:
12+
database = DB()
13+
time_entry_dao = _infrastructure.TimeEntriesSQLDao(database)
14+
time_entry_service = _domain.TimeEntryService(time_entry_dao)
15+
use_case = _domain._use_cases.UpdateTimeEntryUseCase(time_entry_service)
16+
17+
try:
18+
time_entry_id = int(req.route_params.get("id"))
19+
time_entry_data = req.get_json()
20+
21+
if not _validate_time_entry(time_entry_data):
22+
status_code = 400
23+
response = b"Incorrect time entry body"
24+
else:
25+
updated_time_entry = use_case.update_time_entry(time_entry_id, time_entry_data)
26+
status_code, response = [
27+
404, b"Not found"
28+
] if not updated_time_entry else [200, json.dumps(updated_time_entry.__dict__)]
29+
30+
return func.HttpResponse(
31+
body=response,
32+
status_code=status_code,
33+
mimetype="application/json",
34+
)
35+
36+
except ValueError:
37+
return func.HttpResponse(
38+
body=b"Invalid Format ID",
39+
status_code=400,
40+
mimetype="application/json"
41+
)
42+
43+
44+
def _validate_time_entry(time_entry_data: dict) -> bool:
45+
time_entry_keys = [field.name for field in dataclasses.fields(_domain.TimeEntry)]
46+
return all(key in time_entry_keys for key in time_entry_data.keys())

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)