Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 10 additions & 0 deletions V2/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ functions:
- GET
route: time-entries/latest/
authLevel: anonymous

get_time_entries_summary:
handler: time_tracker/time_entries/interface.get_time_entries_summary
events:
- http: true
x-azure-settings:
methods:
- GET
route: time-entries/summary/
authLevel: anonymous

#endregion End Functions Time-Entries

Expand Down
88 changes: 88 additions & 0 deletions V2/tests/api/azure/time_entry_azure_endpoints_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@


TIME_ENTRY_URL = "/api/time-entries/"
TIME_ENTRY_SUMMARY_URL = "/api/time-entries/summary/"


@pytest.fixture(name='insert_time_entry')
Expand Down Expand Up @@ -272,3 +273,90 @@ def test__get_latest_entries_azure_endpoint__returns_not_found__when_recieve_an_

assert response.status_code == HTTPStatus.NOT_FOUND
assert response.get_body().decode("utf-8") == ResponseEnums.NOT_FOUND.value


def test__time_entry_azure_endpoint__returns_the_summary(
test_db, time_entry_factory, insert_time_entry, activity_factory, insert_activity, insert_project
):
inserted_project = insert_project()
inserted_activity = insert_activity(activity_factory(), test_db)
existent_time_entries = time_entry_factory(activity_id=inserted_activity.id,
owner_id=69,
start_date='11/10/2021',
end_date='11/11/2021',
project_id=inserted_project.id)
inserted_time_entries = insert_time_entry(existent_time_entries, test_db).__dict__

req = func.HttpRequest(
method='GET',
body=None,
url=TIME_ENTRY_SUMMARY_URL,
params={'owner_id': inserted_time_entries['owner_id'],
'start_date': '11/10/2021',
'end_date': '11/11/2021'},
)

response = azure_time_entries.get_time_entries_summary(req)

time_entries_obtained = response.get_body().decode("utf-8")
assert response.status_code == HTTPStatus.OK
assert json.loads(time_entries_obtained) == [inserted_time_entries]


def test__time_entry_summary_azure_endpoint__returns_not_found_with_invalid_owner_id(
test_db, insert_activity, activity_factory
):

insert_activity(activity_factory(), test_db)

request_params = {
"method": 'GET',
"body": None,
"url": TIME_ENTRY_SUMMARY_URL,
"params": {"owner_id": 96},
}
req_owner_id = func.HttpRequest(**request_params)

response_owner_id = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_owner_id)

assert response_owner_id.status_code == HTTPStatus.NOT_FOUND
assert response_owner_id.get_body().decode() == ResponseEnums.NOT_FOUND.value

request_params["params"] = {"owner_id": 69, "start_date": "", "end_date": "11/11/2021"}
req_start_date = func.HttpRequest(**request_params)

response_start_date = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_start_date)

assert response_start_date.status_code == HTTPStatus.NOT_FOUND
assert response_start_date.get_body().decode() == ResponseEnums.NOT_FOUND.value


def test__time_entry_summary_azure_endpoint__returns_invalid_date_format_with_invalid_date_format(
test_db, insert_activity, activity_factory
):

insert_activity(activity_factory(), test_db)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of creating an activity if it is not used


wrong_date_format = "30/11/2021"
right_date_format = "11/30/2021"

request_params = {
"method": 'GET',
"body": None,
"url": TIME_ENTRY_SUMMARY_URL,
"params": {"owner_id": 1, "start_date": wrong_date_format, "end_date": right_date_format},
}

req_owner_id = func.HttpRequest(**request_params)
response_owner_id = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_owner_id)

assert response_owner_id.status_code == HTTPStatus.NOT_FOUND
assert response_owner_id.get_body().decode() == ResponseEnums.INVALID_DATE_FORMAT.value

request_params["params"] = {"owner_id": 1, "start_date": right_date_format, "end_date": wrong_date_format}
req_start_date = func.HttpRequest(**request_params)

response_start_date = azure_time_entries._get_time_entries_summary.get_time_entries_summary(req_start_date)

