diff --git a/constants/testing_constants.py b/constants/testing_constants.py index 6c71cf656..30c781033 100644 --- a/constants/testing_constants.py +++ b/constants/testing_constants.py @@ -149,8 +149,8 @@ def MIDNIGHT_EVERY_DAY(): # we need some moments in space-time, so I guess we will use October Thursdays in New York of 2022 -_MERICA_NY = tz.gettz("New_York/America") -OCT_6_NOON_2022 = datetime(2022, 10, 6, 12, ) # Thursday -OCT_13_NOON_2022 = datetime(2022, 10, 13, 12, tzinfo=_MERICA_NY) # Thursday -OCT_20_NOON_2022 = datetime(2022, 10, 20, 12, tzinfo=_MERICA_NY) # Thursday -OCT_27_NOON_2022 = datetime(2022, 10, 27, 12, tzinfo=_MERICA_NY) # Thursday +_MERICA_NY = tz.gettz("America/New_York") +THURS_OCT_6_NOON_2022_NY = datetime(2022, 10, 6, 12, tzinfo=_MERICA_NY) # Thursday +THURS_OCT_13_NOON_2022_NY = datetime(2022, 10, 13, 12, tzinfo=_MERICA_NY) # Thursday +THURS_OCT_20_NOON_2022_NY = datetime(2022, 10, 20, 12, tzinfo=_MERICA_NY) # Thursday +THURS_OCT_27_NOON_2022_NY = datetime(2022, 10, 27, 12, tzinfo=_MERICA_NY) # Thursday diff --git a/services/celery_push_notifications.py b/services/celery_push_notifications.py index 9784b831a..529703784 100644 --- a/services/celery_push_notifications.py +++ b/services/celery_push_notifications.py @@ -26,6 +26,12 @@ UTC = gettz("UTC") +PUSH_NOTIFICATION_LOGGING_ENABLED = False + +def log(*args, **kwargs): + if PUSH_NOTIFICATION_LOGGING_ENABLED: + print(*args, **kwargs) + ################################################################E############### ############################# PUSH NOTIFICATIONS ############################### ################################################################################ @@ -36,6 +42,7 @@ def get_surveys_and_schedules(now: datetime) -> Tuple[DictOfStrToListOfStr, Dict a mapping of fcm tokens to list of survey object ids a mapping of fcm tokens to list of schedule ids a mapping of fcm tokens to patient ids """ + log(f"\nchecking if any scheduled events are in the past (before {now})") # we need to find all possible events and convert them on a per-participant-timezone basis. # The largest timezone offset is +14?, but we will do one whole day and manually filter. @@ -84,8 +91,20 @@ def get_surveys_and_schedules(now: datetime) -> Tuple[DictOfStrToListOfStr, Dict participant_tz_name: str participant_has_bad_tz: bool for scheduled_time, survey_obj_id, study_tz_name, fcm, schedule_id, patient_id, unregistered, participant_tz_name, participant_has_bad_tz in query: + log("\nchecking scheduled event:") + log("unregistered:", unregistered) + log("fcm:", fcm) + log("patient_id:", patient_id) + log("survey_obj_id:", survey_obj_id) + log("scheduled_time:", scheduled_time) + log("schedule_id:", schedule_id) + log("study_tz_name:", study_tz_name) + log("participant_tz_name:", participant_tz_name) + log("participant_has_bad_tz:", participant_has_bad_tz) + # case: this instance has an outdated FCM credential, skip it. if unregistered: + log("nope, unregistered fcm token") continue # The participant and study timezones REALLY SHOULD be valid timezone names. If they aren't @@ -102,9 +121,14 @@ def get_surveys_and_schedules(now: datetime) -> Tuple[DictOfStrToListOfStr, Dict # participant's timezone, and check if That value is in the past. canonical_time = scheduled_time.astimezone(study_tz) participant_time = canonical_time.replace(tzinfo=participant_tz) + log("canonical_time:", canonical_time) + log("participant_time:", participant_time) if participant_time > now: + log("nope, participant time is considered in the future") + log(f"{now} > {participant_time}") continue - + log("yup, participant time is considered in the past") + log(f"{now} <= {participant_time}") surveys[fcm].append(survey_obj_id) schedules[fcm].append(schedule_id) patient_ids[fcm] = patient_id diff --git a/tests/helpers.py b/tests/helpers.py index 836de17d1..5b1e6fdab 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -475,6 +475,7 @@ def generate_a_real_weekly_schedule_event_with_schedule( """ The creation of weekly events is weird, it best to use the real machinery and build some unit tests for it. At time of documenting none exist, but there are some integration tests. """ + # 0 indexes to sunday, 6 indexes to saturday. self.generate_weekly_schedule(self.default_survey, day_of_week, hour, minute) return set_next_weekly(self.default_participant, self.default_survey) diff --git a/tests/test_celery.py b/tests/test_celery.py index bdfedecb1..14ced472e 100644 --- a/tests/test_celery.py +++ b/tests/test_celery.py @@ -2,9 +2,11 @@ from unittest.mock import MagicMock, patch import time_machine +from dateutil.tz import gettz from django.utils import timezone -from constants.testing_constants import OCT_6_NOON_2022, OCT_20_NOON_2022 +from constants.testing_constants import (THURS_OCT_6_NOON_2022_NY, THURS_OCT_13_NOON_2022_NY, + THURS_OCT_20_NOON_2022_NY) from database.schedule_models import ScheduledEvent from services.celery_push_notifications import get_surveys_and_schedules from tests.common import CommonTestCase @@ -17,6 +19,7 @@ class TestCelery(CommonTestCase): class TestGetSurveysAndSchedules(TestCelery): def test_empty_db(self): + self.assertEqual(ScheduledEvent.objects.count(), 0) self.validate_no_schedules() def validate_no_schedules(self): @@ -62,19 +65,58 @@ def test_relative_failure(self): self.generate_easy_relative_schedule_event_with_schedule(timedelta(days=5)) self.validate_no_schedules() - @time_machine.travel(OCT_6_NOON_2022) + @time_machine.travel(THURS_OCT_6_NOON_2022_NY) def test_weekly_success(self): self.populate_default_fcm_token + # TODO: why is this passing # a weekly survey, on a friday, sunday is the zero-index; I hate it more than you. schedule, count_created = self.generate_a_real_weekly_schedule_event_with_schedule(5) self.assertEqual(count_created, 1) - with time_machine.travel(OCT_20_NOON_2022): + with time_machine.travel(THURS_OCT_20_NOON_2022_NY): self.validate_basics(schedule) - @time_machine.travel(OCT_6_NOON_2022) + @time_machine.travel(THURS_OCT_6_NOON_2022_NY) def test_weekly_in_future_fails(self): self.populate_default_fcm_token + # TODO: why is this passing # a weekly survey, on a friday, sunday is the zero-index; I hate it more than you. schedule, count_created = self.generate_a_real_weekly_schedule_event_with_schedule(5) self.assertEqual(count_created, 1) self.validate_no_schedules() + + @time_machine.travel(THURS_OCT_13_NOON_2022_NY) + def test_time_zones(self): + self.populate_default_fcm_token + self.default_study.update_only(timezone_name='America/New_York') # default in tests is normally UTC. + + # need to time travel to the past to get the weekly logic to produce the correct time + with time_machine.travel(THURS_OCT_6_NOON_2022_NY): + # creates a weekly survey for 2022-10-13 12:00:00-04:00 + schedule, count_created = self.generate_a_real_weekly_schedule_event_with_schedule(4, 12, 0) + self.assertEqual(count_created, 1) + + # assert schedule time is equal to 2022-10-13 12:00:00-04:00, then assert components are equal. + self.assertEqual(schedule.scheduled_time, THURS_OCT_13_NOON_2022_NY) + self.assertEqual(schedule.scheduled_time.year, 2022) + self.assertEqual(schedule.scheduled_time.month, 10) + self.assertEqual(schedule.scheduled_time.day, 13) + self.assertEqual(schedule.scheduled_time.hour, 12) + self.assertEqual(schedule.scheduled_time.minute, 0) + self.assertEqual(schedule.scheduled_time.second, 0) + self.assertEqual(schedule.scheduled_time.tzinfo, gettz("America/New_York")) + + # set default participant to pacific time, assert that no push notification is calculated. + self.default_participant.try_set_timezone('America/Los_Angeles') + self.validate_no_schedules() + + # set the time zone to mountain time, assert that no push notification is calculated. + self.default_participant.try_set_timezone('America/Denver') + self.validate_no_schedules() + + # set the time zone to central time, assert that no push notification is calculated. + self.default_participant.try_set_timezone('America/Chicago') + self.validate_no_schedules() + + # but if you set the time zone to New_York the push notification is calculated! + self.default_participant.try_set_timezone('America/New_York') + self.validate_basics(schedule) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 27a3d1b58..c1840fd7c 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -34,7 +34,7 @@ from constants.schedule_constants import EMPTY_WEEKLY_SURVEY_TIMINGS from constants.security_constants import MFA_CREATED from constants.testing_constants import (ADMIN_ROLES, ALL_TESTING_ROLES, ANDROID_CERT, BACKEND_CERT, - IOS_CERT, MIDNIGHT_EVERY_DAY, OCT_6_NOON_2022) + IOS_CERT, MIDNIGHT_EVERY_DAY, THURS_OCT_6_NOON_2022_NY) from constants.url_constants import LOGIN_REDIRECT_SAFE, urlpatterns from constants.user_constants import ALL_RESEARCHER_TYPES, IOS_API, ResearcherRole from database.data_access_models import ChunkRegistry, FileToProcess @@ -4053,7 +4053,7 @@ def test_weekly_basics2(self): output_survey = json.loads(resp.content.decode()) self.assertEqual(output_survey, reference_output) - @time_machine.travel(OCT_6_NOON_2022) + @time_machine.travel(THURS_OCT_6_NOON_2022_NY) def test_absolute_schedule_basics(self): # test for absolute surveys that they show up regardless of the day of the week they fall on, # as long as that day is within the current week. @@ -4094,7 +4094,7 @@ def test_absolute_schedule_out_of_range_past(self): output_survey = json.loads(resp.content.decode()) self.assertEqual(output_survey, self.BASIC_SURVEY_CONTENT) - @time_machine.travel(OCT_6_NOON_2022) + @time_machine.travel(THURS_OCT_6_NOON_2022_NY) def test_relative_schedule_basics(self): # this test needds to run on a thursday # test that a relative survey creates schedules that get output in survey timings at all