From 9b399cde00311d020fd6a4d77fc99987588da84e Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 19 Jun 2025 17:07:35 -0500 Subject: [PATCH 1/2] feat(poc): add support for divided discussions with user groups --- .gitignore | 3 +++ openedx_user_groups/toggles.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 openedx_user_groups/toggles.py diff --git a/.gitignore b/.gitignore index 46d8147..6a6fd92 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ docs/openedx_user_groups.*.rst # Private requirements requirements/private.in requirements/private.txt + +# VSCode +.vscode/ diff --git a/openedx_user_groups/toggles.py b/openedx_user_groups/toggles.py new file mode 100644 index 0000000..f84b6d9 --- /dev/null +++ b/openedx_user_groups/toggles.py @@ -0,0 +1,32 @@ +""" +Toggles for user groups. + +This module defines feature flags (waffle flags) used to enable or disable functionality related to user groups +within the Open edX platform. These toggles allow for dynamic control of features without requiring code changes. +""" + +from opaque_keys.edx.keys import CourseKey + +try: + from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +except ImportError: + CourseWaffleFlag = None + +# Namespace for all user group related waffle flags +WAFFLE_FLAG_NAMESPACE = "user_groups" + +# .. toggle_name: user_groups.enable_user_groups +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: Waffle flag to enable or disable the user groups feature in a course. +# .. toggle_use_cases: temporary, open_edx +# .. toggle_creation_date: 2025-06-19 +# .. toggle_target_removal_date: None +ENABLE_USER_GROUPS = CourseWaffleFlag(f"{WAFFLE_FLAG_NAMESPACE}.enable_user_groups", __name__) + + +def is_user_groups_enabled(course_key: CourseKey) -> bool: + """ + Returns a boolean if user groups are enabled for the course. + """ + return ENABLE_USER_GROUPS.is_enabled(course_key) From c2cd3e9657d92c115ad33eda8b1dc4595519f1ff Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Fri, 27 Jun 2025 15:02:58 -0500 Subject: [PATCH 2/2] test: add unit tests for toggles module --- openedx_user_groups/toggles.py | 10 +- tests/test_toggles.py | 165 +++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 tests/test_toggles.py diff --git a/openedx_user_groups/toggles.py b/openedx_user_groups/toggles.py index f84b6d9..3af02af 100644 --- a/openedx_user_groups/toggles.py +++ b/openedx_user_groups/toggles.py @@ -10,7 +10,15 @@ try: from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag except ImportError: - CourseWaffleFlag = None + + class CourseWaffleFlag: + """Mock CourseWaffleFlag class.""" + + def __init__(self, name, module_name): + """Initialize the CourseWaffleFlag.""" + self.name = name + self.module_name = module_name + # Namespace for all user group related waffle flags WAFFLE_FLAG_NAMESPACE = "user_groups" diff --git a/tests/test_toggles.py b/tests/test_toggles.py new file mode 100644 index 0000000..37782ec --- /dev/null +++ b/tests/test_toggles.py @@ -0,0 +1,165 @@ +""" +Test Suite for the User Group toggles. + +This test suite covers all toggle functionality defined in toggles.py. +""" + +from unittest.mock import Mock, patch + +from django.test import TestCase +from opaque_keys.edx.keys import CourseKey + +from openedx_user_groups.toggles import ENABLE_USER_GROUPS, WAFFLE_FLAG_NAMESPACE, is_user_groups_enabled + + +class TestToggles(TestCase): + """Test toggle functionality and configuration.""" + + def test_waffle_flag_namespace(self): + """Test that the waffle flag namespace is correctly defined. + + Expected Results: + - The namespace should be "user_groups". + """ + self.assertEqual(WAFFLE_FLAG_NAMESPACE, "user_groups") + + def test_enable_user_groups_flag_creation(self): + """Test that the ENABLE_USER_GROUPS flag is created with correct parameters. + + Expected Results: + - The flag should be created with the correct namespace and module name. + """ + expected_flag_name = f"{WAFFLE_FLAG_NAMESPACE}.enable_user_groups" + self.assertEqual(ENABLE_USER_GROUPS.name, expected_flag_name) + self.assertEqual(ENABLE_USER_GROUPS.module_name, "openedx_user_groups.toggles") + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_is_user_groups_enabled_when_flag_enabled(self, mock_flag: Mock): + """Test is_user_groups_enabled returns True when flag is enabled. + + Expected Results: + - The function should return True when the waffle flag is enabled. + """ + course_key = CourseKey.from_string("course-v1:edX+Demo+Course") + mock_flag.is_enabled.return_value = True + + result = is_user_groups_enabled(course_key) + + self.assertTrue(result) + mock_flag.is_enabled.assert_called_once_with(course_key) + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_is_user_groups_enabled_when_flag_disabled(self, mock_flag: Mock): + """Test is_user_groups_enabled returns False when flag is disabled. + + Expected Results: + - The function should return False when the waffle flag is disabled. + """ + course_key = CourseKey.from_string("course-v1:edX+Demo+Course") + mock_flag.is_enabled.return_value = False + + result = is_user_groups_enabled(course_key) + + self.assertFalse(result) + mock_flag.is_enabled.assert_called_once_with(course_key) + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_is_user_groups_enabled_with_different_course_keys(self, mock_flag: Mock): + """Test is_user_groups_enabled works with different course key formats. + + Expected Results: + - The function should work correctly with different course key formats. + """ + course_keys = [ + CourseKey.from_string("course-v1:edX+Demo+Course"), + CourseKey.from_string("course-v1:TestOrg+CS101+2024"), + CourseKey.from_string("course-v1:AnotherOrg+Math101+Fall2024"), + ] + mock_flag.is_enabled.return_value = True + + for course_key in course_keys: + result = is_user_groups_enabled(course_key) + self.assertTrue(result) + mock_flag.is_enabled.assert_called_with(course_key) + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_is_user_groups_enabled_flag_called_with_correct_arguments(self, mock_flag: Mock): + """Test that the flag is called with the correct course key argument. + + Expected Results: + - The waffle flag should be called with the exact course key passed to the function. + """ + course_key = CourseKey.from_string("course-v1:edX+Demo+Course") + mock_flag.is_enabled.return_value = True + + is_user_groups_enabled(course_key) + + mock_flag.is_enabled.assert_called_once_with(course_key) + # Verify the course key is the same object, not just equal + called_course_key = mock_flag.is_enabled.call_args[0][0] + self.assertEqual(called_course_key, course_key) + + +class TestToggleIntegration(TestCase): + """Test toggle integration with course keys and real scenarios.""" + + def setUp(self): + """Set up test data.""" + self.course_key = CourseKey.from_string("course-v1:edX+Demo+Course") + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_toggle_with_real_course_key_objects(self, mock_flag: Mock): + """Test toggle functionality with real CourseKey objects. + + Expected Results: + - The toggle should work correctly with real CourseKey objects. + """ + mock_flag.is_enabled.return_value = True + + result = is_user_groups_enabled(self.course_key) + + self.assertTrue(result) + mock_flag.is_enabled.assert_called_once_with(self.course_key) + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_toggle_behavior_consistency(self, mock_flag: Mock): + """Test that toggle behavior is consistent across multiple calls. + + Expected Results: + - Multiple calls with the same course key should return the same result. + """ + mock_flag.is_enabled.return_value = True + + result1 = is_user_groups_enabled(self.course_key) + result2 = is_user_groups_enabled(self.course_key) + result3 = is_user_groups_enabled(self.course_key) + + self.assertTrue(result1) + self.assertTrue(result2) + self.assertTrue(result3) + self.assertEqual(mock_flag.is_enabled.call_count, 3) + + @patch("openedx_user_groups.toggles.ENABLE_USER_GROUPS") + def test_toggle_with_different_course_keys_returns_different_results(self, mock_flag: Mock): + """Test that different course keys can have different toggle states. + + Expected Results: + - Different course keys can have different toggle states. + """ + course_key1 = CourseKey.from_string("course-v1:edX+Demo+Course") + course_key2 = CourseKey.from_string("course-v1:TestOrg+CS101+2024") + + # Configure mock to return different values for different course keys + def mock_is_enabled(course_key): + if str(course_key) == "course-v1:edX+Demo+Course": + return True + return False + + mock_flag.is_enabled.side_effect = mock_is_enabled + + result1 = is_user_groups_enabled(course_key1) + result2 = is_user_groups_enabled(course_key2) + + self.assertTrue(result1) + self.assertFalse(result2) + self.assertEqual(mock_flag.is_enabled.call_count, 2)