Skip to content

Commit 6f78268

Browse files
committed
feat: TT-425 add get time entries between two given dates
1 parent aedf3d2 commit 6f78268

File tree

16 files changed

+260
-0
lines changed

16 files changed

+260
-0
lines changed

V2/serverless.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ functions:
135135
- GET
136136
route: time-entries/latest/
137137
authLevel: anonymous
138+
139+
get_time_entries_summary:
140+
handler: time_tracker/time_entries/interface.get_time_entries_summary
141+
events:
142+
- http: true
143+
x-azure-settings:
144+
methods:
145+
- GET
146+
route: time-entries/summary/
147+
authLevel: anonymous
138148

139149
#endregion End Functions Time-Entries
140150

V2/tests/api/azure/time_entry_azure_endpoints_test.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414

1515
TIME_ENTRY_URL = "/api/time-entries/"
16+
TIME_ENTRY_SUMMARY_URL = "/api/time-entries/summary/"
1617

1718

1819
@pytest.fixture(name='insert_time_entry')
@@ -272,3 +273,90 @@ def test__get_latest_entries_azure_endpoint__returns_not_found__when_recieve_an_
272273

273274
assert response.status_code == HTTPStatus.NOT_FOUND
274275
assert response.get_body().decode("utf-8") == ResponseEnums.NOT_FOUND.value
276+
277+
278+
def test__time_entry_azure_endpoint__returns_the_summary(
279+
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity, insert_project
280+
):
281+
inserted_project = insert_project()
282+
inserted_activity = insert_activity(activity_factory(), test_db)
283+
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id,
284+
owner_id=69,
285+
start_date='11/10/2021',
286+
end_date='11/11/2021',
287+
project_id=inserted_project.id)
288+
inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__
289+
290+
req = func.HttpRequest(
291+
method='GET',
292+
body=None,
293+
url=TIME_ENTRY_SUMMARY_URL,
294+
params={'owner_id': inserted_time_entries['owner_id'],
295+
'start_date': '11/10/2021',
296+
'end_date': '11/11/2021'},
297+
)
298+
299+
response = azure_time_entries.get_time_entries_summary(req)
300+
301+
time_entries_obtained = response.get_body().decode("utf-8")
302+
assert response.status_code == HTTPStatus.OK
303+
assert json.loads(time_entries_obtained) == [inserted_time_entries]
304+
305+
306+
def test__time_entry_summary_azure_endpoint__returns_not_found_with_invalid_owner_id(
307+
test_db, insert_activity, activity_factory
308+
):
309+
310+
insert_activity(activity_factory(), test_db)
311+
312+
request_params = {
313+
"method": 'GET',
314+
"body": None,
315+
"url": TIME_ENTRY_SUMMARY_URL,
316+
"params": {"owner_id": 96},
317+
}
318+
req_owner_id = func.HttpRequest(**request_params)
319+
320+
response_owner_id = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_owner_id)
321+
322+
assert response_owner_id.status_code == HTTPStatus.NOT_FOUND
323+
assert response_owner_id.get_body().decode() == ResponseEnums.NOT_FOUND.value
324+
325+
request_params["params"] = {"owner_id": 69, "start_date": "", "end_date": "11/11/2021"}
326+
req_start_date = func.HttpRequest(**request_params)
327+
328+
response_start_date = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_start_date)
329+
330+
assert response_start_date.status_code == HTTPStatus.NOT_FOUND
331+
assert response_start_date.get_body().decode() == ResponseEnums.NOT_FOUND.value
332+
333+
334+
def test__time_entry_summary_azure_endpoint__returns_invalid_date_format_with_invalid_date_format(
335+
test_db, insert_activity, activity_factory
336+
):
337+
338+
insert_activity(activity_factory(), test_db)
339+
340+
wrong_date_format = "30/11/2021"
341+
right_date_format = "11/30/2021"
342+
343+
request_params = {
344+
"method": 'GET',
345+
"body": None,
346+
"url": TIME_ENTRY_SUMMARY_URL,
347+
"params": {"owner_id": 1, "start_date": wrong_date_format, "end_date": right_date_format},
348+
}
349+
350+
req_owner_id = func.HttpRequest(**request_params)
351+
response_owner_id = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_owner_id)
352+
353+
assert response_owner_id.status_code == HTTPStatus.NOT_FOUND
354+
assert response_owner_id.get_body().decode() == ResponseEnums.INVALID_DATE_FORMAT.value
355+
356+
request_params["params"] = {"owner_id": 1, "start_date": right_date_format, "end_date": wrong_date_format}
357+
req_start_date = func.HttpRequest(**request_params)
358+
359+
response_start_date = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_start_date)
360+
361+
assert response_start_date.status_code == HTTPStatus.NOT_FOUND
362+
assert response_start_date.get_body().decode() == ResponseEnums.INVALID_DATE_FORMAT.value

