test_models.py 12.3 KB
Newer Older
1 2 3
"""
tests for the models
"""
4 5
from datetime import datetime, timedelta
from django.utils.timezone import UTC
6
from mock import patch
7
from nose.plugins.attrib import attr
8 9 10
from student.models import CourseEnrollment  # pylint: disable=import-error
from student.roles import CourseCcxCoachRole  # pylint: disable=import-error
from student.tests.factories import (  # pylint: disable=import-error
11 12 13 14
    AdminFactory,
    CourseEnrollmentFactory,
    UserFactory,
)
15
from util.tests.test_date_utils import fake_ugettext
16
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
17 18 19 20
from xmodule.modulestore.tests.factories import (
    CourseFactory,
    check_mongo_calls
)
21 22

from .factories import (
cewing committed
23 24
    CcxFactory,
    CcxFutureMembershipFactory,
25 26
)
from ..models import (
cewing committed
27 28
    CcxMembership,
    CcxFutureMembership,
29
)
30
from ..overrides import override_field_for_ccx
31 32


33
@attr('shard_1')
cewing committed
34 35
class TestCcxMembership(ModuleStoreTestCase):
    """Unit tests for the CcxMembership model
36 37 38 39
    """

    def setUp(self):
        """common setup for all tests"""
cewing committed
40
        super(TestCcxMembership, self).setUp()
41 42
        self.course = course = CourseFactory.create()
        coach = AdminFactory.create()
cewing committed
43
        role = CourseCcxCoachRole(course.id)
44
        role.add_users(coach)
cewing committed
45
        self.ccx = CcxFactory(course_id=course.id, coach=coach)
46 47 48 49 50
        enrollment = CourseEnrollmentFactory.create(course_id=course.id)
        self.enrolled_user = enrollment.user
        self.unenrolled_user = UserFactory.create()

    def create_future_enrollment(self, user, auto_enroll=True):
51 52 53
        """
        utility method to create future enrollment
        """
cewing committed
54 55
        pfm = CcxFutureMembershipFactory.create(
            ccx=self.ccx,
56 57 58 59 60 61
            email=user.email,
            auto_enroll=auto_enroll
        )
        return pfm

    def has_course_enrollment(self, user):
62 63 64
        """
        utility method to create future enrollment
        """
65 66 67 68 69
        enrollment = CourseEnrollment.objects.filter(
            user=user, course_id=self.course.id
        )
        return enrollment.exists()

cewing committed
70
    def has_ccx_membership(self, user):
71 72 73
        """
        verify ccx membership
        """
cewing committed
74 75
        membership = CcxMembership.objects.filter(
            student=user, ccx=self.ccx, active=True
76 77 78
        )
        return membership.exists()

cewing committed
79
    def has_ccx_future_membership(self, user):
80 81 82
        """
        verify future ccx membership
        """
cewing committed
83 84
        future_membership = CcxFutureMembership.objects.filter(
            email=user.email, ccx=self.ccx
85 86 87
        )
        return future_membership.exists()

88 89 90 91
    def call_mut(self, student, future_membership):
        """
        Call the method undser test
        """
cewing committed
92
        CcxMembership.auto_enroll(student, future_membership)
93

cewing committed
94
    def test_ccx_auto_enroll_unregistered_user(self):
95 96 97
        """verify auto_enroll works when user is not enrolled in the MOOC

        n.b.  After auto_enroll, user will have both a MOOC enrollment and a
cewing committed
98
              CCX membership
99 100 101
        """
        user = self.unenrolled_user
        pfm = self.create_future_enrollment(user)
cewing committed
102
        self.assertTrue(self.has_ccx_future_membership(user))
103 104
        self.assertFalse(self.has_course_enrollment(user))
        # auto_enroll user
105
        self.call_mut(user, pfm)
106 107

        self.assertTrue(self.has_course_enrollment(user))
cewing committed
108 109
        self.assertTrue(self.has_ccx_membership(user))
        self.assertFalse(self.has_ccx_future_membership(user))
110

cewing committed
111
    def test_ccx_auto_enroll_registered_user(self):
112 113 114 115
        """verify auto_enroll works when user is enrolled in the MOOC
        """
        user = self.enrolled_user
        pfm = self.create_future_enrollment(user)
cewing committed
116
        self.assertTrue(self.has_ccx_future_membership(user))
117 118
        self.assertTrue(self.has_course_enrollment(user))

119
        self.call_mut(user, pfm)
120 121

        self.assertTrue(self.has_course_enrollment(user))
