コード例 #1
0
    def setUp(self):
        super().setUp()

        self.course = DummyCourse(id='course-v1:testX+tt101+2019')
        self.course.save()

        self.user = User(username='******', email='*****@*****.**')
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment,
                                      start_date=datetime(2019, 4, 1))
        self.schedule.save()

        dummy_schedule_patcher = patch('edx_when.models.Schedule',
                                       DummySchedule)
        dummy_schedule_patcher.start()
        self.addCleanup(dummy_schedule_patcher.stop)

        relative_dates_patcher = patch(
            'edx_when.api._are_relative_dates_enabled', return_value=True)
        relative_dates_patcher.start()
        self.addCleanup(relative_dates_patcher.stop)
        self.addCleanup(cache.clear)

        cache.clear()
        DEFAULT_REQUEST_CACHE.clear()
コード例 #2
0
    def test_get_dates_for_course(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES
        first = items[0]
        assert retrieved[(first[0], 'due')] == first[1]['due']

        # second time is cached
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES

        # third time with new course_id

        course2 = DummyCourse(id='course-v1:testX+tt202+2019')
        course2.save()
        new_items = make_items(course2.id)

        enrollment2 = DummyEnrollment(user=self.user, course=course2)
        enrollment2.save()

        schedule2 = DummySchedule(enrollment=enrollment2,
                                  start_date=datetime(2019, 4, 1))
        schedule2.save()

        api.set_dates_for_course(new_items[0][0].course_key, new_items)
        new_retrieved = api.get_dates_for_course(new_items[0][0].course_key)
        assert len(new_retrieved) == NUM_OVERRIDES
        first_id = list(new_retrieved.keys())[0][0]
        last_id = list(retrieved.keys())[0][0]
        assert first_id.course_key != last_id.course_key
        return items
コード例 #3
0
ファイル: test_api.py プロジェクト: edx/edx-when
    def setUp(self):
        super().setUp()

        self.course = DummyCourse(id='course-v1:testX+tt101+2019')
        self.course.save()

        self.course_version = 'TEST_VERSION'

        self.user = User(username='******', email='*****@*****.**')
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(
            enrollment=self.enrollment, created=datetime(2019, 4, 1), start_date=datetime(2019, 4, 1)
        )
        self.schedule.save()

        dummy_schedule_patcher = patch('edx_when.utils.Schedule', DummySchedule)
        dummy_schedule_patcher.start()
        self.addCleanup(dummy_schedule_patcher.stop)

        relative_dates_patcher = patch('edx_when.api._are_relative_dates_enabled', return_value=True)
        relative_dates_patcher.start()
        self.addCleanup(relative_dates_patcher.stop)
        self.addCleanup(TieredCache.dangerous_clear_all_tiers)

        TieredCache.dangerous_clear_all_tiers()
コード例 #4
0
ファイル: test_models.py プロジェクト: httpsOmkar/edx-when
    def setUp(self):
        super(TestContentDate, self).setUp()
        self.course = DummyCourse(id='course-v1:edX+Test+Course')
        self.course.save()

        self.user = User()
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment, start_date=datetime(2020, 1, 1))
        self.schedule.save()

        self.policy = DatePolicy(abs_date=datetime(2020, 1, 1))
        self.content_date = ContentDate(course_id=self.course.id, policy=self.policy)
コード例 #5
0
ファイル: test_models.py プロジェクト: trungdiep/edx-when
class TestContentDate(TestCase):
    """
    Tests of the ContentDate model.
    """
    def setUp(self):
        super().setUp()
        self.course = DummyCourse(id='course-v1:edX+Test+Course')
        self.course.save()

        self.user = User()
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment,
                                      start_date=datetime(2020, 1, 1))
        self.schedule.save()

        self.policy = DatePolicy(abs_date=datetime(2020, 1, 1))
        self.content_date = ContentDate(course_id=self.course.id,
                                        policy=self.policy)

    @patch('edx_when.models.Schedule', DummySchedule)
    def test_schedule_for_user_id(self):
        assert self.content_date.schedule_for_user(
            self.user.id) == self.schedule

    @patch('edx_when.models.Schedule', wraps=DummySchedule)
    def test_schedule_for_user_with_object_does_not_exist(
            self, dummy_schedule):
        """Test that None is returned when schedules are fetched for user."""
        dummy_schedule.objects.get.side_effect = ObjectDoesNotExist()
        assert self.content_date.schedule_for_user(self.user) is None

    @patch('edx_when.models.Schedule', None)
    def test_schedule_for_user_id_no_schedule_installed(self):
        assert self.content_date.schedule_for_user(self.user.id) is None

    @patch('edx_when.models.Schedule', DummySchedule)
    def test_schedule_for_user_obj(self):
        assert self.content_date.schedule_for_user(self.user) == self.schedule

    @patch('edx_when.models.Schedule', wraps=DummySchedule)
    def test_schedule_for_user_obj_no_enrollments(self, mock_schedule):
        mock_schedule.objects.get.side_effect = ObjectDoesNotExist()
        assert self.content_date.schedule_for_user(self.user) is None
コード例 #6
0
ファイル: test_models.py プロジェクト: httpsOmkar/edx-when
class TestContentDate(TestCase):
    """
    Tests of the ContentDate model.
    """
    def setUp(self):
        super(TestContentDate, self).setUp()
        self.course = DummyCourse(id='course-v1:edX+Test+Course')
        self.course.save()

        self.user = User()
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment, start_date=datetime(2020, 1, 1))
        self.schedule.save()

        self.policy = DatePolicy(abs_date=datetime(2020, 1, 1))
        self.content_date = ContentDate(course_id=self.course.id, policy=self.policy)

    @patch('edx_when.models.Schedule', DummySchedule)
    def test_schedule_for_user_id(self):
        assert self.content_date.schedule_for_user(self.user.id) == self.schedule

    @patch('edx_when.models.Schedule', None)
    def test_schedule_for_user_id_no_schedule_installed(self):
        assert self.content_date.schedule_for_user(self.user.id) is None

    def test_schedule_for_user_obj(self):
        assert self.content_date.schedule_for_user(self.user) == self.schedule

    def test_schedule_for_user_obj_no_enrollments(self):
        mock_user = Mock(wraps=self.user)
        del mock_user.courseenrollment_set
        assert self.content_date.schedule_for_user(mock_user) is None
