test_programs.py 9.04 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
"""
3
Unit tests covering the program listing and detail pages.
4
"""
5
import json
6
import re
7
from urlparse import urljoin
8
from uuid import uuid4
9

10
import mock
11
from bs4 import BeautifulSoup
12 13
from django.conf import settings
from django.core.urlresolvers import reverse
14
from django.test import override_settings
15

16
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory, CourseRunFactory, ProgramFactory
17
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
18
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
19
from openedx.core.djangolib.testing.utils import skip_unless_lms
20
from student.tests.factories import CourseEnrollmentFactory, UserFactory
21
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
22
from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
23

24
PROGRAMS_UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
25 26


27
@skip_unless_lms
28
@override_settings(MKTG_URLS={'ROOT': 'https://www.example.com'})
29
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
30
class TestProgramListing(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
31 32 33
    """Unit tests for the program listing page."""
    maxDiff = None
    password = 'test'
34
    url = reverse('program_listing_view')
35

36 37 38 39
    @classmethod
    def setUpClass(cls):
        super(TestProgramListing, cls).setUpClass()

40 41 42
        cls.course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=unicode(cls.course.id))  # pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])
43

44 45
        cls.first_program = ProgramFactory(courses=[course])
        cls.second_program = ProgramFactory(courses=[course])
46 47 48

        cls.data = sorted([cls.first_program, cls.second_program], key=cls.program_sort_key)

49
    def setUp(self):
50 51 52 53 54 55 56
        super(TestProgramListing, self).setUp()

        self.user = UserFactory()
        self.client.login(username=self.user.username, password=self.password)

    @classmethod
    def program_sort_key(cls, program):
57
        """
58
        Helper function used to sort dictionaries representing programs.
59
        """
60
        return program['title']
61

62
    def load_serialized_data(self, response, key):
63
        """
64
        Extract and deserialize serialized data from the response.
65
        """
66 67 68
        pattern = re.compile(r'{key}: (?P<data>\[.*\])'.format(key=key))
        match = pattern.search(response.content)
        serialized = match.group('data')
69

70 71 72
        return json.loads(serialized)

    def assert_dict_contains_subset(self, superset, subset):
73
        """
74 75 76 77
        Verify that the dict superset contains the dict subset.

        Works like assertDictContainsSubset, deprecated since Python 3.2.
        See: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertDictContainsSubset.
78
        """
79 80 81 82 83
        superset_keys = set(superset.keys())
        subset_keys = set(subset.keys())
        intersection = {key: superset[key] for key in superset_keys & subset_keys}

        self.assertEqual(subset, intersection)
84

85
    def test_login_required(self, mock_get_programs):
86
        """
87
        Verify that login is required to access the page.
88
        """
89
        self.create_programs_config()
90
        mock_get_programs.return_value = self.data
91

92
        self.client.logout()
93

94 95 96 97 98 99 100
        response = self.client.get(self.url)
        self.assertRedirects(
            response,
            '{}?next={}'.format(reverse('signin_user'), self.url)
        )

        self.client.login(username=self.user.username, password=self.password)
101

102 103
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
104

105
    def test_404_if_disabled(self, _mock_get_programs):
106 107 108
        """
        Verify that the page 404s if disabled.
        """
109
        self.create_programs_config(enabled=False)
110

111
        response = self.client.get(self.url)
112 113
        self.assertEqual(response.status_code, 404)

114
    def test_empty_state(self, mock_get_programs):
115 116 117 118
        """
        Verify that the response contains no programs data when no programs are engaged.
        """
        self.create_programs_config()
119
        mock_get_programs.return_value = self.data
120

121
        response = self.client.get(self.url)
122
        self.assertContains(response, 'programsData: []')
123

124
    def test_programs_listed(self, mock_get_programs):
125 126 127
        """
        Verify that the response contains accurate programs data when programs are engaged.
        """
128
        self.create_programs_config()
129
        mock_get_programs.return_value = self.data
130 131 132

        CourseEnrollmentFactory(user=self.user, course_id=self.course.id)  # pylint: disable=no-member

