Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
import ddt
from django.test import override_settings
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from milestones.tests.utils import MilestonesTestCaseMixin

from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.toggles import ENABLE_NEW_STUDIO_ADVANCED_SETTINGS_PAGE


@override_waffle_flag(ENABLE_NEW_STUDIO_ADVANCED_SETTINGS_PAGE, active=True)
@ddt.ddt
class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin):
"""
Expand Down
3 changes: 0 additions & 3 deletions cms/djangoapps/contentstore/rest_api/v0/tests/test_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@

import ddt
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from xmodule.modulestore.tests.factories import BlockFactory
from xmodule.tabs import CourseTabList

from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.toggles import ENABLE_NEW_STUDIO_CUSTOM_PAGES


@override_waffle_flag(ENABLE_NEW_STUDIO_CUSTOM_PAGES, active=True)
@ddt.ddt
class TabsAPITests(CourseTestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class CourseIndexViewTest(CourseTestCase, PermissionAccessMixin):
"""
Tests for CourseIndexView.
"""
maxDiff = None # Show the entire dictionary in the diff

def setUp(self):
super().setUp()
Expand Down Expand Up @@ -74,7 +75,10 @@ def test_course_index_response(self):
},
"language_code": "en",
"lms_link": get_lms_link_for_item(self.course.location),
"mfe_proctored_exam_settings_url": "",
"mfe_proctored_exam_settings_url": (
f"http://course-authoring-mfe/course/{self.course.id}"
"/pages-and-resources/proctoring/settings"
),
"notification_dismiss_url": None,
"proctoring_errors": [],
"reindex_link": f"/course/{self.course.id}/search_reindex",
Expand Down Expand Up @@ -121,7 +125,10 @@ def test_course_index_response_with_show_locators(self):
},
"language_code": "en",
"lms_link": get_lms_link_for_item(self.course.location),
"mfe_proctored_exam_settings_url": "",
"mfe_proctored_exam_settings_url": (
f"http://course-authoring-mfe/course/{self.course.id}"
"/pages-and-resources/proctoring/settings"
),
"notification_dismiss_url": None,
"proctoring_errors": [],
"reindex_link": f"/course/{self.course.id}/search_reindex",
Expand Down Expand Up @@ -151,6 +158,6 @@ def test_number_of_calls_to_db(self):
"""
Test to check number of queries made to mysql and mongo
"""
with self.assertNumQueries(32, table_ignorelist=WAFFLE_TABLES):
with self.assertNumQueries(33, table_ignorelist=WAFFLE_TABLES):
with check_mongo_calls(3):
self.client.get(self.url)
Original file line number Diff line number Diff line change
@@ -1,134 +1,61 @@
"""
Unit tests for the course waffle flags view
"""

from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status

from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel

User = get_user_model()


class CourseWaffleFlagsViewTest(CourseTestCase):
"""
Tests for the CourseWaffleFlagsView endpoint, which returns waffle flag states
Basic test for the CourseWaffleFlagsView endpoint, which returns waffle flag states
for a specific course or globally if no course ID is provided.
"""

course_waffle_flags = [
"use_new_custom_pages",
"use_new_schedule_details_page",
"use_new_advanced_settings_page",
"use_new_grading_page",
"use_new_updates_page",
"use_new_import_page",
"use_new_export_page",
"use_new_files_uploads_page",
"use_new_video_uploads_page",
"use_new_course_outline_page",
"use_new_unit_page",
"use_new_course_team_page",
"use_new_certificates_page",
"use_new_textbooks_page",
"use_new_group_configurations_page",
"use_react_markdown_editor"
]

other_expected_waffle_flags = ["enable_course_optimizer"]
maxDiff = None # Show the whole dictionary in the diff

defaults = {
'enable_course_optimizer': False,
'use_new_advanced_settings_page': True,
'use_new_certificates_page': True,
'use_new_course_outline_page': True,
'use_new_course_team_page': True,
'use_new_custom_pages': True,
'use_new_export_page': True,
'use_new_files_uploads_page': True,
'use_new_grading_page': True,
'use_new_group_configurations_page': True,
'use_new_home_page': True,
'use_new_import_page': True,
'use_new_schedule_details_page': True,
'use_new_textbooks_page': True,
'use_new_unit_page': True,
'use_new_updates_page': True,
'use_new_video_uploads_page': False,
'use_react_markdown_editor': False,
}

def setUp(self):
"""
Set up test data and state before each test method.