V2/tests/integration/daos/time_entries_dao_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,32 @@ def test_get_latest_entries__returns_none__when_an_owner_id_is_not_found(
184184
result = dao.get_latest_entries(Faker().pyint())
185185

186186
assert result is None
187+
188+
189+
def test_get_time_entries_summary__returns_a_list_of_time_entries__when_a_time_entry_exists_in_the_given_date_limits(
190+
create_fake_dao, time_entry_factory, insert_activity, activity_factory, test_db, insert_project
191+
):
192+
dao = create_fake_dao(test_db)
193+
inserted_project = insert_project()
194+
inserted_activity = insert_activity(activity_factory(), dao.db)
195+
time_entry_to_insert = time_entry_factory(activity_id=inserted_activity.id,
196+
project_id=inserted_project.id,
197+
start_date='11/11/2021',
198+
end_date='11/12/2021')
199+
inserted_time_entry = dao.create(time_entry_to_insert).__dict__
200+
201+
result = dao.get_time_entries_summary(inserted_time_entry["owner_id"], '1/1/2021', '12/12/2021')
202+
203+
assert result == [inserted_time_entry]
204+
205+
206+
def test_get_time_entries_summary_returns_none__when_an_owner_id_or_date_limits_are_not_found(
207+
create_fake_dao, test_db, insert_activity, activity_factory
208+
):
209+
dao = create_fake_dao(test_db)
210+
insert_activity(activity_factory(), dao.db)
211+
result = dao.get_time_entries_summary(Faker().pyint(),
212+
Faker().date(),
213+
Faker().date())
214+
215+
assert result is None

V2/tests/unit/services/time_entry_service_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,22 @@ def test__get_latest_entries__uses_the_time_entry_dao__to_get_last_entries(
8787

8888
assert expected_latest_time_entries == latest_time_entries
8989
assert time_entry_dao.get_latest_entries.called
90+
91+
92+
def test__get_time_entries_summary__uses_the_time_entry_dao__to_get_time_entries_summary(
93+
mocker,
94+
):
95+
expected_time_entries_summary = mocker.Mock()
96+
time_entry_dao = mocker.Mock(
97+
get_time_entries_summary=mocker.Mock(
98+
return_value=expected_time_entries_summary
99+
)
100+
)
101+
102+
time_entry_service = TimeEntryService(time_entry_dao)
103+
time_entries_summary = time_entry_service.get_time_entries_summary(
104+
Faker().pyint(), Faker().date(), Faker().date()
105+
)
106+
107+
assert expected_time_entries_summary == time_entries_summary
108+
assert time_entry_dao.get_time_entries_summary.called

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
@@ -88,3 +88,16 @@ def test__get_latest_entries_function__uses_the_time_entry_service__to_get_last_
8888

8989
assert time_entry_service.get_latest_entries.called
9090
assert expected_latest_time_entries == latest_time_entries
91+
92+
93+
def test__get_time_entries_summary_function__uses_the_time_entry_service__to_get_summary(
94+
mocker: MockFixture,
95+
):
96+
expected_time_entries_summary = mocker.Mock()
97+
time_entry_service = mocker.Mock(get_time_entries_summary=mocker.Mock(return_value=expected_time_entries_summary))
98+
99+
time_entry_use_case = _use_cases.GetTimeEntriesSummaryUseCase(time_entry_service)
100+
summary = time_entry_use_case.get_time_entries_summary(Faker().pyint(), Faker().date(), Faker().date())
101+
102+
assert time_entry_service.get_time_entries_summary.called
103+
assert expected_time_entries_summary == summary

V2/time_tracker/time_entries/_application/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
from ._time_entries import update_time_entry
55
from ._time_entries import get_time_entries
66
from ._time_entries import get_latest_entries
7+
from ._time_entries import get_time_entries_summary

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from ._update_time_entry import update_time_entry
66
from ._get_time_entries import get_time_entries
77
from ._get_latest_entries import get_latest_entries
8+
from ._get_time_entries_summary import get_time_entries_summary
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import json
2+
from http import HTTPStatus
3+
import datetime
4+
5+
import azure.functions as func
6+
7+
from ... import _domain
8+
from ... import _infrastructure
9+
from time_tracker._infrastructure import DB
10+
from time_tracker.utils.enums import ResponseEnums
11+
12+
13+
def get_time_entries_summary(req: func.HttpRequest) -> func.HttpResponse:
14+
database = DB()
15+
time_entry_dao = _infrastructure.TimeEntriesSQLDao(database)
16+
time_entry_service = _domain.TimeEntryService(time_entry_dao)
17+
use_case = _domain._use_cases.GetTimeEntriesSummaryUseCase(time_entry_service)
18+
19+
response_params = {
20+
"body": ResponseEnums.NOT_FOUND.value,
21+
"status_code": HTTPStatus.NOT_FOUND,
22+
"mimetype": ResponseEnums.MIME_TYPE.value,
23+
}
24+
25+
try:
26+
owner_id = req.params.get("owner_id")
27+
start_date = req.params.get("start_date")
28+
end_date = req.params.get("end_date")
29+
30+
if not owner_id or not start_date or not end_date:
31+
return func.HttpResponse(**response_params)
32+
33+
if not _validate_time_format(start_date) or not _validate_time_format(end_date):
34+
response_params["body"] = ResponseEnums.INVALID_DATE_FORMAT.value
35+
return func.HttpResponse(**response_params)
36+
37+
time_entries = use_case.get_time_entries_summary(
38+
int(owner_id), start_date, end_date
39+
)
40+
41+
if not time_entries:
42+
return func.HttpResponse(**response_params)
43+
44+
response_params["body"] = json.dumps(time_entries, default=str)
45+
response_params["status_code"] = HTTPStatus.OK
46+
47+
return func.HttpResponse(**response_params)
48+
49+
except ValueError:
50+
response_params["body"] = ResponseEnums.INVALID_ID.value
51+
response_params["status_code"] = HTTPStatus.BAD_REQUEST
52+
return func.HttpResponse(**response_params)
53+
54+
55+
def _validate_time_format(time: str, format: str = "%m/%d/%Y"):
56+
try:
57+
datetime.datetime.strptime(time, format)
58+
except ValueError:
59+
return False
60+
return True

V2/time_tracker/time_entries/_domain/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
GetTimeEntriesUseCase,
1111
GetTimeEntryUseCase,
1212
GetLastestTimeEntryUseCase,
13+
GetTimeEntriesSummaryUseCase,
1314
)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ def get_all(self) -> typing.List[TimeEntry]:
2828
@abc.abstractmethod
2929
def get_latest_entries(self, owner_id: int, limit: int) -> typing.List[TimeEntry]:
3030
pass
31+
32+
@abc.abstractmethod
33+
def get_time_entries_summary(self, owner_id: int, start_date: str, end_date: str) -> typing.List[TimeEntry]:
34+
pass

0 commit comments

Comments
 (0)