Skip to content

Commit 5ca5d06

Browse files
committed
Merge branch 'master' into rpenido/fal-4005/list-courses-using-library
2 parents 88f6abd + 6124695 commit 5ca5d06

File tree

49 files changed

+1510
-79
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1510
-79
lines changed

.github/workflows/unit-test-shards.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
"cms/djangoapps/cms_user_tasks/",
239239
"cms/djangoapps/course_creators/",
240240
"cms/djangoapps/export_course_metadata/",
241+
"cms/djangoapps/maintenance/",
241242
"cms/djangoapps/models/",
242243
"cms/djangoapps/pipeline_js/",
243244
"cms/djangoapps/xblock_config/",

cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@
9898
from xmodule.modulestore.django import modulestore
9999
from xmodule.modulestore.exceptions import ItemNotFoundError
100100

101-
102101
logger = logging.getLogger(__name__)
103102

104103

cms/djangoapps/maintenance/__init__.py

Whitespace-only changes.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
"""
2+
Tests for the maintenance app views.
3+
"""
4+
5+
6+
import ddt
7+
from django.conf import settings
8+
from django.urls import reverse
9+
10+
from common.djangoapps.student.tests.factories import AdminFactory, UserFactory
11+
from openedx.features.announcements.models import Announcement
12+
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
13+
14+
from .views import MAINTENANCE_VIEWS
15+
16+
# This list contains URLs of all maintenance app views.
17+
MAINTENANCE_URLS = [reverse(view['url']) for view in MAINTENANCE_VIEWS.values()]
18+
19+
20+
class TestMaintenanceIndex(ModuleStoreTestCase):
21+
"""
22+
Tests for maintenance index view.
23+
"""
24+
25+
def setUp(self):
26+
super().setUp()
27+
self.user = AdminFactory()
28+
login_success = self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
29+
self.assertTrue(login_success)
30+
self.view_url = reverse('maintenance:maintenance_index')
31+
32+
def test_maintenance_index(self):
33+
"""
34+
Test that maintenance index view lists all the maintenance app views.
35+
"""
36+
response = self.client.get(self.view_url)
37+
self.assertContains(response, 'Maintenance', status_code=200)
38+
39+
# Check that all the expected links appear on the index page.
40+
for url in MAINTENANCE_URLS:
41+
self.assertContains(response, url, status_code=200)
42+
43+
44+
@ddt.ddt
45+
class MaintenanceViewTestCase(ModuleStoreTestCase):
46+
"""
47+
Base class for maintenance view tests.
48+
"""
49+
view_url = ''
50+
51+
def setUp(self):
52+
super().setUp()
53+
self.user = AdminFactory()
54+
login_success = self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
55+
self.assertTrue(login_success)
56+
57+
def verify_error_message(self, data, error_message):
58+
"""
59+
Verify the response contains error message.
60+
"""
61+
response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
62+
self.assertContains(response, error_message, status_code=200)
63+
64+
def tearDown(self):
65+
"""
66+
Reverse the setup.
67+
"""
68+
self.client.logout()
69+
super().tearDown()
70+
71+
72+
@ddt.ddt
73+
class MaintenanceViewAccessTests(MaintenanceViewTestCase):
74+
"""
75+
Tests for access control of maintenance views.
76+
"""
77+
@ddt.data(*MAINTENANCE_URLS)
78+
def test_require_login(self, url):
79+
"""
80+
Test that maintenance app requires user login.
81+
"""
82+
# Log out then try to retrieve the page
83+
self.client.logout()
84+
response = self.client.get(url)
85+
86+
# Expect a redirect to the login page
87+
redirect_url = '{login_url}?next={original_url}'.format(
88+
login_url=settings.LOGIN_URL,
89+
original_url=url,
90+
)
91+
92+
# Studio login redirects to LMS login
93+
self.assertRedirects(response, redirect_url, target_status_code=302)
94+
95+
@ddt.data(*MAINTENANCE_URLS)
96+
def test_global_staff_access(self, url):
97+
"""
98+
Test that all maintenance app views are accessible to global staff user.
99+
"""
100+
response = self.client.get(url)
101+
self.assertEqual(response.status_code, 200)
102+
103+
@ddt.data(*MAINTENANCE_URLS)
104+
def test_non_global_staff_access(self, url):
105+
"""
106+
Test that all maintenance app views are not accessible to non-global-staff user.
107+
"""
108+
user = UserFactory(username='test', email='test@example.com', password=self.TEST_PASSWORD)
109+
login_success = self.client.login(username=user.username, password=self.TEST_PASSWORD)
110+
self.assertTrue(login_success)
111+
112+
response = self.client.get(url)
113+
self.assertContains(
114+
response,
115+
f'Must be {settings.PLATFORM_NAME} staff to perform this action.',
116+
status_code=403
117+
)
118+
119+
120+
@ddt.ddt
121+
class TestAnnouncementsViews(MaintenanceViewTestCase):
122+
"""
123+
Tests for the announcements edit view.
124+
"""
125+
126+
def setUp(self):
127+
super().setUp()
128+
self.admin = AdminFactory.create(
129+
email='staff@edx.org',
130+
username='admin',
131+
password=self.TEST_PASSWORD
132+
)
133+
self.client.login(username=self.admin.username, password=self.TEST_PASSWORD)
134+
self.non_staff_user = UserFactory.create(
135+
email='test@edx.org',
136+
username='test',
137+
password=self.TEST_PASSWORD
138+
)
139+
140+
def test_index(self):
141+
"""
142+
Test create announcement view
143+
"""
144+
url = reverse("maintenance:announcement_index")
145+
response = self.client.get(url)
146+
self.assertContains(response, '<div class="announcement-container">')
147+
148+
def test_create(self):
149+
"""
150+
Test create announcement view
151+
"""
152+
url = reverse("maintenance:announcement_create")
153+
self.client.post(url, {"content": "Test Create Announcement", "active": True})
154+
result = Announcement.objects.filter(content="Test Create Announcement").exists()
155+
self.assertTrue(result)
156+
157+
def test_edit(self):
158+
"""
159+
Test edit announcement view
160+
"""
161+
announcement = Announcement.objects.create(content="test")
162+
announcement.save()
163+
url = reverse("maintenance:announcement_edit", kwargs={"pk": announcement.pk})
164+
response = self.client.get(url)
165+
self.assertContains(response, '<div class="wrapper-form announcement-container">')
166+
self.client.post(url, {"content": "Test Edit Announcement", "active": True})
167+
announcement = Announcement.objects.get(pk=announcement.pk)
168+
self.assertEqual(announcement.content, "Test Edit Announcement")
169+
170+
def test_delete(self):
171+
"""
172+
Test delete announcement view
173+
"""
174+
announcement = Announcement.objects.create(content="Test Delete")
175+
announcement.save()
176+
url = reverse("maintenance:announcement_delete", kwargs={"pk": announcement.pk})
177+
self.client.post(url)
178+
result = Announcement.objects.filter(content="Test Edit Announcement").exists()
179+
self.assertFalse(result)
180+
181+
def _test_403(self, viewname, kwargs=None):
182+
url = reverse("maintenance:%s" % viewname, kwargs=kwargs)
183+
response = self.client.get(url)
184+
self.assertEqual(response.status_code, 403)
185+
186+
def test_authorization(self):
187+
self.client.login(username=self.non_staff_user, password=self.TEST_PASSWORD)
188+
announcement = Announcement.objects.create(content="Test Delete")
189+
announcement.save()
190+
191+
self._test_403("announcement_index")
192+
self._test_403("announcement_create")
193+
self._test_403("announcement_edit", {"pk": announcement.pk})
194+
self._test_403("announcement_delete", {"pk": announcement.pk})

cms/djangoapps/maintenance/urls.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
URLs for the maintenance app.
3+
"""
4+
5+
from django.urls import path, re_path
6+
7+
from .views import (
8+
AnnouncementCreateView,
9+
AnnouncementDeleteView,
10+
AnnouncementEditView,
11+
AnnouncementIndexView,
12+
MaintenanceIndexView
13+
)
14+
15+
app_name = 'cms.djangoapps.maintenance'
16+
17+
urlpatterns = [
18+
path('', MaintenanceIndexView.as_view(), name='maintenance_index'),
19+
re_path(r'^announcements/(?P<page>\d+)?$', AnnouncementIndexView.as_view(), name='announcement_index'),
20+
path('announcements/create', AnnouncementCreateView.as_view(), name='announcement_create'),
21+
re_path(r'^announcements/edit/(?P<pk>\d+)?$', AnnouncementEditView.as_view(), name='announcement_edit'),
22+
path('announcements/delete/<int:pk>', AnnouncementDeleteView.as_view(), name='announcement_delete'),
23+
]

0 commit comments

Comments
 (0)