From e290d9e4991e87e1a2e33762d88a697fdc92eb1f Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 26 Oct 2022 11:42:50 -0300 Subject: [PATCH] test: avoid local times near midnight for test_past_swap_days_buttons() (#4642) * test: avoid local times near midnight for test_past_swap_days_buttons() * test: use timezone_options variable consistently * test: add test of timezone_not_near_midnight() method * fix: ensure that timezone_not_near_midnight() always exits --- ietf/meeting/tests_js.py | 8 +++++++- ietf/utils/tests.py | 41 ++++++++++++++++++++++++++++++++++++++++ ietf/utils/timezone.py | 23 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 2d221994a..974d27492 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -34,6 +34,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs from ietf.utils.test_utils import assert_ical_response_is_valid from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled, presence_of_element_child_by_css_selector ) +from ietf.utils.timezone import timezone_not_near_midnight if selenium_enabled(): from selenium.webdriver.common.action_chains import ActionChains @@ -392,7 +393,12 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase): def test_past_swap_days_buttons(self): """Swap days buttons should be hidden for past items""" wait = WebDriverWait(self.driver, 2) - meeting = MeetingFactory(type_id='ietf', date=datetime.datetime.today() - datetime.timedelta(days=3), days=7) + meeting = MeetingFactory( + type_id='ietf', + date=datetime.datetime.today() - datetime.timedelta(days=3), + days=7, + time_zone=timezone_not_near_midnight(), + ) room = RoomFactory(meeting=meeting) # get current time in meeting time zone diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 41679a48b..4814235f6 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -2,12 +2,15 @@ # -*- coding: utf-8 -*- +import datetime import io import json import os.path +import pytz import shutil import types +from mock import patch from pyquery import PyQuery from typing import Dict, List # pyflakes:ignore @@ -39,6 +42,7 @@ from ietf.utils.mail import send_mail_preformatted, send_mail_text, send_mail_mi from ietf.utils.test_runner import get_template_paths, set_coverage_checking from ietf.utils.test_utils import TestCase, unicontent from ietf.utils.text import parse_unicode +from ietf.utils.timezone import timezone_not_near_midnight from ietf.utils.xmldraft import XMLDraft class SendingMail(TestCase): @@ -476,3 +480,40 @@ class TestAndroidSiteManifest(TestCase): manifest = json.loads(unicontent(r)) self.assertTrue('name' in manifest) self.assertTrue('theme_color' in manifest) + + +class TimezoneTests(TestCase): + """Tests of the timezone utilities""" + @patch( + 'ietf.utils.timezone.timezone.now', + return_value=pytz.timezone('America/Chicago').localize(datetime.datetime(2022, 7, 1, 23, 15, 0)), # 23:15:00 + ) + def test_timezone_not_near_midnight(self, mock): + # give it several choices that should be rejected and one that should be accepted + with patch( + 'ietf.utils.timezone.pytz.common_timezones', + [ + 'America/Chicago', # time is 23:15, should be rejected + 'America/Lima', # time is 23:15, should be rejected + 'America/New_York', # time is 00:15, should be rejected + 'Europe/Riga', # time is 07:15, acceptable + ], + ): + # check a few times (will pass by chance < 0.1% of the time) + self.assertEqual(timezone_not_near_midnight(), 'Europe/Riga') + self.assertEqual(timezone_not_near_midnight(), 'Europe/Riga') + self.assertEqual(timezone_not_near_midnight(), 'Europe/Riga') + self.assertEqual(timezone_not_near_midnight(), 'Europe/Riga') + self.assertEqual(timezone_not_near_midnight(), 'Europe/Riga') + + # now give it no valid choice + with patch( + 'ietf.utils.timezone.pytz.common_timezones', + [ + 'America/Chicago', # time is 23:15, should be rejected + 'America/Lima', # time is 23:15, should be rejected + 'America/New_York', # time is 00:15, should be rejected + ], + ): + with self.assertRaises(RuntimeError): + timezone_not_near_midnight() diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index 723512efe..9106c3ae3 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -1,8 +1,11 @@ import pytz import email.utils import datetime +import random from django.conf import settings +from django.utils import timezone + def local_timezone_to_utc(d): """Takes a naive datetime in the local timezone and returns a @@ -37,3 +40,23 @@ def email_time_to_local_timezone(date_string): def date2datetime(date, tz=pytz.utc): return datetime.datetime(*(date.timetuple()[:6]), tzinfo=tz) + +def timezone_not_near_midnight(): + """Get the name of a random timezone where it's not close to midnight + + Avoids midnight +/- 1 hour. Raises RuntimeError if it is unable to find + a time zone satisfying this constraint. + """ + timezone_options = pytz.common_timezones + tzname = random.choice(timezone_options) + right_now = timezone.now().astimezone(pytz.timezone(tzname)) + # Avoid the remote possibility of an infinite loop (might come up + # if there is a problem with the time zone library) + tries_left = 20 + while right_now.hour < 1 or right_now.hour >= 23: + tzname = random.choice(timezone_options) + right_now = right_now.astimezone(pytz.timezone(tzname)) + tries_left -= 1 + if tries_left <= 0: + raise RuntimeError('Unable to find a time zone not near midnight') + return tzname