This method initializes the endpoint URL and creates a set of waffle flags
for the test course, setting each flag's value to `True`.
"""
super().setUp()
self.url = reverse("cms.djangoapps.contentstore:v1:course_waffle_flags")
self.create_waffle_flags(self.course_waffle_flags)
self.create_custom_waffle_flags()

def create_custom_waffle_flags(self, enabled=True):
"""
Helper method to create waffle flags that are not part of `course_waffle_flags` and have
a different format.
"""
WaffleFlagCourseOverrideModel.objects.create(
waffle_flag="contentstore.enable_course_optimizer",
waffle_flag=toggles.ENABLE_COURSE_OPTIMIZER.name,
course_id=self.course.id,
enabled=enabled,
enabled=True,
)

def create_waffle_flags(self, flags, enabled=True):
"""
Helper method to create waffle flag entries in the database for the test course.

Args:
flags (list): A list of flag names to set up.
enabled (bool): The value to set for each flag's enabled state.
"""
for flag in flags:
WaffleFlagCourseOverrideModel.objects.create(
waffle_flag=(
f"contentstore.new_studio_mfe.{flag}"
if flag != "use_react_markdown_editor"
else "contentstore.use_react_markdown_editor"
),
course_id=self.course.id,
enabled=enabled,
)

def expected_response(self, enabled=False):
"""
Generate an expected response dictionary based on the enabled flag.
def test_global_defaults(self):
url = reverse("cms.djangoapps.contentstore:v1:course_waffle_flags")
response = self.client.get(url)
assert response.data == self.defaults

Args:
enabled (bool): State to assign to each waffle flag in the response.

Returns:
dict: A dictionary with each flag set to the value of `enabled`.
"""
res = {flag: enabled for flag in self.course_waffle_flags}
for flag in self.other_expected_waffle_flags:
res[flag] = enabled
return res

def test_get_course_waffle_flags_with_course_id(self):
"""
Test that waffle flags for a specific course are correctly returned when
a valid course ID is provided.

Expected Behavior:
- The response should return HTTP 200 status.
- Each flag returned should be `True` as set up in the `setUp` method.
"""
course_url = reverse(
def test_course_override(self):
url = reverse(
"cms.djangoapps.contentstore:v1:course_waffle_flags",
kwargs={"course_id": self.course.id},
)

expected_response = self.expected_response(enabled=True)
expected_response["use_new_home_page"] = False

response = self.client.get(course_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictEqual(expected_response, response.data)

def test_get_course_waffle_flags_without_course_id(self):
"""
Test that the default waffle flag states are returned when no course ID is provided.

Expected Behavior:
- The response should return HTTP 200 status.
- Each flag returned should default to `False`, representing the global
default state for each flag.
"""
expected_response = self.expected_response(enabled=False)
expected_response["use_new_home_page"] = False

response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictEqual(expected_response, response.data)
response = self.client.get(url)
assert response.data == {
**self.defaults,
"enable_course_optimizer": True,
}
56 changes: 41 additions & 15 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# lint-amnesty, pylint: disable=missing-module-docstring

# TODO: Rewrite several of these assertions so that they check the output of the REST or Python
# APIs rather than parsing HTML from the deprecated legacy frontend pages. In particular, any
# test case using override_waffle_flag(toggles.LEGACY_STUDIO_*, True) will need to be fixed.
# Part of https://github.com/openedx/edx-platform/issues/36275.

import copy
import re
Expand All @@ -17,7 +21,7 @@
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.test import TestCase
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_switch
from edx_toggles.toggles.testutils import override_waffle_switch, override_waffle_flag
from edxval.api import create_video, get_videos_for_course
from fs.osfs import OSFS
from lxml import etree
Expand All @@ -43,6 +47,7 @@
from xmodule.seq_block import SequenceBlock
from xmodule.video_block import VideoBlock

from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.config import waffle
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, get_url, parse_json
from cms.djangoapps.contentstore.utils import (
Expand Down Expand Up @@ -587,6 +592,7 @@ def check_components_on_page(self, component_types, expected_types):
for expected in expected_types:
self.assertContains(resp, expected)

@override_waffle_flag(toggles.LEGACY_STUDIO_UNIT_EDITOR, True)
@ddt.data("<script>alert(1)</script>", "alert('hi')", "</script><script>alert(1)</script>")
def test_container_handler_xss_prevent(self, malicious_code):
"""
Expand All @@ -596,6 +602,7 @@ def test_container_handler_xss_prevent(self, malicious_code):
# Test that malicious code does not appear in html
self.assertNotContains(resp, malicious_code)

@override_waffle_flag(toggles.LEGACY_STUDIO_UNIT_EDITOR, True)
def test_advanced_components_in_edit_unit(self):
# This could be made better, but for now let's just assert that we see the advanced modules mentioned in the
# page response HTML
Expand Down Expand Up @@ -697,9 +704,11 @@ def test_assets_overwrite(self):
# Remove tempdir
shutil.rmtree(root_dir)

@override_waffle_flag(toggles.LEGACY_STUDIO_UNIT_EDITOR, True)
def test_advanced_components_require_two_clicks(self):
self.check_components_on_page(['word_cloud'], ['Word cloud'])

@override_waffle_flag(toggles.LEGACY_STUDIO_UNIT_EDITOR, True)
def test_edit_unit(self):
"""Verifies rendering the editor in all the verticals in the given test course"""
self._check_verticals([self.vert_loc])
Expand Down Expand Up @@ -1379,6 +1388,7 @@ def assert_course_permission_denied(self):
resp = self.client.ajax_post('/course/', self.course_data)
self.assertEqual(resp.status_code, 403)

@override_waffle_flag(toggles.LEGACY_STUDIO_HOME, True)
def test_course_index_view_with_no_courses(self):
"""Test viewing the index page with no courses"""
resp = self.client.get_html('/home/')
Expand All @@ -1400,6 +1410,7 @@ def test_item_factory(self):
item = BlockFactory.create(parent_location=course.location)
self.assertIsInstance(item, SequenceBlock)

@override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_OUTLINE, True)
def test_course_overview_view_with_course(self):
"""Test viewing the course overview page with an existing course"""
course = CourseFactory.create()
Expand Down Expand Up @@ -1499,7 +1510,8 @@ def test_get_html(handler):
)
course_key = course_items[0].id

resp = self._show_course_overview(course_key)
with override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_OUTLINE, True):
resp = self._show_course_overview(course_key)

# course_handler raise 404 for old mongo course
if course_key.deprecated:
Expand All @@ -1510,20 +1522,31 @@ def test_get_html(handler):
self.assertContains(resp, 'Chapter 2')

# go to various pages
test_get_html('import_handler')
test_get_html('export_handler')
test_get_html('course_team_handler')
test_get_html('course_info_handler')
test_get_html('assets_handler')
test_get_html('tabs_handler')
test_get_html('settings_handler')
test_get_html('grading_handler')
test_get_html('advanced_settings_handler')
test_get_html('textbooks_list_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_IMPORT, True):
test_get_html('import_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_EXPORT, True):
test_get_html('export_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True):
test_get_html('course_team_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_UPDATES, True):
test_get_html('course_info_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_FILES_UPLOADS, True):
test_get_html('assets_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_CUSTOM_PAGES, True):
test_get_html('tabs_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True):
test_get_html('settings_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_GRADING, True):
test_get_html('grading_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True):
test_get_html('advanced_settings_handler')
with override_waffle_flag(toggles.LEGACY_STUDIO_TEXTBOOKS, True):
test_get_html('textbooks_list_handler')

# go look at the Edit page
unit_key = course_key.make_usage_key('vertical', 'test_vertical')
resp = self.client.get_html(get_url('container_handler', unit_key))
with override_waffle_flag(toggles.LEGACY_STUDIO_UNIT_EDITOR, True):
resp = self.client.get_html(get_url('container_handler', unit_key))
self.assertEqual(resp.status_code, 200)

def delete_item(category, name):
Expand Down Expand Up @@ -1856,20 +1879,23 @@ def assertInCourseListing(self, course_key):
"""
Asserts that the given course key is NOT in the unsucceeded course action section of the html.
"""
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
with override_waffle_flag(toggles.LEGACY_STUDIO_HOME, True):
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 0)

def assertInUnsucceededCourseActions(self, course_key):
"""
Asserts that the given course key is in the unsucceeded course action section of the html.
"""
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
with override_waffle_flag(toggles.LEGACY_STUDIO_HOME, True):
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 1)

def verify_rerun_course(self, source_course_key, destination_course_key, destination_display_name):
"""
Verify the contents of the course rerun action
"""

rerun_state = CourseRerunState.objects.find_first(course_key=destination_course_key)
expected_states = {
'state': CourseRerunUIStateManager.State.SUCCEEDED,
Expand Down
Loading
Loading