diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 1051c0e31..6ff1f1e76 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -814,28 +814,31 @@ def get_specific_days_stats( elif days is not None: date_range = list(generate_date_range(start_date, days=days)) elif end_date is not None: - date_range = list(generate_date_range(start_date, end_date)) + date_range = list(generate_date_range(start_date, end_date=end_date)) else: raise ValueError("Either 'days' or 'end_date' must be set.") - # Group data by local date - grouped_data = {date: [] for date in date_range} + # Ensure date_range is in correct format + grouped_data = {day.strftime("%Y-%m-%d"): [] for day in date_range} + for item in data: if not isinstance(item.timestamp, datetime): continue - local_datetime = item.timestamp.replace(tzinfo=pytz.utc).astimezone(user_timezone) - local_date = local_datetime.date() - if local_date in grouped_data: - grouped_data[local_date].append(item) + + local_datetime = item.timestamp.astimezone(user_timezone) + local_date_str = local_datetime.strftime("%Y-%m-%d") + + if local_date_str in grouped_data: + grouped_data[local_date_str].append(item) # Build final stats, optionally including total_notifications stats = {} for day in date_range: formatted_day = day.strftime("%Y-%m-%d") - day_data = grouped_data[day] + day_data = grouped_data.get(formatted_day, []) # Avoid KeyError total_for_day = None - if total_notifications and day in total_notifications: - total_for_day = total_notifications[day] + if total_notifications and formatted_day in total_notifications: + total_for_day = total_notifications[formatted_day] stats[formatted_day] = statistics.format_statistics( day_data, diff --git a/tests/app/dao/test_services_get_specific_days.py b/tests/app/dao/test_services_get_specific_days.py index eeb9039e2..df75ca955 100644 --- a/tests/app/dao/test_services_get_specific_days.py +++ b/tests/app/dao/test_services_get_specific_days.py @@ -17,192 +17,144 @@ def generate_expected_output(requested_days, requested_sms_days): StatisticsType.REQUESTED: 1 if day in requested_sms_days else 0, StatisticsType.DELIVERED: 0, StatisticsType.FAILURE: 0, + StatisticsType.PENDING: 0, }, TemplateType.EMAIL: { StatisticsType.REQUESTED: 0, StatisticsType.DELIVERED: 0, StatisticsType.FAILURE: 0, + StatisticsType.PENDING: 0, }, } return output - def create_mock_notification(notification_type, status, timestamp, count=1): return Mock( notification_type=notification_type, status=status, timestamp=timestamp, - count=count, + count=count ) - test_cases = [ - # Case with normal dates that don't carry over + # Case with normal dates that don't carry over to next day when converted to UTC ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 1, 20, 18, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 1, 20, 18, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, "America/New_York", - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]) ), - - # Case where EST is saved as next day in UTC + # Case where EST is saved as next day in UTC, it needs to be converted back to EST after retrieval ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 30, 4, 30, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 30, 4, 30, 0, tzinfo=pytz.utc))], datetime(2025, 1, 29, tzinfo=pytz.utc), 2, "America/New_York", - generate_expected_output(["2025-01-29", "2025-01-30"], ["2025-01-29"]), + generate_expected_output(["2025-01-29", "2025-01-30"], ["2025-01-29"]) ), - - # UTC query + # Case where UTC is queryed ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 10, 15, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 10, 15, 0, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, "UTC", - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-29"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-29"]) ), - - # Central time test + # Central time test ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 3, 0, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 3, 0, 0, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, "America/Chicago", - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]) ), - # Mountain time test ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 5, 0, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 5, 0, 0, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, "America/Denver", - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]) ), - # Pacific time test ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 7, 30, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 7, 30, 0, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, "America/Los_Angeles", - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-28"]) ), - - # No timezone provided => defaults to UTC + # Case where no timezone is provided, ensuring it defaults to UTC ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 29, 10, 15, 0, tzinfo=pytz.utc), - )], + [create_mock_notification(TemplateType.SMS, StatisticsType.REQUESTED, datetime(2025, 1, 29, 10, 15, 0, tzinfo=pytz.utc))], datetime(2025, 1, 28, tzinfo=pytz.utc), 2, None, - generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-29"]), + generate_expected_output(["2025-01-28", "2025-01-29"], ["2025-01-29"]) ), - - # DST Spring Forward + # Daylights savings time Spring Forward: March 10, 2024 at 2 AM EST => jumps to 3 AM EST ( [create_mock_notification( TemplateType.SMS, StatisticsType.REQUESTED, - datetime(2024, 3, 10, 6, 30, 0, tzinfo=pytz.utc), + datetime(2024, 3, 10, 6, 30, 0, tzinfo=pytz.utc) # UTC 6:30 AM => 1:30 AM EST (before DST kicks in) )], datetime(2024, 3, 9, tzinfo=pytz.utc), 2, "America/New_York", - generate_expected_output(["2024-03-09", "2024-03-10"], ["2024-03-10"]), + generate_expected_output(["2024-03-09", "2024-03-10"], ["2024-03-10"]) # Should map to March 10 EST ), - - # DST Fall Back + # Fall Back: November 3, 2024 at 2 AM EDT => goes back to 1 AM EST ( [create_mock_notification( TemplateType.SMS, StatisticsType.REQUESTED, - datetime(2024, 11, 3, 5, 30, 0, tzinfo=pytz.utc), + datetime(2024, 11, 3, 5, 30, 0, tzinfo=pytz.utc) # UTC 5:30 AM => 1:30 AM EST after DST ends )], datetime(2024, 11, 2, tzinfo=pytz.utc), 2, "America/New_York", - generate_expected_output(["2024-11-02", "2024-11-03"], ["2024-11-03"]), + generate_expected_output(["2024-11-02", "2024-11-03"], ["2024-11-03"]) ), - - # No notifications + # There are no notifications ( [], datetime(2025, 1, 29, tzinfo=pytz.utc), 2, "UTC", - generate_expected_output(["2025-01-29", "2025-01-30"], []), + generate_expected_output(["2025-01-29", "2025-01-30"], []) ), - - # Midnight edge case => 12:00 AM UTC => 7:00 PM previous day EST + # Midnight edge case ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 10, 0, 0, 0, tzinfo=pytz.utc), - )], - datetime(2025, 1, 9, tzinfo=pytz.utc), - 2, - "America/New_York", - generate_expected_output(["2025-01-09", "2025-01-10"], ["2025-01-09"]), + [create_mock_notification( + TemplateType.SMS, + StatisticsType.REQUESTED, + datetime(2025, 1, 10, 0, 0, 0, tzinfo=pytz.utc) # 12:00 AM UTC => 7:00 PM Jan 9 EST + )], + datetime(2025, 1, 9, tzinfo=pytz.utc), + 2, + "America/New_York", + generate_expected_output(["2025-01-09", "2025-01-10"], ["2025-01-09"]) # Goes back to Jan 9 EST ), - - # Large query (30 days) + # Large query testing large amounts of data ( - [create_mock_notification( - TemplateType.SMS, - StatisticsType.REQUESTED, - datetime(2025, 1, 15, 12, 0, 0, tzinfo=pytz.utc), - )], - datetime(2025, 1, 1, tzinfo=pytz.utc), - 30, - "America/New_York", - generate_expected_output( - [f"2025-01-{str(day).zfill(2)}" for day in range(1, 31)], - ["2025-01-15"], - ), - ), + [create_mock_notification( + TemplateType.SMS, + StatisticsType.REQUESTED, + datetime(2025, 1, 15, 12, 0, 0, tzinfo=pytz.utc) + )], + datetime(2025, 1, 1, tzinfo=pytz.utc), + 30, + "America/New_York", + generate_expected_output( + [f"2025-01-{str(day).zfill(2)}" for day in range(1, 31)], + ["2025-01-15"] + ) +) ] - -@pytest.mark.parametrize( - "mocked_notifications, start_date, days, timezone, expected_output", - test_cases, -) +@pytest.mark.parametrize("mocked_notifications, start_date, days, timezone, expected_output", test_cases) def test_get_specific_days(mocked_notifications, start_date, days, timezone, expected_output): - results = get_specific_days_stats( - mocked_notifications, - start_date, - days, - timezone=timezone, - ) + results = get_specific_days_stats(mocked_notifications, start_date, days, timezone=timezone) assert results == expected_output