Skip to content

Commit c8a3134

Browse files
authored
feat: TT-404 GET Time Entries (#341)
1 parent 10ec2bb commit c8a3134

File tree

17 files changed

+308
-8
lines changed

17 files changed

+308
-8
lines changed

V2/serverless.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ functions:
6363
- http: true
6464
x-azure-settings:
6565
methods:
66-
- PUT
66+
- PUT
6767
route: activities/{id}
68-
authLevel: anonymous
68+
authLevel: anonymous
6969

7070
create_activity:
7171
handler: time_tracker/activities/interface.create_activity
@@ -87,6 +87,16 @@ functions:
8787
route: time-entries/
8888
authLevel: anonymous
8989

90+
get_time_entries:
91+
handler: time_tracker/time_entries/interface.get_time_entries
92+
events:
93+
- http: true
94+
x-azure-settings:
95+
methods:
96+
- GET
97+
route: time-entries/{id:?}
98+
authLevel: anonymous
99+
90100
delete_time_entry:
91101
handler: time_tracker/time_entries/interface.delete_time_entry
92102
events:

V2/tests/api/azure/time_entry_azure_endpoints_test.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
import json
33
from faker import Faker
4+
from http import HTTPStatus
45

56
import azure.functions as func
67

@@ -39,7 +40,7 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_
3940
time_entry_json_data = json.loads(response.get_body())
4041
time_entry_body['id'] = time_entry_json_data['id']
4142

42-
assert response.status_code == 201
43+
assert response.status_code == HTTPStatus.CREATED
4344
assert time_entry_json_data == time_entry_body
4445

4546

@@ -60,7 +61,7 @@ def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_de
6061
response = azure_time_entries._delete_time_entry.delete_time_entry(req)
6162
time_entry_json_data = json.loads(response.get_body().decode("utf-8"))
6263

63-
assert response.status_code == 200
64+
assert response.status_code == HTTPStatus.OK
6465
assert time_entry_json_data['deleted'] is True
6566

6667

@@ -75,7 +76,65 @@ def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_ti
7576

7677
response = azure_time_entries._delete_time_entry.delete_time_entry(req)
7778

78-
assert response.status_code == 400
79+
assert response.status_code == HTTPStatus.BAD_REQUEST
80+
assert response.get_body() == b'Invalid Format ID'
81+
82+
83+
def test__time_entry_azure_endpoint__returns_all_time_entries(
84+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
85+
):
86+
inserted_activity = insert_activity(activity_factory(), test_db)
87+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
88+
inserted_time_entries = insert_time_entry(time_entries_to_insert, test_db).__dict__
89+
90+
req = func.HttpRequest(method="GET", body=None, url=TIME_ENTRY_URL)
91+
92+
response = azure_time_entries.get_time_entries(req)
93+
time_entries_json_data = response.get_body().decode("utf-8")
94+
time_entry_list = json.loads(time_entries_json_data)
95+
96+
assert response.status_code == HTTPStatus.OK
97+
assert time_entry_list.pop() == inserted_time_entries
98+
99+
100+
def test__time_entry_azure_endpoint__returns_an_time_entry__when_time_entry_matches_its_id(
101+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
102+
):
103+
inserted_activity = insert_activity(activity_factory(), test_db)
104+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
105+
inserted_time_entries = insert_time_entry(time_entries_to_insert, test_db).__dict__
106+
107+
req = func.HttpRequest(
108+
method="GET",
109+
body=None,
110+
url=TIME_ENTRY_URL,
111+
route_params={"id": inserted_time_entries["id"]},
112+
)
113+
114+
response = azure_time_entries.get_time_entries(req)
115+
time_entry_json_data = response.get_body().decode("utf-8")
116+
117+
assert response.status_code == HTTPStatus.OK
118+
assert time_entry_json_data == json.dumps(inserted_time_entries)
119+
120+
121+
def test__get_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id(
122+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity
123+
):
124+
inserted_activity = insert_activity(activity_factory(), test_db)
125+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
126+
insert_time_entry(time_entries_to_insert, test_db).__dict__
127+
128+
req = func.HttpRequest(
129+
method="GET",
130+
body=None,
131+
url=TIME_ENTRY_URL,
132+
route_params={"id": "invalid id"},
133+
)
134+
135+
response = azure_time_entries.get_time_entries(req)
136+
137+
assert response.status_code == HTTPStatus.BAD_REQUEST
79138
assert response.get_body() == b'Invalid Format ID'
80139

81140

V2/tests/integration/daos/time_entries_dao_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import pytest
2+
import typing
3+
24
from faker import Faker
35

46
import time_tracker.time_entries._domain as domain
@@ -98,3 +100,56 @@ def test_update__returns_none__when_doesnt_found_one_time_entry_to_update(
98100
time_entry = dao.update(0, inserted_time_entries)
99101

100102
assert time_entry is None
103+
104+
105+
def test__get_all__returns_a_list_of_time_entries_dto_objects__when_one_or_more_time_entries_are_found_in_sql_database(
106+
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
107+
):
108+
109+
dao = create_fake_dao(test_db)
110+
inserted_activity = insert_activity(activity_factory(), dao.db)
111+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
112+
inserted_time_entries = [dao.create(time_entries_to_insert)]
113+
114+
time_entry = dao.get_all()
115+
116+
assert isinstance(time_entry, typing.List)
117+
assert time_entry == inserted_time_entries
118+
119+
120+
def test__get_all__returns_an_empty_list__when_doesnt_found_any_time_entries_in_sql_database(
121+
test_db, create_fake_dao, insert_activity, activity_factory
122+
):
123+
dao = create_fake_dao(test_db)
124+
insert_activity(activity_factory(), dao.db)
125+
126+
time_entry = dao.get_all()
127+
assert time_entry == []
128+
129+
130+
def test__get_by_id__returns_a_time_entry_dto__when_found_one_time_entry_that_match_id_with_sql_database(
131+
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
132+
):
133+
dao = create_fake_dao(test_db)
134+
inserted_activity = insert_activity(activity_factory(), dao.db)
135+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
136+
inserted_time_entries = dao.create(time_entries_to_insert)
137+
138+
time_entry = dao.get_by_id(time_entries_to_insert.id)
139+
140+
assert isinstance(time_entry, domain.TimeEntry)
141+
assert time_entry.id == inserted_time_entries.id
142+
assert time_entry == inserted_time_entries
143+
144+
145+
def test__get_by_id__returns_none__when_no_time_entry_matches_by_id(
146+
test_db, create_fake_dao, time_entry_factory, insert_activity, activity_factory
147+
):
148+
dao = create_fake_dao(test_db)
149+
inserted_activity = insert_activity(activity_factory(), dao.db)
150+
time_entries_to_insert = time_entry_factory(activity_id=inserted_activity.id)
151+
dao.create(time_entries_to_insert)
152+
153+
time_entry = dao.get_by_id(Faker().pyint())
154+
155+
assert time_entry is None

V2/tests/unit/services/time_entry_service_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,29 @@ def test__update_time_entry__uses_the_time_entry_dao__to_update_one_time_entry(
4646

4747
assert time_entry_dao.update.called
4848
assert expected_time_entry == updated_time_entry
49+
50+
51+
def test__get_all__uses_the_time_entry_dao__to_retrieve_time_entries(mocker):
52+
expected_time_entries = mocker.Mock()
53+
time_entry_dao = mocker.Mock(
54+
get_all=mocker.Mock(return_value=expected_time_entries)
55+
)
56+
time_activity_service = TimeEntryService(time_entry_dao)
57+
58+
actual_activities = time_activity_service.get_all()
59+
60+
assert time_entry_dao.get_all.called
61+
assert expected_time_entries == actual_activities
62+
63+
64+
def test__get_by_id__uses_the_time_entry_dao__to_retrieve_one_time_entry(mocker):
65+
expected_time_entry = mocker.Mock()
66+
time_entry_dao = mocker.Mock(
67+
get_by_id=mocker.Mock(return_value=expected_time_entry)
68+
)
69+
time_entry_service = TimeEntryService(time_entry_dao)
70+
71+
actual_time_entry = time_entry_service.get_by_id(Faker().uuid4())
72+
73+
assert time_entry_dao.get_by_id.called
74+
assert expected_time_entry == actual_time_entry

V2/tests/unit/use_cases/time_entries_use_case_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from time_tracker.time_entries._domain import _use_cases
55

6+
fake = Faker()
7+
68

79
def test__create_time_entry_function__uses_the_time_entries_service__to_create_time_entry(
810
mocker: MockFixture, time_entry_factory
@@ -43,3 +45,33 @@ def test__update_time_entries_function__uses_the_time_entry_service__to_update_a
4345

4446
assert time_entry_service.update.called
4547
assert expected_time_entry == updated_time_entry
48+
49+
50+
def test__get_all_time_entries_function__using_the_use_case_get_time_entries__to_get_all_time_entries(
51+
mocker: MockFixture,
52+
):
53+
expected_time_entries = mocker.Mock()
54+
time_entry_service = mocker.Mock(
55+
get_all=mocker.Mock(return_value=expected_time_entries)
56+
)
57+
58+
time_entries_use_case = _use_cases.GetTimeEntriesUseCase(time_entry_service)
59+
actual_time_entries = time_entries_use_case.get_time_entries()
60+
61+
assert time_entry_service.get_all.called
62+
assert expected_time_entries == actual_time_entries
63+
64+
65+
def test__get_time_entry_by_id_function__uses_the_time_entry_service__to_retrieve_time_entry(
66+
mocker: MockFixture,
67+
):
68+
expected_time_entries = mocker.Mock()
69+
time_entry_service = mocker.Mock(
70+
get_by_id=mocker.Mock(return_value=expected_time_entries)
71+
)
72+
73+
time_entry_use_case = _use_cases.GetTimeEntryUseCase(time_entry_service)
74+
actual_time_entry = time_entry_use_case.get_time_entry_by_id(fake.uuid4())
75+
76+
assert time_entry_service.get_by_id.called
77+
assert expected_time_entries == actual_time_entry

V2/time_tracker/time_entries/_application/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from ._time_entries import create_time_entry
33
from ._time_entries import delete_time_entry
44
from ._time_entries import update_time_entry
5+
from ._time_entries import get_time_entries

V2/time_tracker/time_entries/_application/_time_entries/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from ._create_time_entry import create_time_entry
33
from ._delete_time_entry import delete_time_entry
44
from ._update_time_entry import update_time_entry
5+
from ._get_time_entries import get_time_entries
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import json
2+
from http import HTTPStatus
3+
4+
import azure.functions as func
5+
6+
from time_tracker.time_entries._infrastructure import TimeEntriesSQLDao
7+
from time_tracker.time_entries._domain import TimeEntryService, _use_cases
8+
from time_tracker._infrastructure import DB
9+
10+
11+
NOT_FOUND = b'Not Found'
12+
INVALID_FORMAT_ID = b'Invalid Format ID'
13+
14+
15+
def get_time_entries(req: func.HttpRequest) -> func.HttpResponse:
16+
17+
time_entry_id = req.route_params.get('id')
18+
status_code = HTTPStatus.OK
19+
20+
if time_entry_id:
21+
try:
22+
response = _get_by_id(int(time_entry_id))
23+
if response == NOT_FOUND:
24+
status_code = HTTPStatus.NOT_FOUND
25+
except ValueError:
26+
response = INVALID_FORMAT_ID
27+
status_code = HTTPStatus.BAD_REQUEST
28+
else:
29+
response = _get_all()
30+
31+
return func.HttpResponse(
32+
body=response, status_code=status_code, mimetype="application/json"
33+
)
34+
35+
36+
def _get_by_id(id: int) -> str:
37+
database = DB()
38+
time_entry_use_case = _use_cases.GetTimeEntryUseCase(
39+
_create_time_entry_service(database)
40+
)
41+
time_entry = time_entry_use_case.get_time_entry_by_id(id)
42+
43+
return json.dumps(time_entry.__dict__) if time_entry else NOT_FOUND
44+
45+
46+
def _get_all() -> str:
47+
database = DB()
48+
time_entries_use_case = _use_cases.GetTimeEntriesUseCase(
49+
_create_time_entry_service(database)
50+
)
51+
return json.dumps(
52+
[
53+
time_entry.__dict__
54+
for time_entry in time_entries_use_case.get_time_entries()
55+
]
56+
)
57+
58+
59+
def _create_time_entry_service(db: DB):
60+
time_entry_sql = TimeEntriesSQLDao(db)
61+
return TimeEntryService(time_entry_sql)

V2/time_tracker/time_entries/_domain/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
CreateTimeEntryUseCase,
77
DeleteTimeEntryUseCase,
88
UpdateTimeEntryUseCase,
9-
)
9+
GetTimeEntriesUseCase,
10+
GetTimeEntryUseCase
11+
)

V2/time_tracker/time_entries/_domain/_persistence_contracts/_time_entries_dao.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
import typing
23

34
from time_tracker.time_entries._domain import TimeEntry
45

@@ -15,3 +16,10 @@ def delete(self, id: int) -> TimeEntry:
1516
@abc.abstractmethod
1617
def update(self, id: int, new_time_entry: dict) -> TimeEntry:
1718
pass
19+
20+
def get_by_id(self, id: int) -> TimeEntry:
21+
pass
22+
23+
@abc.abstractmethod
24+
def get_all(self) -> typing.List[TimeEntry]:
25+
pass

0 commit comments

Comments
 (0)