133
        response = self.client.get(self.url)
134 135
        actual = self.load_serialized_data(response, 'programsData')
        actual = sorted(actual, key=self.program_sort_key)
136

137
        for index, actual_program in enumerate(actual):
138
            expected_program = self.data[index]
139 140
            self.assert_dict_contains_subset(actual_program, expected_program)

141
    def test_program_discovery(self, mock_get_programs):
142
        """
143
        Verify that a link to a programs marketing page appears in the response.
144
        """
145
        self.create_programs_config(marketing_path='bar')
146
        mock_get_programs.return_value = self.data
147

148
        marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'bar').rstrip('/')
149 150

        response = self.client.get(self.url)
151
        self.assertContains(response, marketing_root)
152

153
    def test_links_to_detail_pages(self, mock_get_programs):
154
        """
155
        Verify that links to detail pages are present.
156
        """
157
        self.create_programs_config()
158
        mock_get_programs.return_value = self.data
159 160 161 162 163 164 165 166 167

        CourseEnrollmentFactory(user=self.user, course_id=self.course.id)  # pylint: disable=no-member

        response = self.client.get(self.url)
        actual = self.load_serialized_data(response, 'programsData')
        actual = sorted(actual, key=self.program_sort_key)

        for index, actual_program in enumerate(actual):
            expected_program = self.data[index]
168

169 170
            expected_url = reverse('program_details_view', kwargs={'program_uuid': expected_program['uuid']})
            self.assertEqual(actual_program['detail_url'], expected_url)
171

172

173
@skip_unless_lms
174
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
175
class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, SharedModuleStoreTestCase):
176
    """Unit tests for the program details page."""
177
    program_uuid = str(uuid4())
178
    password = 'test'
179
    url = reverse('program_details_view', kwargs={'program_uuid': program_uuid})
180

181 182 183
    @classmethod
    def setUpClass(cls):
        super(TestProgramDetails, cls).setUpClass()
184

185 186 187
        modulestore_course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=unicode(modulestore_course.id))  # pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])
188

189
        cls.data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
190

191 192
    def setUp(self):
        super(TestProgramDetails, self).setUp()
193

194
        self.user = UserFactory()
195 196
        self.client.login(username=self.user.username, password=self.password)

197
    def assert_program_data_present(self, response):
198 199
        """Verify that program data is present."""
        self.assertContains(response, 'programData')
200 201
        self.assertContains(response, 'urls')
        self.assertContains(response, 'program_listing_url')
202
        self.assertContains(response, self.data['title'])
203
        self.assert_programs_tab_present(response)
204

205
    def assert_programs_tab_present(self, response):
206 207 208 209 210
        """Verify that the programs tab is present in the nav."""
        soup = BeautifulSoup(response.content, 'html.parser')
        self.assertTrue(
            any(soup.find_all('a', class_='tab-nav-link', href=reverse('program_listing_view')))
        )
211

212
    def test_login_required(self, mock_get_programs):
213 214 215 216
        """
        Verify that login is required to access the page.
        """
        self.create_programs_config()
217 218 219 220

        catalog_integration = self.create_catalog_integration()
        UserFactory(username=catalog_integration.service_username)

221
        mock_get_programs.return_value = self.data
222 223 224

        self.client.logout()

225
        response = self.client.get(self.url)
226 227
        self.assertRedirects(
            response,
228
            '{}?next={}'.format(reverse('signin_user'), self.url)
229 230
        )

231
        self.client.login(username=self.user.username, password=self.password)
232

233 234
        response = self.client.get(self.url)
        self.assert_program_data_present(response)
235

236
    def test_404_if_disabled(self, _mock_get_programs):
237 238 239
        """
        Verify that the page 404s if disabled.
        """
240
        self.create_programs_config(enabled=False)
241

242 243
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 404)
244

245
    def test_404_if_no_data(self, mock_get_programs):
246
        """Verify that the page 404s if no program data is found."""
247 248
        self.create_programs_config()

249 250
        mock_get_programs.return_value = None

251 252
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 404)