Ejemplo n.º 1
0
 def test_resolver_send(self, mock_ace):
     current_day, offset, target_day, _ = self._get_dates()
     with patch.object(self.task, 'apply_async') as mock_apply_async:
         self.task.enqueue(self.site_config.site, current_day, offset)
     mock_apply_async.assert_any_call(
         (self.site_config.site.id, serialize(target_day), offset, 0, None),
         retry=False,
     )
     mock_apply_async.assert_any_call(
         (self.site_config.site.id, serialize(target_day), offset, self.task.num_bins - 1, None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
Ejemplo n.º 2
0
 def test_resolver_send(self, mock_ace):
     current_day, offset, target_day, _ = self._get_dates()
     with patch.object(self.task, 'apply_async') as mock_apply_async:
         self.task.enqueue(self.site_config.site, current_day, offset)
     mock_apply_async.assert_any_call(
         (self.site_config.site.id, serialize(target_day), offset, 0, None),
         retry=False,
     )
     mock_apply_async.assert_any_call(
         (self.site_config.site.id, serialize(target_day), offset, self.task.num_bins - 1, None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
 def test_resolver_send(self, mock_schedule_bin, mock_ace):
     current_day = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC)
     nudge.ScheduleStartResolver(self.site_config.site, current_day).send(-3)
     test_day = current_day + datetime.timedelta(days=-3)
     self.assertFalse(mock_schedule_bin.called)
     mock_schedule_bin.apply_async.assert_any_call(
         (self.site_config.site.id, serialize(test_day), -3, 0, [], True, None),
         retry=False,
     )
     mock_schedule_bin.apply_async.assert_any_call(
         (self.site_config.site.id, serialize(test_day), -3, tasks.RECURRING_NUDGE_NUM_BINS - 1, [], True, None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
Ejemplo n.º 4
0
 def test_resolver_send(self, mock_schedule_bin, mock_ace):
     current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC)
     nudge.ScheduleStartResolver(self.site_config.site, current_time).send(-3)
     test_time = current_time + datetime.timedelta(days=-3)
     self.assertFalse(mock_schedule_bin.called)
     mock_schedule_bin.apply_async.assert_any_call(
         (self.site_config.site.id, serialize(test_time), -3, 0, [], True, None),
         retry=False,
     )
     mock_schedule_bin.apply_async.assert_any_call(
         (self.site_config.site.id, serialize(test_time), -3, tasks.RECURRING_NUDGE_NUM_BINS - 1, [], True, None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
 def test_resolver_send(self, mock_schedule_hour, mock_ace):
     current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC)
     nudge.ScheduleStartResolver(self.site_config.site, current_time).send(3)
     test_time = current_time - datetime.timedelta(days=3)
     self.assertFalse(mock_schedule_hour.called)
     mock_schedule_hour.apply_async.assert_any_call(
         (self.site_config.site.id, 3, serialize(test_time), [], True, None),
         retry=False,
     )
     mock_schedule_hour.apply_async.assert_any_call(
         (self.site_config.site.id, 3, serialize(test_time + datetime.timedelta(hours=23)), [], True, None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
Ejemplo n.º 6
0
def update_schedules_on_course_start_changed(sender, updated_course_overview,
                                             previous_start_date, **kwargs):
    """
    Updates all course schedules if course hasn't started yet and
    the updated start date is still in the future.
    """
    upgrade_deadline = _calculate_upgrade_deadline(
        updated_course_overview.id,
        content_availability_date=updated_course_overview.start,
    )
    update_course_schedules.apply_async(kwargs=dict(
        course_id=unicode(updated_course_overview.id),
        new_start_date_str=date.serialize(updated_course_overview.start),
        new_upgrade_deadline_str=date.serialize(upgrade_deadline),
    ), )
Ejemplo n.º 7
0
def update_schedules_on_course_start_changed(sender, updated_course_overview,
                                             previous_start_date, **kwargs):  # pylint: disable=unused-argument
    """
    Updates all course schedules start and upgrade_deadline dates based off of
    the new course overview start date.
    """
    upgrade_deadline = _calculate_upgrade_deadline(
        updated_course_overview.id,
        content_availability_date=updated_course_overview.start,
    )
    update_course_schedules.apply_async(kwargs=dict(
        course_id=six.text_type(updated_course_overview.id),
        new_start_date_str=date.serialize(updated_course_overview.start),
        new_upgrade_deadline_str=date.serialize(upgrade_deadline),
    ), )
Ejemplo n.º 8
0
 def create_thread_and_comments(cls):
     cls.thread = {
         'id': cls.discussion_id,
         'course_id': six.text_type(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id',
     }
     cls.comment = {
         'id': 'comment',
         'body': 'comment-body',
         'created_at': date.serialize(ONE_HOUR_AGO),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.comment2 = {
         'id': 'comment2',
         'body': 'comment2-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username
     }
     cls.subcomment = {
         'id': 'subcomment',
         'body': 'subcomment-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': cls.comment['id'],
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.thread['children'] = [cls.comment, cls.comment2]
     cls.comment['child_count'] = 1
     cls.thread2 = {
         'id': cls.discussion_id,
         'course_id': six.text_type(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id-2',
     }
Ejemplo n.º 9
0
 def create_thread_and_comments(cls):  # lint-amnesty, pylint: disable=missing-function-docstring
     cls.thread = {
         'id': cls.discussion_id,
         'course_id': str(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id',
     }
     cls.comment = {
         'id': 'comment',
         'body': 'comment-body',
         'created_at': date.serialize(ONE_HOUR_AGO),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.comment2 = {
         'id': 'comment2',
         'body': 'comment2-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username
     }
     cls.subcomment = {
         'id': 'subcomment',
         'body': 'subcomment-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': cls.comment['id'],
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.thread['children'] = [cls.comment, cls.comment2]
     cls.comment['child_count'] = 1
     cls.thread2 = {
         'id': cls.discussion_id,
         'course_id': str(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id-2',
     }
Ejemplo n.º 10
0
 def create_thread_and_comments(cls):
     cls.thread = {
         'id': cls.discussion_id,
         'course_id': unicode(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id',
     }
     cls.comment = {
         'id': 'comment',
         'body': 'comment-body',
         'created_at': date.serialize(ONE_HOUR_AGO),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.comment2 = {
         'id': 'comment2',
         'body': 'comment2-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': None,
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username
     }
     cls.subcomment = {
         'id': 'subcomment',
         'body': 'subcomment-body',
         'created_at': date.serialize(NOW),
         'thread_id': cls.thread['id'],
         'parent_id': cls.comment['id'],
         'user_id': cls.comment_author.id,
         'username': cls.comment_author.username,
     }
     cls.thread['children'] = [cls.comment, cls.comment2]
     cls.comment['child_count'] = 1
     cls.thread2 = {
         'id': cls.discussion_id,
         'course_id': unicode(cls.course.id),
         'created_at': date.serialize(TWO_HOURS_AGO),
         'title': 'thread-title',
         'user_id': cls.thread_author.id,
         'username': cls.thread_author.username,
         'commentable_id': 'thread-commentable-id-2',
     }
Ejemplo n.º 11
0
    def enqueue(cls, site, current_date, day_offset, override_recipient_email=None):
        current_date = _get_datetime_beginning_of_day(current_date)

        for course_key, config in CourseDurationLimitConfig.all_current_course_configs().items():
            if not config['enabled'][0]:
                cls.log_info(u'Course duration limits disabled for course_key %s, skipping', course_key)
                continue

            # enqueue_enabled, _ = config[cls.enqueue_config_var]
            # TODO: Switch over to a model where enqueing is based in CourseDurationLimitConfig
            enqueue_enabled = waffle.switch_is_active('course_duration_limits.enqueue_enabled')

            if not enqueue_enabled:
                cls.log_info(u'Message queuing disabled for course_key %s', course_key)
                continue

            target_date = current_date + datetime.timedelta(days=day_offset)
            for bin_num in range(cls.num_bins):
                task_args = (
                    site.id,
                    unicode(course_key),
                    serialize(target_date),
                    day_offset,
                    bin_num,
                    override_recipient_email,
                )
                cls().apply_async(
                    task_args,
                    retry=False,
                )
Ejemplo n.º 12
0
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs):
    """
    Updates all course schedules if course hasn't started yet and
    the updated start date is still in the future.
    """
    upgrade_deadline = _calculate_upgrade_deadline(
        updated_course_overview.id,
        content_availability_date=updated_course_overview.start,
    )
    update_course_schedules.apply_async(
        kwargs=dict(
            course_id=unicode(updated_course_overview.id),
            new_start_date_str=date.serialize(updated_course_overview.start),
            new_upgrade_deadline_str=date.serialize(upgrade_deadline),
        ),
    )
Ejemplo n.º 13
0
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs):
    """
    Updates all course schedules start and upgrade_deadline dates based off of
    the new course overview start date.
    """
    upgrade_deadline = _calculate_upgrade_deadline(
        updated_course_overview.id,
        content_availability_date=updated_course_overview.start,
    )
    update_course_schedules.apply_async(
        kwargs=dict(
            course_id=unicode(updated_course_overview.id),
            new_start_date_str=date.serialize(updated_course_overview.start),
            new_upgrade_deadline_str=date.serialize(upgrade_deadline),
        ),
    )
    def test_resolver_send(self, mock_schedule_bin, mock_ace):
        current_day = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC)
        test_day = current_day + datetime.timedelta(days=2)
        ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 3, 15, 34, 30, tzinfo=pytz.UTC))

        reminder.UpgradeReminderResolver(self.site_config.site, current_day).send(2)
        self.assertFalse(mock_schedule_bin.called)
        mock_schedule_bin.apply_async.assert_any_call(
            (self.site_config.site.id, serialize(test_day), 2, 0, [], True, None),
            retry=False,
        )
        mock_schedule_bin.apply_async.assert_any_call(
            (self.site_config.site.id, serialize(test_day), 2, tasks.UPGRADE_REMINDER_NUM_BINS - 1, [], True, None),
            retry=False,
        )
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 15
0
    def send(self, day, override_recipient_email=None):
        """
        Send a message to all users whose schedule started at ``self.current_date`` - ``day``.
        """
        if not ScheduleConfig.current(self.site).enqueue_recurring_nudge:
            return

        try:
            site_config = SiteConfiguration.objects.get(site_id=self.site.id)
            org_list = site_config.values.get('course_org_filter', None)
            exclude_orgs = False
            if not org_list:
                not_orgs = set()
                for other_site_config in SiteConfiguration.objects.all():
                    not_orgs.update(
                        other_site_config.values.get('course_org_filter', []))
                org_list = list(not_orgs)
                exclude_orgs = True
            elif not isinstance(org_list, list):
                org_list = [org_list]
        except SiteConfiguration.DoesNotExist:
            org_list = None
            exclude_orgs = False

        target_date = self.current_date - datetime.timedelta(days=day)
        for hour in range(24):
            target_hour = target_date + datetime.timedelta(hours=hour)
            recurring_nudge_schedule_hour.apply_async(
                (self.site.id, day, serialize(target_hour), org_list,
                 exclude_orgs, override_recipient_email),
                retry=False,
            )
    def test_filter_out_verified_schedules(self):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()

        user = UserFactory.create()
        schedules = [
            self._schedule_factory(
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(i)),
                enrollment__mode=CourseMode.VERIFIED if i in (0, 3) else CourseMode.AUDIT,
            )
            for i in range(5)
        ]

        sent_messages = []
        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1])

            self.task.apply(kwargs=dict(
                site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
                bin_num=self._calculate_bin_for_user(user),
            ))

            messages = [Message.from_string(m) for m in sent_messages]
            self.assertEqual(len(messages), 1)
            message = messages[0]
            self.assertItemsEqual(
                message.context['course_ids'],
                [str(schedules[i].enrollment.course.id) for i in (1, 2, 4)]
            )
Ejemplo n.º 17
0
    def send(self, day_offset, override_recipient_email=None):
        if not self.is_enqueue_enabled():
            self.log_debug('Message queuing disabled for site %s',
                           self.site.domain)
            return

        exclude_orgs, org_list = self.get_course_org_filter()

        target_date = self.current_date + datetime.timedelta(days=day_offset)
        self.log_debug('Target date = %s', target_date.isoformat())
        for bin in range(self.num_bins):
            task_args = (
                self.site.id,
                serialize(target_date),
                day_offset,
                bin,
                org_list,
                exclude_orgs,
                override_recipient_email,
            )
            self.log_debug('Launching task with args = %r', task_args)
            self.async_send_task.apply_async(
                task_args,
                retry=False,
            )
Ejemplo n.º 18
0
    def test_with_course_data(self, mock_get_current_site):
        self.highlights_patcher.stop()
        self.highlights_patcher = None
        mock_get_current_site.return_value = self.site_config.site

        course = CourseFactory(highlights_enabled_for_messaging=True,
                               self_paced=True)
        with self.store.bulk_operations(course.id):
            ItemFactory.create(parent=course,
                               category='chapter',
                               highlights=[u'highlights'])

        enrollment = CourseEnrollmentFactory(course_id=course.id,
                                             user=self.user,
                                             mode=u'audit')
        self.assertEqual(enrollment.schedule.get_experience_type(),
                         ScheduleExperience.EXPERIENCES.course_updates)

        _, offset, target_day, _ = self._get_dates(
            offset=self.expected_offsets[0])
        enrollment.schedule.start = target_day
        enrollment.schedule.save()

        with patch.object(tasks, 'ace') as mock_ace:
            self.task().apply(kwargs=dict(
                site_id=self.site_config.site.id,
                target_day_str=serialize(target_day),
                day_offset=offset,
                bin_num=self._calculate_bin_for_user(enrollment.user),
            ))

            self.assertTrue(mock_ace.send.called)
Ejemplo n.º 19
0
    def test_no_course_overview(self, mock_schedule_send):

        schedule = ScheduleFactory.create(upgrade_deadline=datetime.datetime(
            2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), )
        schedule.enrollment.course_id = CourseKey.from_string(
            'edX/toy/Not_2012_Fall')
        schedule.enrollment.save()

        test_time = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC)
        test_time_str = serialize(test_time)
        for b in range(tasks.UPGRADE_REMINDER_NUM_BINS):
            with self.assertNumQueries(NUM_QUERIES_NO_MATCHING_SCHEDULES,
                                       table_blacklist=WAFFLE_TABLES):
                tasks.upgrade_reminder_schedule_bin(
                    self.site_config.site.id,
                    target_day_str=test_time_str,
                    day_offset=2,
                    bin_num=b,
                    org_list=[schedule.enrollment.course.org],
                )

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
Ejemplo n.º 20
0
    def enqueue(cls, site, current_date, day_offset, override_recipient_email=None):
        current_date = _get_datetime_beginning_of_day(current_date)

        for course_key, config in CourseDurationLimitConfig.all_current_course_configs().items():
            if not config['enabled'][0]:
                cls.log_info(u'Course duration limits disabled for course_key %s, skipping', course_key)
                continue

            # enqueue_enabled, _ = config[cls.enqueue_config_var]
            # TODO: Switch over to a model where enqueing is based in CourseDurationLimitConfig
            enqueue_enabled = waffle.switch_is_active('course_duration_limits.enqueue_enabled')

            if not enqueue_enabled:
                cls.log_info(u'Message queuing disabled for course_key %s', course_key)
                continue

            target_date = current_date + datetime.timedelta(days=day_offset)
            task_args = (
                site.id,
                six.text_type(course_key),
                serialize(target_date),
                day_offset,
                override_recipient_email,
            )
            cls().apply_async(
                task_args,
                retry=False,
            )
Ejemplo n.º 21
0
    def test_upsell(self, enable_config, testcase):
        DynamicUpgradeDeadlineConfiguration.objects.create(
            enabled=enable_config)

        current_day, offset, target_day, _ = self._get_dates()
        upgrade_deadline = None
        if testcase.set_deadline:
            upgrade_deadline = current_day + datetime.timedelta(
                days=testcase.deadline_offset)

        schedule = self._schedule_factory(upgrade_deadline=upgrade_deadline)

        sent_messages = []
        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(
                args[1])
            self.task.apply(kwargs=dict(
                site_id=self.site_config.site.id,
                target_day_str=serialize(target_day),
                day_offset=offset,
                bin_num=self._calculate_bin_for_user(schedule.enrollment.user),
            ))
        self.assertEqual(len(sent_messages), 1)

        found_upsell = self._contains_upsell(sent_messages[0])
        expect_upsell = enable_config and testcase.expect_upsell
        self.assertEqual(found_upsell, expect_upsell)
    def test_site_config(self, org_list, exclude_orgs, expected_message_count, mock_schedule_send, mock_ace):
        filtered_org = 'filtered_org'
        unfiltered_org = 'unfiltered_org'
        site1 = SiteFactory.create(domain='foo1.bar', name='foo1.bar')
        limited_config = SiteConfigurationFactory.create(values={'course_org_filter': [filtered_org]}, site=site1)
        site2 = SiteFactory.create(domain='foo2.bar', name='foo2.bar')
        unlimited_config = SiteConfigurationFactory.create(values={'course_org_filter': []}, site=site2)

        for config in (limited_config, unlimited_config):
            ScheduleConfigFactory.create(site=config.site)

        ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC),
            enrollment__course__org=filtered_org,
        )
        for _ in range(2):
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC),
                enrollment__course__org=unfiltered_org,
            )

        test_time_str = serialize(datetime.datetime(2017, 8, 2, 17, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                limited_config.site.id, 3, test_time_str, org_list=org_list, exclude_orgs=exclude_orgs,
            )

        self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 23
0
    def test_multiple_enrollments(self, mock_schedule_send, mock_ace):
        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(
                upgrade_deadline=datetime.datetime(2017,
                                                   8,
                                                   3,
                                                   19,
                                                   44,
                                                   30,
                                                   tzinfo=pytz.UTC),
                enrollment__user=user,
                enrollment__course__id=CourseLocator(
                    'edX', 'toy', 'Course{}'.format(course_num)))
            for course_num in (1, 2, 3)
        ]

        test_time = datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC)
        test_time_str = serialize(test_time)
        with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES,
                                   table_blacklist=WAFFLE_TABLES):
            tasks.upgrade_reminder_schedule_bin(
                self.site_config.site.id,
                target_day_str=test_time_str,
                day_offset=2,
                bin_num=user.id % tasks.UPGRADE_REMINDER_NUM_BINS,
                org_list=[schedules[0].enrollment.course.org],
            )
        self.assertEqual(mock_schedule_send.apply_async.call_count, 3)
        self.assertFalse(mock_ace.send.called)
    def test_filter_out_verified_schedules(self):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()

        user = UserFactory.create()
        schedules = [
            self._schedule_factory(
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy',
                                                     'Course{}'.format(i)),
                enrollment__mode=CourseMode.VERIFIED
                if i in (0, 3) else CourseMode.AUDIT,
            ) for i in range(5)
        ]

        sent_messages = []
        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(
                args[1])

            self.task().apply(kwargs=dict(
                site_id=self.site_config.site.id,
                target_day_str=serialize(target_day),
                day_offset=offset,
                bin_num=self._calculate_bin_for_user(user),
            ))

            messages = [Message.from_string(m) for m in sent_messages]
            self.assertEqual(len(messages), 1)
            message = messages[0]
            six.assertCountEqual(
                self, message.context['course_ids'],
                [str(schedules[i].enrollment.course.id) for i in (1, 2, 4)])
Ejemplo n.º 25
0
    def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace):
        schedules = [
            ScheduleFactory.create(
                upgrade_deadline=datetime.datetime(2017,
                                                   8,
                                                   3,
                                                   18,
                                                   44,
                                                   30,
                                                   tzinfo=pytz.UTC),
                enrollment__user=UserFactory.create(),
                enrollment__course__id=CourseLocator('edX', 'toy', 'Bin'))
            for _ in range(schedule_count)
        ]

        test_time = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC)
        test_time_str = serialize(test_time)
        for b in range(tasks.UPGRADE_REMINDER_NUM_BINS):
            # waffle flag takes an extra query before it is cached
            with self.assertNumQueries(3 if b == 0 else 2):
                tasks.upgrade_reminder_schedule_bin(
                    self.site_config.site.id,
                    target_day_str=test_time_str,
                    day_offset=2,
                    bin_num=b,
                    org_list=[schedules[0].enrollment.course.org],
                )
        self.assertEqual(mock_schedule_send.apply_async.call_count,
                         schedule_count)
        self.assertFalse(mock_ace.send.called)
    def test_no_upsell_button_when_DUDConfiguration_is_off(self):
        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False)

        user = UserFactory.create()
        course_id = CourseLocator('edX', 'toy', 'Course1')

        first_day_of_schedule = datetime.datetime.now(pytz.UTC)
        target_day = first_day_of_schedule
        target_hour_as_string = serialize(target_day)
        nudge_day = 3

        schedule = ScheduleFactory.create(start=first_day_of_schedule,
                                          enrollment__user=user,
                                          enrollment__course__id=course_id)
        schedule.enrollment.course.self_paced = True
        schedule.enrollment.course.save()

        bin_task_parameters = [
            target_hour_as_string,
            nudge_day,
            user,
            schedule.enrollment.course.org
        ]
        sent_messages = self._stub_sender_and_collect_sent_messages(bin_task=tasks.recurring_nudge_schedule_bin,
                                                                    stubbed_send_task=patch.object(tasks, '_recurring_nudge_schedule_send'),
                                                                    bin_task_params=bin_task_parameters)

        self.assertEqual(len(sent_messages), 1)

        message_attributes = sent_messages[0][1]
        self.assertFalse(self._contains_upsell_attribute(message_attributes))
Ejemplo n.º 27
0
    def test_site_config(self, this_org_list, other_org_list, expected_message_count, mock_ace):
        filtered_org = 'filtered_org'
        unfiltered_org = 'unfiltered_org'
        this_config = SiteConfigurationFactory.create(values={'course_org_filter': this_org_list})
        other_config = SiteConfigurationFactory.create(values={'course_org_filter': other_org_list})

        for config in (this_config, other_config):
            ScheduleConfigFactory.create(site=config.site)

        user1 = UserFactory.create(id=self.task.num_bins)
        user2 = UserFactory.create(id=self.task.num_bins * 2)
        current_day, offset, target_day, upgrade_deadline = self._get_dates()

        self._schedule_factory(
            enrollment__course__org=filtered_org,
            enrollment__user=user1,
        )
        self._schedule_factory(
            enrollment__course__org=unfiltered_org,
            enrollment__user=user1,
        )
        self._schedule_factory(
            enrollment__course__org=unfiltered_org,
            enrollment__user=user2,
        )

        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            self.task().apply(kwargs=dict(
                site_id=this_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=0
            ))

        self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 28
0
    def test_multiple_target_schedules(self, mock_ace):
        user = UserFactory.create()
        current_day, offset, target_day, upgrade_deadline = self._get_dates()
        num_courses = 3
        for course_index in range(num_courses):
            self._schedule_factory(
                enrollment__user=user,
                enrollment__course__id=CourseKey.from_string(
                    'edX/toy/course{}'.format(course_index)))

        # 2 queries per course, one for the course opt out and one for the course modes
        # one query for course modes for the first schedule if we aren't checking the deadline for each course
        additional_course_queries = (
            num_courses *
            2) - 1 if self.queries_deadline_for_each_course else 1
        expected_query_count = NUM_QUERIES_FIRST_MATCH + additional_course_queries
        with self.assertNumQueries(expected_query_count,
                                   table_blacklist=WAFFLE_TABLES):
            with patch.object(self.task,
                              'async_send_task') as mock_schedule_send:
                self.task().apply(kwargs=dict(
                    site_id=self.site_config.site.id,
                    target_day_str=serialize(target_day),
                    day_offset=offset,
                    bin_num=self._calculate_bin_for_user(user),
                ))

        expected_call_count = 1 if self.consolidates_emails_for_learner else num_courses
        self.assertEqual(mock_schedule_send.apply_async.call_count,
                         expected_call_count)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 29
0
    def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace):
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC),
                enrollment__course__id=CourseLocator('edX', 'toy', 'Bin')
            ) for i in range(schedule_count)
        ]

        bins_in_use = frozenset((s.enrollment.user.id % tasks.RECURRING_NUDGE_NUM_BINS) for s in schedules)

        test_time = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC)
        test_time_str = serialize(test_time)
        for b in range(tasks.RECURRING_NUDGE_NUM_BINS):
            expected_queries = NUM_QUERIES_NO_MATCHING_SCHEDULES
            if b in bins_in_use:
                # to fetch course modes for valid schedules
                expected_queries += NUM_COURSE_MODES_QUERIES

            with self.assertNumQueries(expected_queries, table_blacklist=WAFFLE_TABLES):
                tasks.recurring_nudge_schedule_bin(
                    self.site_config.site.id, target_day_str=test_time_str, day_offset=-3, bin_num=b,
                    org_list=[schedules[0].enrollment.course.org],
                )
        self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count)
        self.assertFalse(mock_ace.send.called)
    def send(self, day, override_recipient_email=None):
        """
        Send a message to all users whose schedule started at ``self.current_date`` - ``day``.
        """
        if not ScheduleConfig.current(self.site).enqueue_recurring_nudge:
            return

        try:
            site_config = SiteConfiguration.objects.get(site_id=self.site.id)
            org_list = site_config.values.get('course_org_filter', None)
            exclude_orgs = False
            if not org_list:
                not_orgs = set()
                for other_site_config in SiteConfiguration.objects.all():
                    not_orgs.update(other_site_config.values.get('course_org_filter', []))
                org_list = list(not_orgs)
                exclude_orgs = True
            elif not isinstance(org_list, list):
                org_list = [org_list]
        except SiteConfiguration.DoesNotExist:
            org_list = None
            exclude_orgs = False

        target_date = self.current_date - datetime.timedelta(days=day)
        for hour in range(24):
            target_hour = target_date + datetime.timedelta(hours=hour)
            recurring_nudge_schedule_hour.apply_async(
                (self.site.id, day, serialize(target_hour), org_list, exclude_orgs, override_recipient_email),
                retry=False,
            )
Ejemplo n.º 31
0
    def enqueue(cls,
                site,
                current_date,
                day_offset,
                override_recipient_email=None):
        current_date = resolvers._get_datetime_beginning_of_day(current_date)

        if not cls.is_enqueue_enabled(site):
            cls.log_debug('Message queuing disabled for site %s', site.domain)
            return

        target_date = current_date + datetime.timedelta(days=day_offset)
        cls.log_debug('Target date = %s', target_date.isoformat())
        for bin in range(cls.num_bins):
            task_args = (
                site.id,
                serialize(target_date),
                day_offset,
                bin,
                override_recipient_email,
            )
            cls.log_debug('Launching task with args = %r', task_args)
            cls.apply_async(
                task_args,
                retry=False,
            )
Ejemplo n.º 32
0
    def test_no_course_overview(self):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()
        # Don't use CourseEnrollmentFactory since it creates a course overview
        enrollment = CourseEnrollment.objects.create(
            course_id=CourseKey.from_string('edX/toy/Not_2012_Fall'),
            user=UserFactory.create(),
        )
        self._schedule_factory(enrollment=enrollment)

        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            for bin_num in range(self.task().num_bins):
                self.task().apply(kwargs=dict(
                    site_id=self.site_config.site.id,
                    target_day_str=serialize(target_day),
                    day_offset=offset,
                    bin_num=bin_num,
                ))

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
Ejemplo n.º 33
0
    def enqueue(cls,
                site,
                current_date,
                day_offset,
                override_recipient_email=None):  # lint-amnesty, pylint: disable=missing-function-docstring
        set_code_owner_attribute_from_module(__name__)
        target_datetime = (current_date - datetime.timedelta(days=day_offset))

        if not cls.is_enqueue_enabled(site):
            cls.log_info(u'Message queuing disabled for site %s', site.domain)
            return

        cls.log_info(u'Target date = %s', target_datetime.date().isoformat())
        for course_key in CourseOverview.get_all_course_keys():
            task_args = (
                site.id,
                serialize(
                    target_datetime
                ),  # Need to leave as a datetime for serialization purposes here
                str(course_key
                    ),  # Needs to be a string for celery to properly process
                override_recipient_email,
            )
            cls.log_info(u'Launching task with args = %r', task_args)
            cls.task_instance.apply_async(
                task_args,
                retry=False,
            )