コード例 #7
0
class ApiTests(TestCase):
    """
    Tests for edx_when.api
    """
    def setUp(self):
        super().setUp()

        self.course = DummyCourse(id='course-v1:testX+tt101+2019')
        self.course.save()

        self.user = User(username='******', email='*****@*****.**')
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment,
                                      start_date=datetime(2019, 4, 1))
        self.schedule.save()

        dummy_schedule_patcher = patch('edx_when.models.Schedule',
                                       DummySchedule)
        dummy_schedule_patcher.start()
        self.addCleanup(dummy_schedule_patcher.stop)

        relative_dates_patcher = patch(
            'edx_when.api._are_relative_dates_enabled', return_value=True)
        relative_dates_patcher.start()
        self.addCleanup(relative_dates_patcher.stop)
        self.addCleanup(cache.clear)

        cache.clear()
        DEFAULT_REQUEST_CACHE.clear()

    @patch('edx_when.api.Schedule', DummySchedule)
    def test_get_schedules_with_due_date_for_abs_date(self):
        self.schedule.start_date = datetime(2019, 3, 22)
        items = make_items(with_relative=False)
        assignment_date = items[0][1].get('due')
        api.set_date_for_block(items[0][0].course_key, items[0][0], 'due',
                               assignment_date)
        # Specify the actual assignment due date so this will return true
        schedules = api.get_schedules_with_due_date(
            items[0][0].course_key, datetime.date(assignment_date))
        assert len(schedules) > 0
        for schedule in schedules:
            assert schedule.enrollment.course_id == items[0][0].course_key
            assert schedule.enrollment.user.id == self.user.id

    @patch('edx_when.api.Schedule', DummySchedule)
    def test_get_schedules_with_due_date_for_rel_date(self):
        items = make_items(with_relative=False)
        api.set_dates_for_course(items[0][0].course_key, items)
        relative_date = timedelta(days=2)
        api.set_date_for_block(items[0][0].course_key, items[0][0], 'due',
                               relative_date)
        assignment_date = items[0][1].get('due') + relative_date
        # Move the schedule's start to the first assignment's original due since it's now offset
        self.schedule.start_date = items[0][1].get('due')
        self.schedule.save()
        # Specify the actual assignment due date so this will return true
        schedules = api.get_schedules_with_due_date(items[0][0].course_key,
                                                    assignment_date.date())
        assert len(schedules) > 0
        for schedule in schedules:
            assert schedule.enrollment.course_id == items[0][0].course_key
            assert schedule.enrollment.user.id == self.user.id

    @patch('edx_when.api.Schedule', DummySchedule)
    def test_get_schedules_with_due_date_for_abs_user_dates(self):
        items = make_items(with_relative=True)
        api.set_dates_for_course(items[0][0].course_key, items)
        assignment_date = items[0][1].get('due')
        api.set_date_for_block(items[0][0].course_key,
                               items[0][0],
                               'due',
                               assignment_date,
                               user=self.user)
        models.UserDate.objects.create(
            abs_date=assignment_date,
            user=self.user,
            content_date=models.ContentDate.objects.first(),
        )
        # Specify the actual assignment due date so this will return true
        schedules = api.get_schedules_with_due_date(items[0][0].course_key,
                                                    assignment_date.date())
        assert len(
            schedules
        ) == 1  # Make sure there's only one schedule, we should not have duplicates
        assert schedules[0].enrollment.course_id == items[0][0].course_key
        assert schedules[0].enrollment.user.id == self.user.id

    @patch('edx_when.api.Schedule', DummySchedule)
    def test_get_schedules_with_due_date_for_rel_user_dates(self):
        items = make_items(with_relative=True)
        api.set_dates_for_course(items[0][0].course_key, items)
        assignment_date = items[0][1].get('due')
        api.set_date_for_block(items[0][0].course_key,
                               items[0][0],
                               'due',
                               assignment_date,
                               user=self.user)
        models.UserDate.objects.create(
            rel_date=timedelta(days=2),
            user=self.user,
            content_date=models.ContentDate.objects.first(),
        )
        # Specify the actual assignment due date so this will return true
        schedules = api.get_schedules_with_due_date(items[0][0].course_key,
                                                    assignment_date.date())
        assert len(
            schedules
        ) == 1  # Make sure there's only one schedule, we should not have duplicates
        assert schedules[0].enrollment.course_id == items[0][0].course_key
        assert schedules[0].enrollment.user.id == self.user.id

    def test_set_dates_for_course(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)

        cdates = models.ContentDate.objects.all()
        assert len(cdates) == NUM_OVERRIDES

    def test_get_dates_for_course(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES
        first = items[0]
        assert retrieved[(first[0], 'due')] == first[1]['due']

        # second time is cached
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES

        # third time with new course_id

        course2 = DummyCourse(id='course-v1:testX+tt202+2019')
        course2.save()
        new_items = make_items(course2.id)

        enrollment2 = DummyEnrollment(user=self.user, course=course2)
        enrollment2.save()

        schedule2 = DummySchedule(enrollment=enrollment2,
                                  start_date=datetime(2019, 4, 1))
        schedule2.save()

        api.set_dates_for_course(new_items[0][0].course_key, new_items)
        new_retrieved = api.get_dates_for_course(new_items[0][0].course_key)
        assert len(new_retrieved) == NUM_OVERRIDES
        first_id = list(new_retrieved.keys())[0][0]
        last_id = list(retrieved.keys())[0][0]
        assert first_id.course_key != last_id.course_key
        return items

    def test_get_dates_no_schedule(self):
        items = make_items(with_relative=True)
        course_key = items[0][0].course_key
        api.set_dates_for_course(course_key, items)
        retrieved = api.get_dates_for_course(course_key, user=self.user)
        assert len(retrieved) == 6
        self.schedule.delete()
        retrieved = api.get_dates_for_course(course_key,
                                             user=self.user,
                                             use_cached=False)
        assert len(retrieved) == 3

    def test_get_user_date_no_schedule(self):
        items = make_items()
        course_key = items[0][0].course_key
        api.set_dates_for_course(course_key, items)
        before_override = api.get_dates_for_course(course_key, user=self.user)
        assert len(before_override) == 3

        # Override a date for the user with a relative date, but remove the schedule
        # so that the override can't be applied
        api.set_date_for_block(course_key,
                               items[0][0],
                               'due',
                               timedelta(days=2),
                               user=self.user)
        self.schedule.delete()

        after_override = api.get_dates_for_course(course_key,
                                                  user=self.user,
                                                  use_cached=False)
        assert before_override == after_override

    def test_clear_dates_for_course(self):
        items = self.test_get_dates_for_course()
        keep_date = models.ContentDate.objects.get(location=items[1][0])

        with self.assertNumQueries(1):
            api.clear_dates_for_course(items[0][0].course_key,
                                       keep=[keep_date.id])

        retrieved = api.get_dates_for_course(items[0][0].course_key,
                                             use_cached=False)
        self.assertEqual(len(retrieved), 1)
        self.assertEqual(list(retrieved.keys())[0][0], items[1][0])

        with self.assertNumQueries(1):
            api.clear_dates_for_course(items[0][0].course_key)
        self.assertEqual(
            api.get_dates_for_course(items[0][0].course_key, use_cached=False),
            {})

    def test_set_user_override_invalid_block(self):
        items = make_items()
        first = items[0]
        block_id = first[0]
        api.set_dates_for_course(str(block_id.course_key), items)

        with self.assertRaises(api.MissingDateError):
            # can't set a user override for content without a date
            bad_block_id = make_block_id()
            api.set_date_for_block(bad_block_id.course_key,
                                   bad_block_id,
                                   'due',
                                   datetime(2019, 4, 6),
                                   user=self.user)

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 3)),
        (datetime(2019, 4, 6), timedelta(days=-1)),
        (timedelta(days=5), timedelta(days=-1)),
    )
    @ddt.unpack
    def test_set_user_override_invalid_date(self, initial_date, override_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date
        api.set_dates_for_course(str(block_id.course_key), items)

        with self.assertRaises(api.InvalidDateError):
            api.set_date_for_block(block_id.course_key,
                                   block_id,
                                   'due',
                                   override_date,
                                   user=self.user)

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (datetime(2019, 4, 6), timedelta(days=3), datetime(2019, 4, 9)),
        (timedelta(days=3), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (timedelta(days=3), timedelta(days=2), datetime(2019, 4, 6)),
    )
    @ddt.unpack
    def test_set_user_override(self, initial_date, override_date,
                               expected_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date

        api.set_dates_for_course(str(block_id.course_key), items)

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               override_date,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        cache.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        assert retrieved[block_id, 'due'] == expected_date

        overrides = api.get_overrides_for_block(block_id.course_key, block_id)
        assert len(overrides) == 1
        assert overrides[0][2] == expected_date

        overrides = list(
            api.get_overrides_for_user(block_id.course_key, self.user))
        assert len(overrides) == 1
        assert overrides[0] == {
            'location': block_id,
            'actual_date': expected_date
        }

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        # The expected date shifts from 4/6 to 4/4 because once it converts to a relative date,
        # it is based off the schedule start and the users schedule start is 4/1
        (datetime(2019, 4, 6), timedelta(days=3), datetime(2019, 4, 4)),
        (timedelta(days=3), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        # Because the relative date is changed for the entire course, the user's date goes
        # from 4/4 to 4/3 because it is based off the schedule start and the users
        # schedule start is 4/1. This is different from when you call set_date_for_block
        # and pass in a user as that will adjust it from the old due date (see test_set_user_override)
        (timedelta(days=3), timedelta(days=2), datetime(2019, 4, 3)),
    )
    @ddt.unpack
    def test_set_date_for_block(self, initial_date, override_date,
                                expected_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date

        api.set_dates_for_course(str(block_id.course_key), items)

        api.set_date_for_block(block_id.course_key, block_id, 'due',
                               override_date)
        DEFAULT_REQUEST_CACHE.clear()
        cache.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        assert retrieved[block_id, 'due'] == expected_date

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (datetime(2019, 4, 6), timedelta(days=3), datetime(2019, 4, 9)),
        (timedelta(days=3), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (timedelta(days=3), timedelta(days=2), datetime(2019, 4, 6)),
    )
    @ddt.unpack
    def test_remove_user_override(self, initial_date, override_date,
                                  expected_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date

        api.set_dates_for_course(str(block_id.course_key), items)

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               override_date,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        cache.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        assert retrieved[block_id, 'due'] == expected_date

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               None,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        cache.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        if isinstance(initial_date, timedelta):
            user_initial_date = self.schedule.start_date + initial_date
        else:
            user_initial_date = initial_date
        assert retrieved[block_id, 'due'] == user_initial_date

    def test_get_date_for_block(self):
        items = make_items()
        course_id = items[0][0].course_key
        api.set_dates_for_course(course_id, items)
        block_id, data = items[0]
        assert api.get_date_for_block(course_id, block_id,
                                      user=self.user) == data['due']
        assert api.get_date_for_block(course_id, 'bad', user=self.user) is None

    def test_is_enabled(self):
        items = make_items()
        course_id = items[0][0].course_key
        assert not api.is_enabled_for_course(course_id)
        api.set_dates_for_course(course_id, items)
        assert api.is_enabled_for_course(course_id)

    def test_allow_relative_dates(self):
        course_key = CourseLocator('testX', 'tt101', '2019')
        block1 = make_block_id(course_key)
        date1 = datetime(2019, 3, 22)
        block2 = make_block_id(course_key)
        date2 = datetime(2019, 3, 23)
        date2_override_delta = timedelta(days=10)
        date2_override = date2 + date2_override_delta
        block3 = make_block_id(course_key)
        date3_delta = timedelta(days=1)
        date3 = self.schedule.start_date + date3_delta
        block4 = make_block_id(course_key)
        date4_delta = timedelta(days=2)
        date4 = self.schedule.start_date + date4_delta
        date4_override = datetime(2019, 4, 24)
        items = [
            (block1, {
                'due': date1
            }),  # absolute
            (block2, {
                'due': date2
            }),  # absolute, to be overwritten by relative date
            (block3, {
                'due': date3_delta
            }),  # relative
            (block4, {
                'due': date4_delta
            }),  # relative, to be overwritten by absolute date
        ]
        api.set_dates_for_course(course_key, items)
        api.set_date_for_block(course_key,
                               block2,
                               'due',
                               date2_override_delta,
                               user=self.user)
        api.set_date_for_block(course_key,
                               block4,
                               'due',
                               date4_override,
                               user=self.user)

        # get_dates_for_course
        dates = [
            ((block1, 'due'), date1),
            ((block2, 'due'), date2),
            ((block3, 'due'), date3),
            ((block4, 'due'), date4),
        ]
        user_dates = [
            ((block1, 'due'), date1),
            ((block2, 'due'), date2_override),
        ]
        assert api.get_dates_for_course(course_key,
                                        schedule=self.schedule) == dict(dates)
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_dates_for_course(
                course_key, schedule=self.schedule) == dict(dates[0:2])
            assert api.get_dates_for_course(course_key,
                                            schedule=self.schedule,
                                            user=self.user) == dict(user_dates)

        # get_date_for_block
        assert api.get_date_for_block(course_key, block2) == date2
        assert api.get_date_for_block(course_key, block4,
                                      user=self.user) == date4_override
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_date_for_block(course_key, block2) == date2
            assert api.get_date_for_block(course_key, block1,
                                          user=self.user) == date1
            assert api.get_date_for_block(course_key, block2,
                                          user=self.user) == date2_override
            assert api.get_date_for_block(course_key, block4,
                                          user=self.user) is None

        # get_overrides_for_block
        block2_overrides = [(self.user.username, 'unknown', date2_override)]
        assert api.get_overrides_for_block(course_key,
                                           block2) == block2_overrides
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_overrides_for_block(course_key, block2) == [
                (self.user.username, 'unknown', date2_override)
            ]

        # get_overrides_for_user
        user_overrides = [
            {
                'location': block4,
                'actual_date': date4_override
            },
            {
                'location': block2,
                'actual_date': date2_override
            },
        ]
        assert list(api.get_overrides_for_user(course_key,
                                               self.user)) == user_overrides
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert list(api.get_overrides_for_user(
                course_key, self.user)) == user_overrides

    def test_relative_date_past_end_date(self):
        course_key = CourseLocator('testX', 'tt101', '2019')
        start_block = make_block_id(course_key, block_type='course')
        start_date = datetime(2019, 3, 15)
        before_end_date_block = make_block_id(course_key)
        before_end_date_delta = timedelta(days=1)
        before_end_date = self.schedule.start_date + before_end_date_delta
        after_end_date_block = make_block_id(course_key)
        after_end_date_delta = timedelta(days=10)
        end_block = make_block_id(course_key, block_type='course')
        end_date = datetime(2019, 4, 4)
        self.schedule.created = datetime(
            2019, 3, 20)  # set a while back, before the 4/1 start_date
        self.schedule.save()
        items = [
            (start_block, {
                'start': start_date
            }),  # start dates are always absolute
            (before_end_date_block, {
                'due': before_end_date_delta
            }),  # relative
            (after_end_date_block, {
                'due': after_end_date_delta
            }),  # relative
            (end_block, {
                'end': end_date
            }),  # end dates are always absolute
        ]
        api.set_dates_for_course(course_key, items)

        dates = [
            ((start_block, 'start'), start_date),
            ((before_end_date_block, 'due'), before_end_date),
            # Because the end date for this block would have been after the course end date,
            # the block will have an end date of the course end date
            ((after_end_date_block, 'due'), end_date),
            ((end_block, 'end'), end_date),
        ]
        assert api.get_dates_for_course(course_key,
                                        schedule=self.schedule) == dict(dates)

    def test_relative_date_past_cutoff_date(self):
        course_key = CourseLocator('testX', 'tt101', '2019')
        start_block = make_block_id(course_key, block_type='course')
        start_date = datetime(2019, 3, 15)
        first_block = make_block_id(course_key)
        first_delta = timedelta(days=1)
        second_block = make_block_id(course_key)
        second_delta = timedelta(days=10)
        end_block = make_block_id(course_key, block_type='course')
        end_date = datetime(2019, 4, 20)
        items = [
            (start_block, {
                'start': start_date
            }),  # start dates are always absolute
            (first_block, {
                'due': first_delta
            }),  # relative
            (second_block, {
                'due': second_delta
            }),  # relative
            (end_block, {
                'end': end_date
            }),  # end dates are always absolute
        ]
        api.set_dates_for_course(course_key, items)

        # Try one with just enough as a sanity check
        self.schedule.created = end_date - second_delta
        self.schedule.save()
        dates = [
            ((start_block, 'start'), start_date),
            ((first_block, 'due'), self.schedule.start_date + first_delta),
            ((second_block, 'due'), self.schedule.start_date + second_delta),
            ((end_block, 'end'), end_date),
        ]
        assert api.get_dates_for_course(course_key,
                                        schedule=self.schedule) == dict(dates)

        cache.clear()
        DEFAULT_REQUEST_CACHE.clear()

        # Now set schedule start date too close to the end date and verify that we no longer get due dates
        self.schedule.created = datetime(2019, 4, 15)
        self.schedule.save()
        dates = [
            ((start_block, 'start'), start_date),
            ((first_block, 'due'), None),
            ((second_block, 'due'), None),
            ((end_block, 'end'), end_date),
        ]
        assert api.get_dates_for_course(course_key,
                                        schedule=self.schedule) == dict(dates)

    @ddt.data(*[(has_schedule, pass_user_object, pass_schedule, item_count)
                for has_schedule in (True, False)
                for pass_user_object in (True, False)
                for pass_schedule in (True, False)
                for item_count in (1, 5, 25, 100)])
    @ddt.unpack
    def test_get_dates_for_course_query_counts(self, has_schedule,
                                               pass_user_object, pass_schedule,
                                               item_count):
        if not has_schedule:
            self.schedule.delete()
        items = [(make_block_id(self.course.id), {
            'due': datetime(2020, 1, 1) + timedelta(days=i)
        }) for i in range(item_count)]
        api.set_dates_for_course(self.course.id, items)

        user = self.user if pass_user_object else self.user.id
        schedule = self.schedule if pass_schedule and has_schedule else None

        if has_schedule and pass_schedule:
            query_count = 2
        else:
            query_count = 3
        with self.assertNumQueries(query_count):
            dates = api.get_dates_for_course(course_id=self.course.id,
                                             user=user,
                                             schedule=schedule)

        # Second time, the request cache eliminates all querying...
        with self.assertNumQueries(0):
            cached_dates = api.get_dates_for_course(course_id=self.course.id,
                                                    user=user,
                                                    schedule=schedule)
            assert dates == cached_dates

        # Now wipe the request cache...
        DEFAULT_REQUEST_CACHE.clear()

        # This time, test the external cache (which eliminates the one large
        # query to ContentDates).
        with self.assertNumQueries(query_count - 1):
            externally_cached_dates = api.get_dates_for_course(
                course_id=self.course.id, user=user, schedule=schedule)
            assert dates == externally_cached_dates

        # Finally, force uncached behavior with used_cache=False
        with self.assertNumQueries(query_count):
            uncached_dates = api.get_dates_for_course(course_id=self.course.id,
                                                      user=user,
                                                      schedule=schedule,
                                                      use_cached=False)
            assert dates == uncached_dates

    def test_set_dates_for_course_query_counts(self):
        items = make_items()

        with self.assertNumQueries(2):  # two for transaction wrappers
            with patch('edx_when.api.set_date_for_block',
                       return_value=1) as mock_set:
                with patch(
                        'edx_when.api.clear_dates_for_course') as mock_clear:
                    api.set_dates_for_course(self.course.id, items)

        self.assertEqual(mock_set.call_count, NUM_OVERRIDES)
        self.assertEqual(mock_clear.call_args_list,
                         [call(self.course.id, keep=[1] * NUM_OVERRIDES)])

    def test_set_date_for_block_query_counts(self):
        args = (self.course.id, make_block_id(self.course.id), 'due',
                datetime(2019, 3, 22))

        # Each date we make has:
        #  1 get & 1 create for the date itself
        #  1 get & 1 create for the sub-policy
        with self.assertNumQueries(4):
            api.set_date_for_block(*args)

        # When setting same items, we should only do initial read
        with self.assertNumQueries(1):
            api.set_date_for_block(*args)
コード例 #8
0
class ApiTests(TestCase):
    """
    Tests for edx_when.api
    """
    def setUp(self):
        super(ApiTests, self).setUp()
        self.course = DummyCourse(id='course-v1:testX+tt101+2019')
        self.course.save()

        self.user = User(username='******', email='*****@*****.**')
        self.user.save()

        self.enrollment = DummyEnrollment(user=self.user, course=self.course)
        self.enrollment.save()

        self.schedule = DummySchedule(enrollment=self.enrollment,
                                      start_date=datetime(2019, 4, 1))
        self.schedule.save()

        dummy_schedule_patcher = patch('edx_when.models.Schedule',
                                       DummySchedule)
        dummy_schedule_patcher.start()
        self.addCleanup(dummy_schedule_patcher.stop)

        relative_dates_patcher = patch(
            'edx_when.api._are_relative_dates_enabled', return_value=True)
        relative_dates_patcher.start()
        self.addCleanup(relative_dates_patcher.stop)

        DEFAULT_REQUEST_CACHE.clear()

    def test_set_dates_for_course(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)

        cdates = models.ContentDate.objects.all()
        assert len(cdates) == NUM_OVERRIDES

    def test_get_dates_for_course(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES
        first = items[0]
        assert retrieved[(first[0], 'due')] == first[1]['due']

        # second time is cached
        retrieved = api.get_dates_for_course(items[0][0].course_key)
        assert len(retrieved) == NUM_OVERRIDES

        # third time with new course_id

        course2 = DummyCourse(id='course-v1:testX+tt202+2019')
        course2.save()
        new_items = make_items(course2.id)

        enrollment2 = DummyEnrollment(user=self.user, course=course2)
        enrollment2.save()

        schedule2 = DummySchedule(enrollment=enrollment2,
                                  start_date=datetime(2019, 4, 1))
        schedule2.save()

        api.set_dates_for_course(new_items[0][0].course_key, new_items)
        new_retrieved = api.get_dates_for_course(new_items[0][0].course_key)
        assert len(new_retrieved) == NUM_OVERRIDES
        first_id = list(new_retrieved.keys())[0][0]
        last_id = list(retrieved.keys())[0][0]
        assert first_id.course_key != last_id.course_key
        return items

    def test_get_dates_no_schedule(self):
        items = make_items(with_relative=True)
        api.set_dates_for_course(items[0][0].course_key, items)
        retrieved = api.get_dates_for_course(items[0][0].course_key,
                                             user=self.user)
        assert len(retrieved) == 6
        self.schedule.delete()
        retrieved = api.get_dates_for_course(items[0][0].course_key,
                                             user=self.user,
                                             use_cached=False)
        assert len(retrieved) == 3

    def test_get_user_date_no_schedule(self):
        items = make_items()
        api.set_dates_for_course(items[0][0].course_key, items)
        before_override = api.get_dates_for_course(items[0][0].course_key,
                                                   user=self.user)
        assert len(before_override) == 3

        # Override a date for the user with a relative date, but remove the schedule
        # so that the override can't be applied
        api.set_date_for_block(items[0][0].course_key,
                               items[0][0],
                               'due',
                               timedelta(days=2),
                               user=self.user)
        self.schedule.delete()

        after_override = api.get_dates_for_course(items[0][0].course_key,
                                                  user=self.user,
                                                  use_cached=False)
        assert before_override == after_override

    def test_clear_dates_for_course(self):
        items = self.test_get_dates_for_course()
        api.clear_dates_for_course(items[0][0].course_key)
        retrieved = api.get_dates_for_course(items[0][0].course_key,
                                             use_cached=False)
        assert not retrieved

    def test_set_user_override_invalid_block(self):
        items = make_items()
        first = items[0]
        block_id = first[0]
        api.set_dates_for_course(six.text_type(block_id.course_key), items)

        with self.assertRaises(api.MissingDateError):
            # can't set a user override for content without a date
            bad_block_id = make_block_id()
            api.set_date_for_block(bad_block_id.course_key,
                                   bad_block_id,
                                   'due',
                                   datetime(2019, 4, 6),
                                   user=self.user)

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 3)),
        (datetime(2019, 4, 6), timedelta(days=-1)),
        (timedelta(days=5), timedelta(days=-1)),
    )
    @ddt.unpack
    def test_set_user_override_invalid_date(self, initial_date, override_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date
        api.set_dates_for_course(six.text_type(block_id.course_key), items)

        with self.assertRaises(api.InvalidDateError):
            api.set_date_for_block(block_id.course_key,
                                   block_id,
                                   'due',
                                   override_date,
                                   user=self.user)

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (datetime(2019, 4, 6), timedelta(days=3), datetime(2019, 4, 9)),
        (timedelta(days=3), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (timedelta(days=3), timedelta(days=2), datetime(2019, 4, 6)),
    )
    @ddt.unpack
    def test_set_user_override(self, initial_date, override_date,
                               expected_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date

        api.set_dates_for_course(six.text_type(block_id.course_key), items)

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               override_date,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        assert retrieved[block_id, 'due'] == expected_date

        overrides = api.get_overrides_for_block(block_id.course_key, block_id)
        assert len(overrides) == 1
        assert overrides[0][2] == expected_date

        overrides = list(
            api.get_overrides_for_user(block_id.course_key, self.user))
        assert len(overrides) == 1
        assert overrides[0] == {
            'location': block_id,
            'actual_date': expected_date
        }

    @ddt.data(
        (datetime(2019, 4, 6), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (datetime(2019, 4, 6), timedelta(days=3), datetime(2019, 4, 9)),
        (timedelta(days=3), datetime(2019, 4, 10), datetime(2019, 4, 10)),
        (timedelta(days=3), timedelta(days=2), datetime(2019, 4, 6)),
    )
    @ddt.unpack
    def test_remove_user_override(self, initial_date, override_date,
                                  expected_date):
        items = make_items()
        first = items[0]
        block_id = first[0]
        items[0][1]['due'] = initial_date

        api.set_dates_for_course(six.text_type(block_id.course_key), items)

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               override_date,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        assert retrieved[block_id, 'due'] == expected_date

        api.set_date_for_block(block_id.course_key,
                               block_id,
                               'due',
                               None,
                               user=self.user)
        DEFAULT_REQUEST_CACHE.clear()
        retrieved = api.get_dates_for_course(block_id.course_key,
                                             user=self.user.id)
        assert len(retrieved) == NUM_OVERRIDES
        if isinstance(initial_date, timedelta):
            user_initial_date = self.schedule.start_date + initial_date
        else:
            user_initial_date = initial_date
        assert retrieved[block_id, 'due'] == user_initial_date

    def test_get_date_for_block(self):
        items = make_items()
        course_id = items[0][0].course_key
        api.set_dates_for_course(course_id, items)
        block_id, data = items[0]
        assert api.get_date_for_block(course_id, block_id,
                                      user=self.user) == data['due']
        assert api.get_date_for_block(course_id, 'bad', user=self.user) is None

    def test_is_enabled(self):
        items = make_items()
        course_id = items[0][0].course_key
        assert not api.is_enabled_for_course(course_id)
        api.set_dates_for_course(course_id, items)
        assert api.is_enabled_for_course(course_id)

    def test_allow_relative_dates(self):
        course_key = CourseLocator('testX', 'tt101', '2019')
        block1 = make_block_id(course_key)
        date1 = datetime(2019, 3, 22)
        block2 = make_block_id(course_key)
        date2 = datetime(2019, 3, 23)
        date2_override_delta = timedelta(days=10)
        date2_override = date2 + date2_override_delta
        block3 = make_block_id(course_key)
        date3_delta = timedelta(days=1)
        date3 = self.schedule.start_date + date3_delta
        block4 = make_block_id(course_key)
        date4_delta = timedelta(days=2)
        date4 = self.schedule.start_date + date4_delta
        date4_override = datetime(2019, 4, 24)
        items = [
            (block1, {
                'due': date1
            }),  # absolute
            (block2, {
                'due': date2
            }),  # absolute, to be overwritten by relative date
            (block3, {
                'due': date3_delta
            }),  # relative
            (block4, {
                'due': date4_delta
            }),  # relative, to be overwritten by absolute date
        ]
        api.set_dates_for_course(course_key, items)
        api.set_date_for_block(course_key,
                               block2,
                               'due',
                               date2_override_delta,
                               user=self.user)
        api.set_date_for_block(course_key,
                               block4,
                               'due',
                               date4_override,
                               user=self.user)

        # get_dates_for_course
        dates = [
            ((block1, 'due'), date1),
            ((block2, 'due'), date2),
            ((block3, 'due'), date3),
            ((block4, 'due'), date4),
        ]
        assert api.get_dates_for_course(course_key,
                                        schedule=self.schedule) == dict(dates)
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_dates_for_course(
                course_key, schedule=self.schedule) == dict(dates[0:2])
            assert api.get_dates_for_course(course_key,
                                            schedule=self.schedule,
                                            user=self.user) == dict(dates[0:2])

        # get_date_for_block
        assert api.get_date_for_block(course_key, block2) == date2
        assert api.get_date_for_block(course_key, block4,
                                      user=self.user) == date4_override
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_date_for_block(course_key, block2) == date2
            assert api.get_date_for_block(course_key, block1,
                                          user=self.user) == date1
            assert api.get_date_for_block(course_key, block2,
                                          user=self.user) == date2
            assert api.get_date_for_block(course_key, block4,
                                          user=self.user) is None

        # get_overrides_for_block
        block2_overrides = [(self.user.username, 'unknown', date2_override)]
        assert api.get_overrides_for_block(course_key,
                                           block2) == block2_overrides
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert api.get_overrides_for_block(course_key, block2) == []

        # get_overrides_for_user
        user_overrides = [
            {
                'location': block4,
                'actual_date': date4_override
            },
            {
                'location': block2,
                'actual_date': date2_override
            },
        ]
        assert list(api.get_overrides_for_user(course_key,
                                               self.user)) == user_overrides
        with patch('edx_when.api._are_relative_dates_enabled',
                   return_value=False):
            assert list(api.get_overrides_for_user(
                course_key, self.user)) == [user_overrides[0]]