assert response_start_date.status_code == HTTPStatus.NOT_FOUND
assert response_start_date.get_body().decode() == ResponseEnums.INVALID_DATE_FORMAT.value
29 changes: 29 additions & 0 deletions V2/tests/integration/daos/time_entries_dao_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,32 @@ def test_get_latest_entries__returns_none__when_an_owner_id_is_not_found(
result = dao.get_latest_entries(Faker().pyint())

assert result is None


def test_get_time_entries_summary__returns_a_list_of_time_entries__when_a_time_entry_exists_in_the_given_date_limits(
create_fake_dao, time_entry_factory, insert_activity, activity_factory, test_db, insert_project
):
dao = create_fake_dao(test_db)
inserted_project = insert_project()
inserted_activity = insert_activity(activity_factory(), dao.db)
time_entry_to_insert = time_entry_factory(activity_id=inserted_activity.id,
project_id=inserted_project.id,
start_date='11/11/2021',
end_date='11/12/2021')
inserted_time_entry = dao.create(time_entry_to_insert).__dict__

result = dao.get_time_entries_summary(inserted_time_entry["owner_id"], '1/1/2021', '12/12/2021')

assert result == [inserted_time_entry]


def test_get_time_entries_summary_returns_none__when_an_owner_id_or_date_limits_are_not_found(
create_fake_dao, test_db, insert_activity, activity_factory
):
dao = create_fake_dao(test_db)
insert_activity(activity_factory(), dao.db)
result = dao.get_time_entries_summary(Faker().pyint(),
Faker().date(),
Faker().date())

assert result is None
19 changes: 19 additions & 0 deletions V2/tests/unit/services/time_entry_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,22 @@ def test__get_latest_entries__uses_the_time_entry_dao__to_get_last_entries(

assert expected_latest_time_entries == latest_time_entries
assert time_entry_dao.get_latest_entries.called


def test__get_time_entries_summary__uses_the_time_entry_dao__to_get_time_entries_summary(
mocker,
):
expected_time_entries_summary = mocker.Mock()
time_entry_dao = mocker.Mock(
get_time_entries_summary=mocker.Mock(
return_value=expected_time_entries_summary
)
)

time_entry_service = TimeEntryService(time_entry_dao)
time_entries_summary = time_entry_service.get_time_entries_summary(
Faker().pyint(), Faker().date(), Faker().date()
)

assert expected_time_entries_summary == time_entries_summary
assert time_entry_dao.get_time_entries_summary.called
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 @@ -88,3 +88,16 @@ def test__get_latest_entries_function__uses_the_time_entry_service__to_get_last_

assert time_entry_service.get_latest_entries.called
assert expected_latest_time_entries == latest_time_entries


def test__get_time_entries_summary_function__uses_the_time_entry_service__to_get_summary(
mocker: MockFixture,
):
expected_time_entries_summary = mocker.Mock()
time_entry_service = mocker.Mock(get_time_entries_summary=mocker.Mock(return_value=expected_time_entries_summary))

time_entry_use_case = _use_cases.GetTimeEntriesSummaryUseCase(time_entry_service)
summary = time_entry_use_case.get_time_entries_summary(Faker().pyint(), Faker().date(), Faker().date())

assert time_entry_service.get_time_entries_summary.called
assert expected_time_entries_summary == summary
1 change: 1 addition & 0 deletions V2/time_tracker/time_entries/_application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from ._time_entries import update_time_entry
from ._time_entries import get_time_entries
from ._time_entries import get_latest_entries
from ._time_entries import get_time_entries_summary
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from ._update_time_entry import update_time_entry
from ._get_time_entries import get_time_entries
from ._get_latest_entries import get_latest_entries
from ._get_time_entries_summary import get_time_entries_summary
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import json
from http import HTTPStatus
import datetime

import azure.functions as func

from ... import _domain
from ... import _infrastructure
from time_tracker._infrastructure import DB
from time_tracker.utils.enums import ResponseEnums


def get_time_entries_summary(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.GetTimeEntriesSummaryUseCase(time_entry_service)

response_params = {
"body": ResponseEnums.NOT_FOUND.value,
"status_code": HTTPStatus.NOT_FOUND,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should go HTTPStatus.NOT_FOUND.value if not take everything as 200, at same for others status

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not corrected yet

"mimetype": ResponseEnums.MIME_TYPE.value,
}

try:
owner_id = req.params.get("owner_id")
start_date = req.params.get("start_date")
end_date = req.params.get("end_date")

if not owner_id or not start_date or not end_date:
return func.HttpResponse(**response_params)

if not _validate_time_format(start_date) or not _validate_time_format(end_date):
response_params["body"] = ResponseEnums.INVALID_DATE_FORMAT.value
return func.HttpResponse(**response_params)

time_entries = use_case.get_time_entries_summary(
int(owner_id), start_date, end_date
)

if not time_entries:
return func.HttpResponse(**response_params)

response_params["body"] = json.dumps(time_entries, default=str)
response_params["status_code"] = HTTPStatus.OK

return func.HttpResponse(**response_params)

except ValueError:
response_params["body"] = ResponseEnums.INVALID_ID.value
response_params["status_code"] = HTTPStatus.BAD_REQUEST
return func.HttpResponse(**response_params)


def _validate_time_format(time: str, format: str = "%m/%d/%Y"):
try:
datetime.datetime.strptime(time, format)
except ValueError:
return False
return True
1 change: 1 addition & 0 deletions V2/time_tracker/time_entries/_domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
GetTimeEntriesUseCase,
GetTimeEntryUseCase,
GetLastestTimeEntryUseCase,
GetTimeEntriesSummaryUseCase,
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ def get_all(self) -> typing.List[TimeEntry]:
@abc.abstractmethod
def get_latest_entries(self, owner_id: int, limit: int) -> typing.List[TimeEntry]:
pass

@abc.abstractmethod
def get_time_entries_summary(self, owner_id: int, start_date: str, end_date: str) -> typing.List[TimeEntry]:
pass
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 @@ -24,3 +24,6 @@ def get_all(self) -> typing.List[TimeEntry]:

def get_latest_entries(self, owner_id: int, limit: int) -> typing.List[TimeEntry]:
return self.time_entry_dao.get_latest_entries(owner_id, limit)

def get_time_entries_summary(self, owner_id: int, start_date: str, end_date: str) -> typing.List[TimeEntry]:
return self.time_entry_dao.get_time_entries_summary(owner_id, start_date, end_date)
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
from ._get_time_entry_use_case import GetTimeEntriesUseCase
from ._get_time_entry_by_id_use_case import GetTimeEntryUseCase
from ._get_latest_entries_use_case import GetLastestTimeEntryUseCase
from ._get_time_entries_summary_use_case import GetTimeEntriesSummaryUseCase
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from time_tracker.time_entries._domain import TimeEntry, TimeEntryService
import typing


class GetTimeEntriesSummaryUseCase:

def __init__(self, time_entry_service: TimeEntryService):
self.time_entry_service = time_entry_service

def get_time_entries_summary(self, owner_id: int, start_date: str, end_date: str) -> typing.List[TimeEntry]:
return self.time_entry_service.get_time_entries_summary(owner_id, start_date, end_date)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import sqlalchemy
import sqlalchemy.sql as sql
# from sqlalchemy import func

import time_tracker.time_entries._domain as domain
from time_tracker._infrastructure import _db
Expand Down Expand Up @@ -104,3 +105,19 @@ def get_latest_entries(self, owner_id: int, limit: int = 20) -> typing.List[doma
time_entries_data = self.db.get_session().execute(query)
list_time_entries = [dict(entry) for entry in time_entries_data]
return list_time_entries if len(list_time_entries) > 0 else None

def get_time_entries_summary(self, owner_id: int, start_date: str, end_date: str,
format: str = "%m/%d/%Y") -> typing.List[domain.TimeEntry]:
query = (
self.time_entry.select()
.where(sqlalchemy.and_(self.time_entry.c.owner_id == owner_id,
self.time_entry.c.start_date.between(start_date, end_date),
self.time_entry.c.end_date.between(start_date, end_date),
self.time_entry.c.deleted.is_(False))
)
.order_by(self.time_entry.c.start_date.desc())
)

time_entries = self.db.get_session().execute(query)
list_time_entries = [dict(entry) for entry in time_entries]
return list_time_entries if len(list_time_entries) > 0 else None
1 change: 1 addition & 0 deletions V2/time_tracker/time_entries/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from ._application import update_time_entry
from ._application import get_time_entries
from ._application import get_latest_entries
from ._application import get_time_entries_summary
1 change: 1 addition & 0 deletions V2/time_tracker/utils/enums/response_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ class ResponseEnums(Enum):
INCORRECT_BODY = "Incorrect body"

MIME_TYPE = "application/json"
INVALID_DATE_FORMAT = "Bad time format, should use date format %m/%d/%Y"