diff --git a/tests/time_tracker_api/time_entries/time_entries_namespace_test.py b/tests/time_tracker_api/time_entries/time_entries_namespace_test.py index cb1417dd..e4984d8b 100644 --- a/tests/time_tracker_api/time_entries/time_entries_namespace_test.py +++ b/tests/time_tracker_api/time_entries/time_entries_namespace_test.py @@ -12,6 +12,8 @@ get_current_month, current_datetime, current_datetime_str, + get_date_range_of_month, + datetime_str, ) from utils import worked_time @@ -581,35 +583,121 @@ def test_create_with_valid_uuid_format_should_return_created( @pytest.mark.parametrize( - 'url,month,year', + 'url', [ - ('/time-entries?month=4&year=2020', 4, 2020), - ('/time-entries?month=4', 4, get_current_year()), - ('/time-entries', get_current_month(), get_current_year()), + ( + '/time-entries?start_date=2020-04-01T00:00:00&end_date=2020-04-30T23:00:00' + ), + ('/time-entries?month=4&year=2020'), + ('/time-entries?month=4'), + ('/time-entries?year=2020'), + ('/time-entries'), ], ) -def test_find_all_is_called_with_generated_dates( +def test_get_all_passes_date_range_built_from_params_to_find_all( + client: FlaskClient, valid_header: dict, url: str, time_entries_dao +): + time_entries_dao.repository.find_all = Mock(return_value=[]) + + response = client.get(url, headers=valid_header) + + time_entries_dao.repository.find_all.assert_called_once() + _, kwargs = time_entries_dao.repository.find_all.call_args + assert 'date_range' in kwargs + assert 'start_date' in kwargs['date_range'] + assert 'end_date' in kwargs['date_range'] + + +@pytest.mark.parametrize( + 'url,start_date,end_date', + [ + ( + '/time-entries?month=4&year=2020', + '2020-04-01T05:00:00+00:00', + '2020-05-01T04:59:59.999999+00:00', + ), + ( + '/time-entries?start_date=2020-04-01T00:00:00&end_date=2020-04-30T23:00:00', + '2020-04-01T05:00:00+00:00', + '2020-05-01T04:00:00+00:00', + ), + ], +) +def test_get_all_passes_date_range_to_find_all_with_default_tz_offset( client: FlaskClient, - mocker: MockFixture, valid_header: dict, - owner_id: str, + time_entries_dao, url: str, - month: int, - year: int, + start_date: str, + end_date: str, ): - from time_tracker_api.time_entries.time_entries_namespace import ( - time_entries_dao, - ) + time_entries_dao.repository.find_all = Mock(return_value=[]) - dao_get_all_mock = mocker.patch.object( - time_entries_dao, 'get_all', return_value=[] - ) + response = client.get(url, headers=valid_header) - response = client.get(url, headers=valid_header, follow_redirects=True) + time_entries_dao.repository.find_all.assert_called_once() + _, kwargs = time_entries_dao.repository.find_all.call_args + assert 'date_range' in kwargs + assert 'start_date' in kwargs['date_range'] + assert 'end_date' in kwargs['date_range'] + assert kwargs['date_range']['start_date'] == start_date + assert kwargs['date_range']['end_date'] == end_date - assert HTTPStatus.OK == response.status_code - assert json.loads(response.data) is not None - dao_get_all_mock.assert_called_once() + +@pytest.mark.parametrize( + 'url,start_date,end_date', + [ + ( + '/time-entries?month=4&year=2020&timezone_offset=300', + '2020-04-01T05:00:00+00:00', + '2020-05-01T04:59:59.999999+00:00', + ), + ( + '/time-entries?start_date=2020-04-01T00:00:00&end_date=2020-04-30T23:00:00&timezone_offset=300', + '2020-04-01T05:00:00+00:00', + '2020-05-01T04:00:00+00:00', + ), + ( + '/time-entries?month=4&year=2020&timezone_offset=-120', + '2020-03-31T22:00:00+00:00', + '2020-04-30T21:59:59.999999+00:00', + ), + ( + '/time-entries?start_date=2020-04-01T00:00:00&end_date=2020-04-30T23:00:00&timezone_offset=-120', + '2020-03-31T22:00:00+00:00', + '2020-04-30T21:00:00+00:00', + ), + ( + '/time-entries?month=4&year=2020&timezone_offset=420', + '2020-04-01T07:00:00+00:00', + '2020-05-01T06:59:59.999999+00:00', + ), + ( + '/time-entries?start_date=2020-04-01T00:00:00&end_date=2020-04-30T23:00:00&timezone_offset=420', + '2020-04-01T07:00:00+00:00', + '2020-05-01T06:00:00+00:00', + ), + ], +) +def test_get_all_passes_date_range_to_find_all_with_given_tz_offset( + client: FlaskClient, + valid_header: dict, + time_entries_dao, + url: str, + start_date: str, + end_date: str, +): + time_entries_dao.repository.find_all = Mock(return_value=[]) + + response = client.get(url, headers=valid_header) + + time_entries_dao.repository.find_all.assert_called_once() + _, kwargs = time_entries_dao.repository.find_all.call_args + assert 'date_range' in kwargs + assert 'start_date' in kwargs['date_range'] + assert 'end_date' in kwargs['date_range'] + assert kwargs['date_range']['start_date'] == start_date + assert kwargs['date_range']['end_date'] == end_date def test_summary_is_called_with_date_range_from_worked_time_module( @@ -678,6 +766,6 @@ def test_paginated_sends_max_count_and_offset_on_call_to_repository( time_entries_dao.repository.find_all.assert_called_once() - args, kwargs = time_entries_dao.repository.find_all.call_args + _, kwargs = time_entries_dao.repository.find_all.call_args assert 'max_count' in kwargs and kwargs['max_count'] is not None assert 'offset' in kwargs and kwargs['offset'] is not None diff --git a/time_tracker_api/time_entries/time_entries_model.py b/time_tracker_api/time_entries/time_entries_model.py index 7b76b2ab..885495c3 100644 --- a/time_tracker_api/time_entries/time_entries_model.py +++ b/time_tracker_api/time_entries/time_entries_model.py @@ -6,7 +6,7 @@ from flask_restplus import abort from flask_restplus._http import HTTPStatus -from datetime import datetime, timedelta +from datetime import timedelta from commons.data_access_layer.cosmos_db import ( CosmosDBDao, @@ -556,30 +556,22 @@ def get_worked_time(self, args: dict): @staticmethod def handle_date_filter_args(args: dict) -> dict: - date_range = None - year = None - month = None - if 'month' and 'year' in args: - month = int(args.get("month")) - year = int(args.get("year")) - args.pop('month') - args.pop('year') - elif "start_date" and "end_date" in args: - date_range = args.copy() - if "owner_id" in date_range: - date_range.pop("owner_id") - args.pop("start_date") - args.pop("end_date") - elif 'month' in args: - month = int(args.get("month")) - year = get_current_year() - args.pop('month') + if "start_date" and "end_date" in args: + start_date = str_to_datetime(args.pop('start_date')) + end_date = str_to_datetime(args.pop('end_date')) else: - month = get_current_month() - year = get_current_year() - return ( - date_range if date_range else get_date_range_of_month(year, month) - ) + month = int(args.pop("month", get_current_month())) + year = int(args.pop("year", get_current_year())) + start_date, end_date = get_date_range_of_month(year, month) + + offset_in_minutes = int(args.pop('timezone_offset', 300)) + start_date = start_date + timedelta(minutes=offset_in_minutes) + end_date = end_date + timedelta(minutes=offset_in_minutes) + + return { + 'start_date': datetime_str(start_date), + 'end_date': datetime_str(end_date), + } def create_dao() -> TimeEntriesDao: diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index bbd58c1c..e40471da 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -216,6 +216,14 @@ location='args', ) +attributes_filter.add_argument( + 'timezone_offset', + required=False, + store_missing=False, + help="(Filter) Time zone difference, in minutes, from current locale (host system settings) to UTC.", + location='args', +) + @ns.route('') class TimeEntries(Resource): diff --git a/utils/time.py b/utils/time.py index a856ed70..37082d76 100644 --- a/utils/time.py +++ b/utils/time.py @@ -30,22 +30,18 @@ def get_current_day() -> int: return datetime.now(pytz.UTC).day -def get_date_range_of_month(year: int, month: int) -> Dict[str, str]: +def get_date_range_of_month(year: int, month: int): def get_last_day_of_month(year: int, month: int) -> int: from calendar import monthrange return monthrange(year=year, month=month)[1] - first_day_of_month = 1 - start_date = datetime( - year=year, month=month, day=first_day_of_month, tzinfo=timezone.utc - ) + start_date = datetime(year=year, month=month, day=1, tzinfo=timezone.utc) - last_day_of_month = get_last_day_of_month(year=year, month=month) end_date = datetime( year=year, month=month, - day=last_day_of_month, + day=get_last_day_of_month(year, month), hour=23, minute=59, second=59, @@ -53,10 +49,7 @@ def get_last_day_of_month(year: int, month: int) -> int: tzinfo=timezone.utc, ) - return { - 'start_date': datetime_str(start_date), - 'end_date': datetime_str(end_date), - } + return start_date, end_date def str_to_datetime(value: str) -> datetime: