Files
edx-platform/cms/djangoapps/contentstore/views/tests/test_user.py
Kyle McCormick 8e9f94424d feat!: Flip Studio MFE Waffle Flags to be On-By-Default (#36495)
This makes nearly all of Studio React-by-default by replacing the
"opt-in-to-React" flags with a set of parallel
"opt-out-of-React-and-use-the-legacy-experience" flags. Here is the
mapping:

* `contentstore.new_studio_mfe.use_new_unit_page` ->
  `!legacy_studio.unit_editor`
* `new_core_editors.use_new_problem_editor` ->
  `!legacy_studio.problem_editor`
* `new_core_editors.use_new_text_editor` ->
  `!legacy_studio.text_editor`
* `new_core_editors.use_new_video_editor` ->
  `!legacy_studio.video_editor`
* `new_studio_mfe.use_new_home_page` ->
  `!legacy_studio.home`
* `contentstore.new_studio_mfe.use_new_custom_pages` ->
  `!legacy_studio.custom_pages`
* `contentstore.new_studio_mfe.use_new_schedule_details_page` ->
  `!legacy_studio.schedule_details`
* `contentstore.new_studio_mfe.use_new_advanced_settings_page` ->
  `!legacy_studio.advanced_settings`
* `contentstore.new_studio_mfe.use_new_grading_page` ->
  `!legacy_studio.grading`
* `contentstore.new_studio_mfe.use_new_updates_page` ->
  `!legacy_studio.updates`
* `contentstore.new_studio_mfe.use_new_import_page` ->
  `!legacy_studio.import`
* `contentstore.new_studio_mfe.use_new_export_page` ->
  `!legacy_studio.export`
* `contentstore.new_studio_mfe.use_new_files_uploads_page` ->
  `!legacy_studio.files_uploads`
* `contentstore.new_studio_mfe.use_new_course_outline_page` ->
  `!legacy_studio.course_outline`
* `contentstore.new_studio_mfe.use_new_course_team_page` ->
  `!legacy_studio.course_team`
* `contentstore.new_studio_mfe.use_new_certificates_page` ->
  `!legacy_studio.certificates`
* `contentstore.new_studio_mfe.use_new_textbooks_page` ->
  `!legacy_studio.textbooks`
* `contentstore.new_studio_mfe.use_new_group_configurations_page` ->
  `!legacy_studio.configurations`

Part of: https://github.com/openedx/edx-platform/issues/36275
2025-04-24 12:34:35 -04:00

326 lines
13 KiB
Python

"""
Tests for contentstore/views/user.py.
"""
import json
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from edx_toggles.toggles.testutils import override_waffle_flag
from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import reverse_course_url
from common.djangoapps.student import auth
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from common.djangoapps.student.tests.factories import UserFactory
class UsersTestCase(CourseTestCase): # lint-amnesty, pylint: disable=missing-class-docstring
def setUp(self):
super().setUp()
self.ext_user = UserFactory.create(
username="joe", email="joe@comedycentral.com", password="haha",
)
self.ext_user.is_active = True
self.ext_user.is_staff = False
self.ext_user.save()
self.inactive_user = UserFactory.create(
username="carl", email="carl@comedycentral.com", password="haha",
)
self.inactive_user.is_active = False
self.inactive_user.is_staff = False
self.inactive_user.save()
self.index_url = self.course_team_url()
self.detail_url = self.course_team_url(email=self.ext_user.email)
self.inactive_detail_url = self.course_team_url(email=self.inactive_user.email)
self.invalid_detail_url = self.course_team_url(email='nonexistent@user.com')
def course_team_url(self, email=None):
return reverse_course_url(
'course_team_handler', self.course.id,
kwargs={'email': email} if email else {}
)
@override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True)
def test_index(self):
resp = self.client.get(self.index_url, HTTP_ACCEPT='text/html')
# ext_user is not currently a member of the course team, and so should
# not show up on the page.
self.assertNotContains(resp, self.ext_user.email)
@override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True)
def test_index_member(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.ext_user)
resp = self.client.get(self.index_url, HTTP_ACCEPT='text/html')
self.assertContains(resp, self.ext_user.email)
def test_detail(self):
resp = self.client.get(self.detail_url)
self.assertEqual(resp.status_code, 200)
result = json.loads(resp.content.decode('utf-8'))
self.assertEqual(result["role"], None)
self.assertTrue(result["active"])
def test_detail_inactive(self):
resp = self.client.get(self.inactive_detail_url)
self.assertEqual(resp.status_code, 200)
result = json.loads(resp.content.decode('utf-8'))
self.assertFalse(result["active"])
def test_detail_invalid(self):
resp = self.client.get(self.invalid_detail_url)
self.assertEqual(resp.status_code, 404)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
def test_detail_post(self):
resp = self.client.post(
self.detail_url,
data={"role": ""},
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
# no content: should not be in any roles
self.assertFalse(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
self.assertFalse(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
self.assert_not_enrolled()
def test_detail_post_staff(self):
resp = self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
self.assertFalse(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
self.assert_enrolled()
def test_detail_post_staff_other_inst(self):
auth.add_users(self.user, CourseInstructorRole(self.course.id), self.user)
resp = self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
self.assertFalse(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
self.assert_enrolled()
# check that other user is unchanged
user = User.objects.get(email=self.user.email)
self.assertTrue(auth.user_has_role(user, CourseInstructorRole(self.course.id)))
self.assertFalse(CourseStaffRole(self.course.id).has_user(user))
def test_detail_post_instructor(self):
resp = self.client.post(
self.detail_url,
data=json.dumps({"role": "instructor"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
self.assertFalse(CourseStaffRole(self.course.id).has_user(ext_user))
self.assert_enrolled()
def test_detail_post_missing_role(self):
resp = self.client.post(
self.detail_url,
data=json.dumps({"toys": "fun"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 400)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
self.assert_not_enrolled()
def test_detail_post_no_json(self):
resp = self.client.post(
self.detail_url,
data={"role": "staff"},
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
self.assertFalse(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
self.assert_enrolled()
def test_detail_delete_staff(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.ext_user)
resp = self.client.delete(
self.detail_url,
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertFalse(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
def test_detail_delete_instructor(self):
auth.add_users(self.user, CourseInstructorRole(self.course.id), self.ext_user, self.user)
resp = self.client.delete(
self.detail_url,
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertFalse(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
def test_delete_last_instructor(self):
auth.add_users(self.user, CourseInstructorRole(self.course.id), self.ext_user)
resp = self.client.delete(
self.detail_url,
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 400)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
def test_post_last_instructor(self):
auth.add_users(self.user, CourseInstructorRole(self.course.id), self.ext_user)
resp = self.client.post(
self.detail_url,
data={"role": "staff"},
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 400)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseInstructorRole(self.course.id)))
def test_permission_denied_self(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
self.user.is_staff = False
self.user.save()
self_url = self.course_team_url(email=self.user.email)
resp = self.client.post(
self_url,
data={"role": "instructor"},
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 403)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
def test_permission_denied_other(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
self.user.is_staff = False
self.user.save()
resp = self.client.post(
self.detail_url,
data={"role": "instructor"},
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 403)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
def test_staff_can_delete_self(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.user)
self.user.is_staff = False
self.user.save()
self_url = self.course_team_url(email=self.user.email)
resp = self.client.delete(self_url)
self.assertEqual(resp.status_code, 204)
# reload user from DB
user = User.objects.get(email=self.user.email)
self.assertFalse(auth.user_has_role(user, CourseStaffRole(self.course.id)))
def test_staff_cannot_delete_other(self):
auth.add_users(self.user, CourseStaffRole(self.course.id), self.user, self.ext_user)
self.user.is_staff = False
self.user.save()
resp = self.client.delete(self.detail_url)
self.assertEqual(resp.status_code, 403)
result = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", result)
# reload user from DB
ext_user = User.objects.get(email=self.ext_user.email)
self.assertTrue(auth.user_has_role(ext_user, CourseStaffRole(self.course.id)))
def test_user_not_initially_enrolled(self):
# Verify that ext_user is not enrolled in the new course before being added as a staff member.
self.assert_not_enrolled()
def test_remove_staff_does_not_unenroll(self):
# Add user with staff permissions.
self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assert_enrolled()
# Remove user from staff on course. Will not un-enroll them from the course.
resp = self.client.delete(
self.detail_url,
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
self.assert_enrolled()
def test_staff_to_instructor_still_enrolled(self):
# Add user with staff permission.
self.client.post(
self.detail_url,
data=json.dumps({"role": "staff"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assert_enrolled()
# Now add with instructor permission. Verify still enrolled.
resp = self.client.post(
self.detail_url,
data=json.dumps({"role": "instructor"}),
content_type="application/json",
HTTP_ACCEPT="application/json",
)
self.assertEqual(resp.status_code, 204)
self.assert_enrolled()
def assert_not_enrolled(self):
""" Asserts that self.ext_user is not enrolled in self.course. """
self.assertFalse(
CourseEnrollment.is_enrolled(self.ext_user, self.course.id),
'Did not expect ext_user to be enrolled in course'
)
def assert_enrolled(self):
""" Asserts that self.ext_user is enrolled in self.course. """
self.assertTrue(
CourseEnrollment.is_enrolled(self.ext_user, self.course.id),
'User ext_user should have been enrolled in the course'
)