Ejemplo n.º 34
0
    def enqueue(cls,
                site,
                current_date,
                day_offset,
                override_recipient_email=None):  # lint-amnesty, pylint: disable=missing-function-docstring
        set_code_owner_attribute_from_module(__name__)
        current_date = resolvers._get_datetime_beginning_of_day(current_date)  # lint-amnesty, pylint: disable=protected-access

        if not cls.is_enqueue_enabled(site):
            cls.log_info(u'Message queuing disabled for site %s', site.domain)
            return

        target_date = current_date + datetime.timedelta(days=day_offset)
        cls.log_info(u'Target date = %s', target_date.isoformat())
        for bin in range(cls.num_bins):  # lint-amnesty, pylint: disable=redefined-builtin
            task_args = (
                site.id,
                serialize(target_date),
                day_offset,
                bin,
                override_recipient_email,
            )
            cls.log_info(u'Launching task with args = %r', task_args)
            cls.task_instance.apply_async(
                task_args,
                retry=False,
            )
Ejemplo n.º 35
0
    def test_no_upsell_button_when_DUDConfiguration_is_off(self):
        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False)

        user = UserFactory.create()
        course_id = CourseLocator('edX', 'toy', 'Course1')

        first_day_of_schedule = datetime.datetime.now(pytz.UTC)
        target_day = first_day_of_schedule
        target_hour_as_string = serialize(target_day)
        nudge_day = 3

        schedule = ScheduleFactory.create(start=first_day_of_schedule,
                                          enrollment__user=user,
                                          enrollment__course__id=course_id)
        schedule.enrollment.course.self_paced = True
        schedule.enrollment.course.save()

        bin_task_parameters = [
            target_hour_as_string,
            nudge_day,
            user,
            schedule.enrollment.course.org
        ]
        sent_messages = self._stub_sender_and_collect_sent_messages(bin_task=tasks.recurring_nudge_schedule_bin,
                                                                    stubbed_send_task=patch.object(tasks, '_recurring_nudge_schedule_send'),
                                                                    bin_task_params=bin_task_parameters)

        self.assertEqual(len(sent_messages), 1)

        message_attributes = sent_messages[0][1]
        self.assertFalse(self._contains_upsell_attribute(message_attributes))
Ejemplo n.º 36
0
    def test_no_course_overview(self):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()
        # Don't use CourseEnrollmentFactory since it creates a course overview
        enrollment = CourseEnrollment.objects.create(
            course_id=CourseKey.from_string('edX/toy/Not_2012_Fall'),
            user=UserFactory.create(),
        )
        self._schedule_factory(enrollment=enrollment)

        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            for bin_num in range(self.task().num_bins):
                self.task().apply(kwargs=dict(
                    site_id=self.site_config.site.id,
                    target_day_str=serialize(target_day),
                    day_offset=offset,
                    bin_num=bin_num,
                ))

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
    def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace):
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC),
                enrollment__course__id=CourseLocator('edX', 'toy', 'Bin')
            ) for i in range(schedule_count)
        ]

        bins_in_use = frozenset((s.enrollment.user.id % tasks.RECURRING_NUDGE_NUM_BINS) for s in schedules)

        test_datetime = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC)
        test_datetime_str = serialize(test_datetime)
        for b in range(tasks.RECURRING_NUDGE_NUM_BINS):
            expected_queries = NUM_QUERIES_NO_MATCHING_SCHEDULES
            if b in bins_in_use:
                # to fetch course modes for valid schedules
                expected_queries += NUM_COURSE_MODES_QUERIES

            with self.assertNumQueries(expected_queries, table_blacklist=WAFFLE_TABLES):
                tasks.recurring_nudge_schedule_bin(
                    self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=b,
                    org_list=[schedules[0].enrollment.course.org],
                )
        self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 38
0
    def test_no_course_overview(self, mock_schedule_send):

        schedule = ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC),
        )
        schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall')
        schedule.enrollment.save()

        test_time = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC)
        test_time_str = serialize(test_time)
        for b in range(tasks.RECURRING_NUDGE_NUM_BINS):
            # waffle flag takes an extra query before it is cached
            with self.assertNumQueries(3 if b == 0 else 2):
                tasks.recurring_nudge_schedule_bin(
                    self.site_config.site.id, target_day_str=test_time_str, day_offset=-3, bin_num=b,
                    org_list=[schedule.enrollment.course.org],
                )

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
    def test_multiple_enrollments(self, test_hour, messages_sent,
                                  mock_schedule_send, mock_ace):
        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(start=datetime.datetime(2017,
                                                           8,
                                                           1,
                                                           hour,
                                                           44,
                                                           30,
                                                           tzinfo=pytz.UTC),
                                   enrollment__user=user,
                                   enrollment__course__id=CourseLocator(
                                       'edX', 'toy', 'Hour{}'.format(hour)))
            for hour in (19, 20, 21)
        ]

        test_time_str = serialize(
            datetime.datetime(2017, 8, 1, test_hour, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                self.site_config.site.id,
                3,
                test_time_str,
                [schedules[0].enrollment.course.org],
            )
        self.assertEqual(mock_schedule_send.apply_async.call_count,
                         messages_sent)
        self.assertFalse(mock_ace.send.called)
    def test_no_course_overview(self, mock_schedule_send):

        schedule = ScheduleFactory.create(start=datetime.datetime(
            2017, 8, 1, 20, 34, 30, tzinfo=pytz.UTC), )
        schedule.enrollment.course_id = CourseKey.from_string(
            'edX/toy/Not_2012_Fall')
        schedule.enrollment.save()

        test_time_str = serialize(
            datetime.datetime(2017, 8, 1, 20, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                self.site_config.site.id,
                3,
                test_time_str,
                [schedule.enrollment.course.org],
            )

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
Ejemplo n.º 41
0
    def test_course_end(self, has_course_ended):
        user1 = UserFactory.create(id=self.tested_task.num_bins)
        current_day, offset, target_day = self._get_dates()

        schedule = ScheduleFactory.create(
            start=target_day,
            upgrade_deadline=target_day,
            enrollment__course__self_paced=True,
            enrollment__user=user1,
        )

        schedule.enrollment.course.start = current_day - datetime.timedelta(
            days=30)
        end_date_offset = -2 if has_course_ended else 2
        schedule.enrollment.course.end = current_day + datetime.timedelta(
            days=end_date_offset)
        schedule.enrollment.course.save()

        with patch.object(self.tested_task,
                          'async_send_task') as mock_schedule_send:
            self.tested_task.apply(kwargs=dict(
                site_id=self.site_config.site.id,
                target_day_str=serialize(target_day),
                day_offset=offset,
                bin_num=0,
            ))

        if has_course_ended:
            self.assertFalse(mock_schedule_send.apply_async.called)
        else:
            self.assertTrue(mock_schedule_send.apply_async.called)
Ejemplo n.º 42
0
    def test_multiple_enrollments(self, mock_ace):
        user = UserFactory.create()
        current_day, offset, target_day = self._get_dates()
        num_courses = 3
        for course_index in range(num_courses):
            ScheduleFactory.create(
                start=target_day,
                upgrade_deadline=target_day,
                enrollment__course__self_paced=True,
                enrollment__user=user,
                enrollment__course__id=CourseKey.from_string(
                    'edX/toy/course{}'.format(course_index)))

        course_queries = num_courses if self.has_course_queries else 0
        expected_query_count = NUM_QUERIES_FIRST_MATCH + course_queries + NUM_QUERIES_NO_ORG_LIST
        with self.assertNumQueries(expected_query_count,
                                   table_blacklist=WAFFLE_TABLES):
            with patch.object(self.tested_task,
                              'async_send_task') as mock_schedule_send:
                self.tested_task.apply(kwargs=dict(
                    site_id=self.site_config.site.id,
                    target_day_str=serialize(target_day),
                    day_offset=offset,
                    bin_num=self._calculate_bin_for_user(user),
                ))
            self.assertEqual(mock_schedule_send.apply_async.call_count, 1)
            self.assertFalse(mock_ace.send.called)
 def test_resolver_send(self, mock_schedule_hour, mock_ace):
     current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC)
     nudge.ScheduleStartResolver(self.site_config.site,
                                 current_time).send(3)
     test_time = current_time - datetime.timedelta(days=3)
     self.assertFalse(mock_schedule_hour.called)
     mock_schedule_hour.apply_async.assert_any_call(
         (self.site_config.site.id, 3, serialize(test_time), [], True,
          None),
         retry=False,
     )
     mock_schedule_hour.apply_async.assert_any_call(
         (self.site_config.site.id, 3,
          serialize(test_time + datetime.timedelta(hours=23)), [], True,
          None),
         retry=False,
     )
     self.assertFalse(mock_ace.send.called)
Ejemplo n.º 44
0
    def _assert_template_for_offset(self, offset, message_count):
        current_day, offset, target_day, upgrade_deadline = self._get_dates(offset)

        user = UserFactory.create()
        for course_index in range(message_count):
            self._schedule_factory(
                offset=offset,
                enrollment__user=user,
                enrollment__course__id=CourseKey.from_string('edX/toy/course{}'.format(course_index))
            )

        patch_policies(self, [StubPolicy([ChannelType.PUSH])])

        mock_channel = Mock(
            channel_type=ChannelType.EMAIL,
            action_links=[],
            tracker_image_sources=[],
        )

        channel_map = ChannelMap([
            ['sailthru', mock_channel],
        ])

        sent_messages = []
        with self.settings(TEMPLATES=self._get_template_overrides()):
            with patch.object(self.task, 'async_send_task') as mock_schedule_send:
                mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args)

                num_expected_queries = NUM_QUERIES_FIRST_MATCH
                if self.queries_deadline_for_each_course:
                    # one query per course for opt-out and one for course modes
                    num_expected_queries += (message_count * 2) - 1
                else:
                    num_expected_queries += 1

                with self.assertNumQueries(num_expected_queries, table_blacklist=WAFFLE_TABLES):
                    self.task().apply(kwargs=dict(
                        site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
                        bin_num=self._calculate_bin_for_user(user),
                    ))
            num_expected_messages = 1 if self.consolidates_emails_for_learner else message_count
            self.assertEqual(len(sent_messages), num_expected_messages)

            with self.assertNumQueries(NUM_QUERIES_PER_MESSAGE_DELIVERY):
                with patch('openedx.core.djangoapps.schedules.tasks.segment.track') as mock_segment_track:
                    with patch('edx_ace.channel.channels', return_value=channel_map):
                        self.deliver_task(*sent_messages[0])
                        self.assertEqual(mock_segment_track.call_count, 1)

            self.assertEqual(mock_channel.deliver.call_count, 1)
            for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls:
                for template in attr.astuple(email):
                    self.assertNotIn("TEMPLATE WARNING", template)
                    self.assertNotIn("{{", template)
                    self.assertNotIn("}}", template)

            return mock_channel.deliver.mock_calls
