test_api.py 8.68 KB
Newer Older
1 2 3
"""
Test for course API
"""
4
from hashlib import md5
5 6

from django.contrib.auth.models import AnonymousUser
7
from django.http import Http404
8
from opaque_keys.edx.keys import CourseKey
9
from rest_framework.exceptions import PermissionDenied
10 11
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory
12

13
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
14
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
15
from xmodule.modulestore.tests.factories import check_mongo_calls
16

17
from ..api import course_detail, list_courses
18
from .mixins import CourseApiFactoryMixin
19 20 21 22 23 24


class CourseApiTestMixin(CourseApiFactoryMixin):
    """
    Establish basic functionality for Course API tests
    """
25 26 27 28
    @classmethod
    def setUpClass(cls):
        super(CourseApiTestMixin, cls).setUpClass()
        cls.request_factory = APIRequestFactory()
29
        CourseOverview.get_all_courses()  # seed the CourseOverview table
30

31
    def verify_course(self, course, course_id=u'edX/toy/2012_Fall'):
32 33 34 35 36
        """
        Ensure that the returned course is the course we just created
        """
        self.assertEqual(course_id, str(course.id))

37 38 39 40 41

class CourseDetailTestMixin(CourseApiTestMixin):
    """
    Common functionality for course_detail tests
    """
42 43
    ENABLED_SIGNALS = ['course_published']

44 45 46 47 48
    def _make_api_call(self, requesting_user, target_user, course_key):
        """
        Call the `course_detail` api endpoint to get information on the course
        identified by `course_key`.
        """
49
        request = Request(self.request_factory.get('/'))
50
        request.user = requesting_user
51 52
        with check_mongo_calls(0):
            return course_detail(request, target_user.username, course_key)
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67


class TestGetCourseDetail(CourseDetailTestMixin, SharedModuleStoreTestCase):
    """
    Test course_detail api function
    """
    @classmethod
    def setUpClass(cls):
        super(TestGetCourseDetail, cls).setUpClass()
        cls.course = cls.create_course()
        cls.hidden_course = cls.create_course(course=u'hidden', visible_to_staff_only=True)
        cls.honor_user = cls.create_user('honor', is_staff=False)
        cls.staff_user = cls.create_user('staff', is_staff=True)

    def test_get_existing_course(self):
68 69
        course = self._make_api_call(self.honor_user, self.honor_user, self.course.id)
        self.verify_course(course)
70 71 72

    def test_get_nonexistent_course(self):
        course_key = CourseKey.from_string(u'edX/toy/nope')
73
        with self.assertRaises(Http404):
74
            self._make_api_call(self.honor_user, self.honor_user, course_key)
75 76

    def test_hidden_course_for_honor(self):
77
        with self.assertRaises(Http404):
78
            self._make_api_call(self.honor_user, self.honor_user, self.hidden_course.id)
79 80

    def test_hidden_course_for_staff(self):
81 82
        course = self._make_api_call(self.staff_user, self.staff_user, self.hidden_course.id)
        self.verify_course(course, course_id=u'edX/hidden/2012_Fall')
83 84

    def test_hidden_course_for_staff_as_honor(self):
85
        with self.assertRaises(Http404):
86
            self._make_api_call(self.staff_user, self.honor_user, self.hidden_course.id)
87 88 89 90 91 92


class CourseListTestMixin(CourseApiTestMixin):
    """
    Common behavior for list_courses tests
    """
93
    def _make_api_call(self, requesting_user, specified_user, org=None, filter_=None):
94 95 96 97
        """
        Call the list_courses api endpoint to get information about
        `specified_user` on behalf of `requesting_user`.
        """
98
        request = Request(self.request_factory.get('/'))
99
        request.user = requesting_user
100
        with check_mongo_calls(0):
101
            return list_courses(request, specified_user.username, org=org, filter_=filter_)
102

103 104 105 106 107 108 109
    def verify_courses(self, courses):
        """
        Verify that there is one course, and that it has the expected format.
        """
        self.assertEqual(len(courses), 1)
        self.verify_course(courses[0])

110 111 112 113 114

class TestGetCourseList(CourseListTestMixin, SharedModuleStoreTestCase):
    """
    Test the behavior of the `list_courses` api function.
    """
115
    ENABLED_SIGNALS = ['course_published']
116

117 118 119
    @classmethod
    def setUpClass(cls):
        super(TestGetCourseList, cls).setUpClass()
120
        cls.course = cls.create_course()
