diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index 37b975bcc6..0db573c5b9 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -1,10 +1,12 @@ # Copyright (c) 2019, Frappe Technologies and Contributors # License: MIT. See LICENSE +from datetime import timedelta + import frappe from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs from frappe.tests.utils import FrappeTestCase from frappe.utils import get_datetime -from frappe.utils.data import add_to_date +from frappe.utils.data import add_to_date, now_datetime class TestScheduledJobType(FrappeTestCase): @@ -82,3 +84,18 @@ class TestScheduledJobType(FrappeTestCase): self.assertTrue(job.is_event_due(get_datetime("2019-01-01 00:15:01"))) self.assertFalse(job.is_event_due(get_datetime("2019-01-01 00:05:06"))) self.assertFalse(job.is_event_due(get_datetime("2019-01-01 00:14:59"))) + + def test_cold_start(self): + now = now_datetime() + just_before_12_am = now.replace(hour=11, minute=59, second=30) + just_after_12_am = now.replace(hour=0, minute=0, second=30) + timedelta(days=1) + + job = frappe.new_doc("Scheduled Job Type") + job.frequency = "Daily" + job.set_user_and_timestamp() + + with self.freeze_time(just_before_12_am): + self.assertFalse(job.is_event_due()) + + with self.freeze_time(just_after_12_am): + self.assertTrue(job.is_event_due()) diff --git a/frappe/tests/test_test_utils.py b/frappe/tests/test_test_utils.py index 4e5c424ca6..de004ac028 100644 --- a/frappe/tests/test_test_utils.py +++ b/frappe/tests/test_test_utils.py @@ -1,5 +1,8 @@ +from datetime import timedelta + import frappe from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils.data import now_datetime class TestTestUtils(FrappeTestCase): @@ -27,6 +30,13 @@ class TestTestUtils(FrappeTestCase): restored_settings = frappe.get_system_settings("logout_on_password_reset") self.assertEqual(current_setting, restored_settings) + def test_time_freezing(self): + now = now_datetime() + + tomorrow = now + timedelta(days=1) + with self.freeze_time(tomorrow): + self.assertEqual(now_datetime(), tomorrow) + def tearDownModule(): """assertions for ensuring tests didn't leave state behind""" diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index 77fc740d33..ee34fb0a5e 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -7,9 +7,12 @@ from collections.abc import Sequence from contextlib import contextmanager from unittest.mock import patch +import pytz + import frappe from frappe.model.base_document import BaseDocument, get_controller from frappe.utils import cint +from frappe.utils.data import convert_utc_to_timezone, get_datetime, get_system_timezone datetime_like_types = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta) @@ -154,6 +157,17 @@ class FrappeTestCase(unittest.TestCase): frappe.init(old_site, force=True) frappe.connect() + @contextmanager + def freeze_time(self, time_to_freeze, *args, **kwargs): + from freezegun import freeze_time + + # Freeze time expects UTC or tzaware objects. We have neither, so convert to UTC. + timezone = pytz.timezone(get_system_timezone()) + fake_time_with_tz = timezone.localize(get_datetime(time_to_freeze)).astimezone(pytz.utc) + + with freeze_time(fake_time_with_tz, *args, **kwargs): + yield + class MockedRequestTestCase(FrappeTestCase): def setUp(self): diff --git a/pyproject.toml b/pyproject.toml index b0521bbf0a..6a829aa885 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,3 +106,4 @@ unittest-xml-reporting = "~=3.2.0" watchdog = "~=3.0.0" hypothesis = "~=6.77.0" responses = "==0.23.1" +freezegun = "~=1.2.2"