cewing committed
122 123
        self.assertTrue(self.has_ccx_membership(user))
        self.assertFalse(self.has_ccx_future_membership(user))
124 125

    def test_future_membership_disallows_auto_enroll(self):
cewing committed
126
        """verify that the CcxFutureMembership can veto auto_enroll
127 128 129
        """
        user = self.unenrolled_user
        pfm = self.create_future_enrollment(user, auto_enroll=False)
cewing committed
130
        self.assertTrue(self.has_ccx_future_membership(user))
131 132
        self.assertFalse(self.has_course_enrollment(user))

133
        self.assertRaises(ValueError, self.call_mut, user, pfm)
134 135

        self.assertFalse(self.has_course_enrollment(user))
cewing committed
136 137
        self.assertFalse(self.has_ccx_membership(user))
        self.assertTrue(self.has_ccx_future_membership(user))
138 139


140
@attr('shard_1')
141 142 143 144 145 146 147 148 149 150 151 152 153 154
class TestCCX(ModuleStoreTestCase):
    """Unit tests for the CustomCourseForEdX model
    """

    def setUp(self):
        """common setup for all tests"""
        super(TestCCX, self).setUp()
        self.course = course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=course.id, coach=coach)

    def set_ccx_override(self, field, value):
cewing committed
155
        """Create a field override for the test CCX on <field> with <value>"""
156 157 158 159 160 161 162 163 164 165 166
        override_field_for_ccx(self.ccx, self.course, field, value)

    def test_ccx_course_is_correct_course(self):
        """verify that the course property of a ccx returns the right course"""
        expected = self.course
        actual = self.ccx.course
        self.assertEqual(expected, actual)

    def test_ccx_course_caching(self):
        """verify that caching the propery works to limit queries"""
        with check_mongo_calls(1):
cewing committed
167 168 169 170
            # these statements are used entirely to demonstrate the
            # instance-level caching of these values on CCX objects. The
            # check_mongo_calls context is the point here.
            self.ccx.course  # pylint: disable=pointless-statement
171
        with check_mongo_calls(0):
cewing committed
172
            self.ccx.course  # pylint: disable=pointless-statement
173 174 175 176 177 178 179 180 181 182 183

    def test_ccx_start_is_correct(self):
        """verify that the start datetime for a ccx is correctly retrieved

        Note that after setting the start field override microseconds are
        truncated, so we can't do a direct comparison between before and after.
        For this reason we test the difference between and make sure it is less
        than one second.
        """
        expected = datetime.now(UTC())
        self.set_ccx_override('start', expected)
cewing committed
184
        actual = self.ccx.start  # pylint: disable=no-member
185
        diff = expected - actual
186
        self.assertTrue(abs(diff.total_seconds()) < 1)
187 188 189 190 191 192

    def test_ccx_start_caching(self):
        """verify that caching the start property works to limit queries"""
        now = datetime.now(UTC())
        self.set_ccx_override('start', now)
        with check_mongo_calls(1):
cewing committed
193 194 195 196
            # these statements are used entirely to demonstrate the
            # instance-level caching of these values on CCX objects. The
            # check_mongo_calls context is the point here.
            self.ccx.start  # pylint: disable=pointless-statement, no-member
197
        with check_mongo_calls(0):
cewing committed
198
            self.ccx.start  # pylint: disable=pointless-statement, no-member
199 200 201

    def test_ccx_due_without_override(self):
        """verify that due returns None when the field has not been set"""
cewing committed
202
        actual = self.ccx.due  # pylint: disable=no-member
203
        self.assertIsNone(actual)
204 205 206 207 208

    def test_ccx_due_is_correct(self):
        """verify that the due datetime for a ccx is correctly retrieved"""
        expected = datetime.now(UTC())
        self.set_ccx_override('due', expected)
cewing committed
209
        actual = self.ccx.due  # pylint: disable=no-member
210
        diff = expected - actual
211
        self.assertTrue(abs(diff.total_seconds()) < 1)
212 213 214 215 216 217

    def test_ccx_due_caching(self):
        """verify that caching the due property works to limit queries"""
        expected = datetime.now(UTC())
        self.set_ccx_override('due', expected)
        with check_mongo_calls(1):
cewing committed
218 219 220 221
            # these statements are used entirely to demonstrate the
            # instance-level caching of these values on CCX objects. The
            # check_mongo_calls context is the point here.
            self.ccx.due  # pylint: disable=pointless-statement, no-member
222
        with check_mongo_calls(0):