Ejemplo n.º 45
0
 def default(self, o):  # pylint: disable=method-hidden
     if isinstance(o, UUID):
         return six.text_type(o)
     elif isinstance(o, date.datetime):
         return date.serialize(o)
     elif hasattr(o, u'to_json'):
         return o.to_json()
     else:
         return super(MessageEncoder, self).default(o)
Ejemplo n.º 46
0
    def _assert_template_for_offset(self, offset, message_count):
        current_day, offset, target_day, upgrade_deadline = self._get_dates(offset)

        user = UserFactory.create()
        for course_index in range(message_count):
            self._schedule_factory(
                offset=offset,
                enrollment__user=user,
                enrollment__course__id=CourseKey.from_string('edX/toy/course{}'.format(course_index))
            )

        patch_policies(self, [StubPolicy([ChannelType.PUSH])])

        mock_channel = Mock(
            channel_type=ChannelType.EMAIL,
            action_links=[],
            tracker_image_sources=[],
        )

        channel_map = ChannelMap([
            ['sailthru', mock_channel],
        ])

        sent_messages = []
        with self.settings(TEMPLATES=self._get_template_overrides()):
            with patch.object(self.task, 'async_send_task') as mock_schedule_send:
                mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args)

                num_expected_queries = NUM_QUERIES_FIRST_MATCH
                if self.queries_deadline_for_each_course:
                    # one query per course for opt-out and one for course modes
                    num_expected_queries += (message_count * 2) - 1
                else:
                    num_expected_queries += 1

                with self.assertNumQueries(num_expected_queries, table_blacklist=WAFFLE_TABLES):
                    self.task().apply(kwargs=dict(
                        site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
                        bin_num=self._calculate_bin_for_user(user),
                    ))
            num_expected_messages = 1 if self.consolidates_emails_for_learner else message_count
            self.assertEqual(len(sent_messages), num_expected_messages)

            with self.assertNumQueries(NUM_QUERIES_PER_MESSAGE_DELIVERY):
                with patch('openedx.core.djangoapps.schedules.tasks.segment.track') as mock_segment_track:
                    with patch('edx_ace.channel.channels', return_value=channel_map):
                        self.deliver_task(*sent_messages[0])
                        self.assertEqual(mock_segment_track.call_count, 1)

            self.assertEqual(mock_channel.deliver.call_count, 1)
            for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls:
                for template in attr.astuple(email):
                    self.assertNotIn("TEMPLATE WARNING", template)
                    self.assertNotIn("{{", template)
                    self.assertNotIn("}}", template)

            return mock_channel.deliver.mock_calls
    def test_course_without_verified_mode(self, mock_ace):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()
        schedule = self._schedule_factory()
        schedule.enrollment.course.modes.filter(mode_slug=CourseMode.VERIFIED).delete()

        self.task.apply(kwargs=dict(
            site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
            bin_num=self._calculate_bin_for_user(schedule.enrollment.user),
        ))
        self.assertEqual(mock_ace.send.called, False)
    def test_verified_learner(self, is_verified, mock_ace):
        current_day, offset, target_day, upgrade_deadline = self._get_dates()
        schedule = self._schedule_factory(
            enrollment__mode=CourseMode.VERIFIED if is_verified else CourseMode.AUDIT,
        )

        self.task.apply(kwargs=dict(
            site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
            bin_num=self._calculate_bin_for_user(schedule.enrollment.user),
        ))

        self.assertEqual(mock_ace.send.called, not is_verified)
    def test_schedule_hour(self, schedule_count, mock_schedule_send, mock_ace):
        schedules = [
            ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 18, 34, 30, tzinfo=pytz.UTC))
            for _ in range(schedule_count)
        ]

        test_time_str = serialize(datetime.datetime(2017, 8, 1, 18, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                self.site_config.site.id, 3, test_time_str, [schedules[0].enrollment.course.org],
            )
        self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 50
0
 def _send_message_task(self, schedule, offset, target_day):
     """
     Calls the task for sending a message to the given schedule and for the given
     offset and target_day. Returns the message that would have been sent.
     """
     sent_messages = []
     with patch.object(self.task, 'async_send_task') as mock_schedule_send:
         mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1])
         self.task().apply(kwargs=dict(
             site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
             bin_num=self._calculate_bin_for_user(schedule.enrollment.user),
         ))
     self.assertEqual(len(sent_messages), 1)
     return Message.from_string(sent_messages[0])
    def test_templates(self, message_count, day):

        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC),
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num))
            )
            for course_num in range(message_count)
        ]

        test_datetime = datetime.datetime(2017, 8, 3, 19, tzinfo=pytz.UTC)
        test_datetime_str = serialize(test_datetime)

        patch_policies(self, [StubPolicy([ChannelType.PUSH])])
        mock_channel = Mock(
            name='test_channel',
            channel_type=ChannelType.EMAIL
        )
        patch_channels(self, [mock_channel])

        sent_messages = []

        with self.settings(TEMPLATES=self._get_template_overrides()):
            with patch.object(tasks, '_recurring_nudge_schedule_send') as mock_schedule_send:
                mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args)

                with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES):
                    tasks.recurring_nudge_schedule_bin(
                        self.site_config.site.id, target_day_str=test_datetime_str, day_offset=day,
                        bin_num=self._calculate_bin_for_user(user), org_list=[schedules[0].enrollment.course.org],
                    )

            self.assertEqual(len(sent_messages), 1)

            # Load the site
            # Check the schedule config
            with self.assertNumQueries(2):
                for args in sent_messages:
                    tasks._recurring_nudge_schedule_send(*args)

            self.assertEqual(mock_channel.deliver.call_count, 1)
            for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls:
                for template in attr.astuple(email):
                    self.assertNotIn("TEMPLATE WARNING", template)