121 122 123 124 125 126
        cls.staff_user = cls.create_user("staff", is_staff=True)
        cls.honor_user = cls.create_user("honor", is_staff=False)

    def test_as_staff(self):
        courses = self._make_api_call(self.staff_user, self.staff_user)
        self.assertEqual(len(courses), 1)
127
        self.verify_courses(courses)
128 129 130

    def test_for_honor_user_as_staff(self):
        courses = self._make_api_call(self.staff_user, self.honor_user)
131
        self.verify_courses(courses)
132 133 134

    def test_as_honor(self):
        courses = self._make_api_call(self.honor_user, self.honor_user)
135
        self.verify_courses(courses)
136 137 138 139 140 141 142 143

    def test_for_staff_user_as_honor(self):
        with self.assertRaises(PermissionDenied):
            self._make_api_call(self.honor_user, self.staff_user)

    def test_as_anonymous(self):
        anonuser = AnonymousUser()
        courses = self._make_api_call(anonuser, anonuser)
144
        self.verify_courses(courses)
145 146 147 148 149 150

    def test_for_honor_user_as_anonymous(self):
        anonuser = AnonymousUser()
        with self.assertRaises(PermissionDenied):
            self._make_api_call(anonuser, self.staff_user)

151 152 153 154 155 156

class TestGetCourseListMultipleCourses(CourseListTestMixin, ModuleStoreTestCase):
    """
    Test the behavior of the `list_courses` api function (with tests that
    modify the courseware).
    """
157
    ENABLED_SIGNALS = ['course_published']
158 159 160 161 162 163 164

    def setUp(self):
        super(TestGetCourseListMultipleCourses, self).setUp()
        self.course = self.create_course()
        self.staff_user = self.create_user("staff", is_staff=True)
        self.honor_user = self.create_user("honor", is_staff=False)

165 166 167 168 169
    def test_multiple_courses(self):
        self.create_course(course='second')
        courses = self._make_api_call(self.honor_user, self.honor_user)
        self.assertEqual(len(courses), 2)

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    def test_filter_by_org(self):
        """Verify that courses are filtered by the provided org key."""
        # Create a second course to be filtered out of queries.
        alternate_course = self.create_course(
            org=md5(self.course.org).hexdigest()
        )

        self.assertNotEqual(alternate_course.org, self.course.org)

        # No filtering.
        unfiltered_courses = self._make_api_call(self.staff_user, self.staff_user)
        for org in [self.course.org, alternate_course.org]:
            self.assertTrue(
                any(course.org == org for course in unfiltered_courses)
            )

        # With filtering.
        filtered_courses = self._make_api_call(self.staff_user, self.staff_user, org=self.course.org)
        self.assertTrue(
            all(course.org == self.course.org for course in filtered_courses)
        )

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    def test_filter(self):
        # Create a second course to be filtered out of queries.
        alternate_course = self.create_course(course='mobile', mobile_available=True)

        test_cases = [
            (None, [alternate_course, self.course]),
            (dict(mobile_available=True), [alternate_course]),
            (dict(mobile_available=False), [self.course]),
        ]
        for filter_, expected_courses in test_cases:
            filtered_courses = self._make_api_call(self.staff_user, self.staff_user, filter_=filter_)
            self.assertEquals(
                {course.id for course in filtered_courses},
                {course.id for course in expected_courses},
                "testing course_api.api.list_courses with filter_={}".format(filter_),
            )

209 210 211 212 213 214

class TestGetCourseListExtras(CourseListTestMixin, ModuleStoreTestCase):
    """
    Tests of course_list api function that require alternative configurations
    of created courses.
    """
215 216
    ENABLED_SIGNALS = ['course_published']

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
    @classmethod
    def setUpClass(cls):
        super(TestGetCourseListExtras, cls).setUpClass()
        cls.staff_user = cls.create_user("staff", is_staff=True)
        cls.honor_user = cls.create_user("honor", is_staff=False)

    def test_no_courses(self):
        courses = self._make_api_call(self.honor_user, self.honor_user)
        self.assertEqual(len(courses), 0)

    def test_hidden_course_for_honor(self):
        self.create_course(visible_to_staff_only=True)
        courses = self._make_api_call(self.honor_user, self.honor_user)
        self.assertEqual(len(courses), 0)

    def test_hidden_course_for_staff(self):
        self.create_course(visible_to_staff_only=True)
        courses = self._make_api_call(self.staff_user, self.staff_user)
235
        self.verify_courses(courses)