cewing committed
223
            self.ccx.due  # pylint: disable=pointless-statement, no-member
224 225 226 227 228 229 230

    def test_ccx_has_started(self):
        """verify that a ccx marked as starting yesterday has started"""
        now = datetime.now(UTC())
        delta = timedelta(1)
        then = now - delta
        self.set_ccx_override('start', then)
cewing committed
231
        self.assertTrue(self.ccx.has_started())  # pylint: disable=no-member
232 233 234 235 236 237 238

    def test_ccx_has_not_started(self):
        """verify that a ccx marked as starting tomorrow has not started"""
        now = datetime.now(UTC())
        delta = timedelta(1)
        then = now + delta
        self.set_ccx_override('start', then)
cewing committed
239
        self.assertFalse(self.ccx.has_started())  # pylint: disable=no-member
240 241 242 243 244 245 246

    def test_ccx_has_ended(self):
        """verify that a ccx that has a due date in the past has ended"""
        now = datetime.now(UTC())
        delta = timedelta(1)
        then = now - delta
        self.set_ccx_override('due', then)
cewing committed
247
        self.assertTrue(self.ccx.has_ended())  # pylint: disable=no-member
248 249 250 251 252 253 254 255

    def test_ccx_has_not_ended(self):
        """verify that a ccx that has a due date in the future has not eneded
        """
        now = datetime.now(UTC())
        delta = timedelta(1)
        then = now + delta
        self.set_ccx_override('due', then)
cewing committed
256
        self.assertFalse(self.ccx.has_ended())  # pylint: disable=no-member
257 258 259

    def test_ccx_without_due_date_has_not_ended(self):
        """verify that a ccx without a due date has not ended"""
cewing committed
260
        self.assertFalse(self.ccx.has_ended())  # pylint: disable=no-member
261

262 263 264 265 266
    # ensure that the expected localized format will be found by the i18n
    # service
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "SHORT_DATE_FORMAT": "%b %d, %Y",
    }))
267 268 269 270 271
    def test_start_datetime_short_date(self):
        """verify that the start date for a ccx formats properly by default"""
        start = datetime(2015, 1, 1, 12, 0, 0, tzinfo=UTC())
        expected = "Jan 01, 2015"
        self.set_ccx_override('start', start)
cewing committed
272
        actual = self.ccx.start_datetime_text()  # pylint: disable=no-member
273 274
        self.assertEqual(expected, actual)

275 276 277
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M",
    }))
278 279 280 281 282
    def test_start_datetime_date_time_format(self):
        """verify that the DATE_TIME format also works as expected"""
        start = datetime(2015, 1, 1, 12, 0, 0, tzinfo=UTC())
        expected = "Jan 01, 2015 at 12:00 UTC"
        self.set_ccx_override('start', start)
cewing committed
283
        actual = self.ccx.start_datetime_text('DATE_TIME')  # pylint: disable=no-member
284 285
        self.assertEqual(expected, actual)

286 287 288
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "SHORT_DATE_FORMAT": "%b %d, %Y",
    }))
289 290 291 292 293
    def test_end_datetime_short_date(self):
        """verify that the end date for a ccx formats properly by default"""
        end = datetime(2015, 1, 1, 12, 0, 0, tzinfo=UTC())
        expected = "Jan 01, 2015"
        self.set_ccx_override('due', end)
cewing committed
294
        actual = self.ccx.end_datetime_text()  # pylint: disable=no-member
295 296
        self.assertEqual(expected, actual)

297 298 299
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M",
    }))
300 301 302 303 304
    def test_end_datetime_date_time_format(self):
        """verify that the DATE_TIME format also works as expected"""
        end = datetime(2015, 1, 1, 12, 0, 0, tzinfo=UTC())
        expected = "Jan 01, 2015 at 12:00 UTC"
        self.set_ccx_override('due', end)
cewing committed
305
        actual = self.ccx.end_datetime_text('DATE_TIME')  # pylint: disable=no-member
306 307
        self.assertEqual(expected, actual)

308 309 310
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M",
    }))
311 312 313
    def test_end_datetime_no_due_date(self):
        """verify that without a due date, the end date is an empty string"""
        expected = ''
cewing committed
314
        actual = self.ccx.end_datetime_text()  # pylint: disable=no-member
315
        self.assertEqual(expected, actual)
cewing committed
316
        actual = self.ccx.end_datetime_text('DATE_TIME')  # pylint: disable=no-member
317
        self.assertEqual(expected, actual)