Ejemplo n.º 52
0
    def send(self, day_offset, override_recipient_email=None):
        if not self.is_enqueue_enabled():
            self.log_debug('Message queuing disabled for site %s', self.site.domain)
            return

        exclude_orgs, org_list = self.get_course_org_filter()

        target_date = self.current_date + datetime.timedelta(days=day_offset)
        self.log_debug('Target date = %s', target_date.isoformat())
        for bin in range(self.num_bins):
            task_args = (
                self.site.id, serialize(target_date), day_offset, bin, org_list, exclude_orgs, override_recipient_email,
            )
            self.log_debug('Launching task with args = %r', task_args)
            self.async_send_task.apply_async(
                task_args,
                retry=False,
            )
    def test_multiple_enrollments(self, test_hour, messages_sent, mock_schedule_send, mock_ace):
        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 1, hour, 44, 30, tzinfo=pytz.UTC),
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy', 'Hour{}'.format(hour))
            )
            for hour in (19, 20, 21)
        ]

        test_time_str = serialize(datetime.datetime(2017, 8, 1, test_hour, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                self.site_config.site.id, 3, test_time_str, [schedules[0].enrollment.course.org],
            )
        self.assertEqual(mock_schedule_send.apply_async.call_count, messages_sent)
        self.assertFalse(mock_ace.send.called)
Ejemplo n.º 54
0
    def _check_if_email_sent_for_experience(self, test_config):
        current_day, offset, target_day, _ = self._get_dates(offset=test_config.offset)

        kwargs = {
            'offset': offset
        }
        if test_config.experience is None:
            kwargs['experience'] = None
        else:
            kwargs['experience__experience_type'] = test_config.experience
        schedule = self._schedule_factory(**kwargs)

        with patch.object(tasks, 'ace') as mock_ace:
            self.task().apply(kwargs=dict(
                site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset,
                bin_num=self._calculate_bin_for_user(schedule.enrollment.user),
            ))

            self.assertEqual(mock_ace.send.called, test_config.email_sent)
    def test_send_after_course_end(self, mock_schedule_send):
        user1 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS)

        schedule = ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC),
            enrollment__user=user1,
        )
        schedule.enrollment.course = CourseOverviewFactory()
        schedule.enrollment.course.end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=1)

        test_datetime = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC)
        test_datetime_str = serialize(test_datetime)

        tasks.recurring_nudge_schedule_bin.apply_async(
            self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=0,
            org_list=[schedule.enrollment.course.org],
        )

        self.assertFalse(mock_schedule_send.apply_async.called)
Ejemplo n.º 56
0
    def test_course_end(self, has_course_ended):
        user1 = UserFactory.create(id=self.task.num_bins)
        current_day, offset, target_day, upgrade_deadline = self._get_dates()

        end_date_offset = -2 if has_course_ended else 2
        self._schedule_factory(
            enrollment__user=user1,
            enrollment__course__start=current_day - datetime.timedelta(days=30),
            enrollment__course__end=current_day + datetime.timedelta(days=end_date_offset)
        )

        with patch.object(self.task, 'async_send_task') as mock_schedule_send:
            self.task().apply(kwargs=dict(
                site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=0,
            ))

        if has_course_ended:
            self.assertFalse(mock_schedule_send.apply_async.called)
        else:
            self.assertTrue(mock_schedule_send.apply_async.called)
    def test_templates(self, message_count, day):

        settings.TEMPLATES[0]['OPTIONS']['string_if_invalid'] = "TEMPLATE WARNING - MISSING VARIABLE [%s]"
        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 1, 19, 44, 30, tzinfo=pytz.UTC),
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy', 'Hour{}'.format(idx))
            )
            for idx in range(message_count)
        ]

        test_time_str = serialize(datetime.datetime(2017, 8, 1, 19, tzinfo=pytz.UTC))

        patch_policies(self, [StubPolicy([ChannelType.PUSH])])
        mock_channel = Mock(
            name='test_channel',
            channel_type=ChannelType.EMAIL
        )
        patch_channels(self, [mock_channel])

        sent_messages = []

        with patch.object(tasks, '_recurring_nudge_schedule_send') as mock_schedule_send:
            mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args)

            with self.assertNumQueries(2):
                tasks.recurring_nudge_schedule_hour(
                    self.site_config.site.id, day, test_time_str, [schedules[0].enrollment.course.org],
                )

        self.assertEqual(len(sent_messages), 1)

        for args in sent_messages:
            tasks._recurring_nudge_schedule_send(*args)

        self.assertEqual(mock_channel.deliver.call_count, 1)
        for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls:
            for template in attr.astuple(email):
                self.assertNotIn("TEMPLATE WARNING", template)
    def test_no_course_overview(self, mock_schedule_send):

        schedule = ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 1, 20, 34, 30, tzinfo=pytz.UTC),
        )
        schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall')
        schedule.enrollment.save()

        test_time_str = serialize(datetime.datetime(2017, 8, 1, 20, tzinfo=pytz.UTC))
        with self.assertNumQueries(2):
            tasks.recurring_nudge_schedule_hour(
                self.site_config.site.id, 3, test_time_str, [schedule.enrollment.course.org],
            )

        # There is no database constraint that enforces that enrollment.course_id points
        # to a valid CourseOverview object. However, in that case, schedules isn't going
        # to attempt to address it, and will instead simply skip those users.
        # This happens 'transparently' because django generates an inner-join between
        # enrollment and course_overview, and thus will skip any rows where course_overview
        # is null.
        self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
    def test_multiple_enrollments(self, mock_schedule_send, mock_ace):
        user = UserFactory.create()
        schedules = [
            ScheduleFactory.create(
                start=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC),
                enrollment__user=user,
                enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num))
            )
            for course_num in (1, 2, 3)
        ]

        test_datetime = datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC)
        test_datetime_str = serialize(test_datetime)
        with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES):
            tasks.recurring_nudge_schedule_bin(
                self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3,
                bin_num=user.id % tasks.RECURRING_NUDGE_NUM_BINS,
                org_list=[schedules[0].enrollment.course.org],
            )
        self.assertEqual(mock_schedule_send.apply_async.call_count, 1)
        self.assertFalse(mock_ace.send.called)
    def test_site_config(self, org_list, exclude_orgs, expected_message_count, mock_schedule_send, mock_ace):
        filtered_org = 'filtered_org'
        unfiltered_org = 'unfiltered_org'
        site1 = SiteFactory.create(domain='foo1.bar', name='foo1.bar')
        limited_config = SiteConfigurationFactory.create(values={'course_org_filter': [filtered_org]}, site=site1)
        site2 = SiteFactory.create(domain='foo2.bar', name='foo2.bar')
        unlimited_config = SiteConfigurationFactory.create(values={'course_org_filter': []}, site=site2)

        for config in (limited_config, unlimited_config):
            ScheduleConfigFactory.create(site=config.site)

        user1 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS)
        user2 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS * 2)

        ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC),
            enrollment__course__org=filtered_org,
            enrollment__user=user1,
        )
        ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC),
            enrollment__course__org=unfiltered_org,
            enrollment__user=user1,
        )
        ScheduleFactory.create(
            start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC),
            enrollment__course__org=unfiltered_org,
            enrollment__user=user2,
        )

        test_datetime = datetime.datetime(2017, 8, 3, 17, tzinfo=pytz.UTC)
        test_datetime_str = serialize(test_datetime)
        with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES):
            tasks.recurring_nudge_schedule_bin(
                limited_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=0,
                org_list=org_list, exclude_orgs=exclude_orgs,
            )

        self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count)
        self.assertFalse(mock_ace.send.called)