Skip to content
Open
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
691 changes: 0 additions & 691 deletions lms/djangoapps/discussion/django_comment_client/base/tests.py

Large diffs are not rendered by default.

1,221 changes: 1,221 additions & 0 deletions lms/djangoapps/discussion/django_comment_client/base/tests_v2.py

Large diffs are not rendered by default.

216 changes: 216 additions & 0 deletions lms/djangoapps/discussion/django_comment_client/tests/group_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,219 @@ def test_team_discussion_id_not_cohorted(self, mock_is_forum_v2_enabled, mock_re
self.call_view(mock_is_forum_v2_enabled, mock_request, team.discussion_topic_id, self.student, '')

self._assert_comments_service_called_without_group_id(mock_request)


class GroupIdAssertionMixinV2:
"""
Provides assertion methods for testing group_id functionality in forum v2.

This mixin contains helper methods to verify that the comments service is called
with the correct group_id parameters and that responses contain the expected
group information.
"""
def _get_params_last_call(self, function_name):
"""
Returns the data or params dict that `mock_request` was called with.
"""
return self.get_mock_func_calls(function_name)[-1][1]

def _assert_comments_service_called_with_group_id(self, group_id):
assert self.check_mock_called('get_user_threads')
assert self._get_params_last_call('get_user_threads')['group_id'] == group_id

def _assert_comments_service_called_without_group_id(self):
assert self.check_mock_called('get_user_threads')
assert 'group_id' not in self._get_params_last_call('get_user_threads')

def _assert_html_response_contains_group_info(self, response):
group_info = {"group_id": None, "group_name": None}
match = re.search(r'"group_id": (\d*),', response.content.decode('utf-8'))
if match and match.group(1) != '':
group_info["group_id"] = int(match.group(1))
match = re.search(r'"group_name": "(\w*)"', response.content.decode('utf-8'))
if match:
group_info["group_name"] = match.group(1)
self._assert_thread_contains_group_info(group_info)

def _assert_json_response_contains_group_info(self, response, extract_thread=None):
"""
:param extract_thread: a function which accepts a dictionary (complete
json response payload) and returns another dictionary (first
occurrence of a thread model within that payload). if None is
passed, the identity function is assumed.
"""
payload = json.loads(response.content.decode('utf-8'))
thread = extract_thread(payload) if extract_thread else payload
self._assert_thread_contains_group_info(thread)

def _assert_thread_contains_group_info(self, thread):
assert thread['group_id'] == self.student_cohort.id
assert thread['group_name'] == self.student_cohort.name


class CohortedTopicGroupIdTestMixinV2(GroupIdAssertionMixinV2):
"""
Provides test cases to verify that views pass the correct `group_id` to
the comments service when requesting content in cohorted discussions for forum v2.
"""
def call_view(self, commentable_id, user, group_id, pass_group_id=True):
"""
Call the view for the implementing test class, constructing a request
from the parameters.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass

def test_cohorted_topic_student_without_group_id(self):
self.call_view("cohorted_topic", self.student, '', pass_group_id=False)
self._assert_comments_service_called_with_group_id(self.student_cohort.id)

def test_cohorted_topic_student_none_group_id(self):
self.call_view("cohorted_topic", self.student, "")
self._assert_comments_service_called_with_group_id(self.student_cohort.id)

def test_cohorted_topic_student_with_own_group_id(self):
self.call_view("cohorted_topic", self.student, self.student_cohort.id)
self._assert_comments_service_called_with_group_id(self.student_cohort.id)

def test_cohorted_topic_student_with_other_group_id(self):
self.call_view(
"cohorted_topic",
self.student,
self.moderator_cohort.id
)
self._assert_comments_service_called_with_group_id(self.student_cohort.id)

def test_cohorted_topic_moderator_without_group_id(self):
self.call_view(
"cohorted_topic",
self.moderator,
'',
pass_group_id=False
)
self._assert_comments_service_called_without_group_id()

def test_cohorted_topic_moderator_none_group_id(self):
self.call_view("cohorted_topic", self.moderator, "")
self._assert_comments_service_called_without_group_id()

def test_cohorted_topic_moderator_with_own_group_id(self):
self.call_view(
"cohorted_topic",
self.moderator,
self.moderator_cohort.id
)
self._assert_comments_service_called_with_group_id(self.moderator_cohort.id)

def test_cohorted_topic_moderator_with_other_group_id(self):
self.call_view(
"cohorted_topic",
self.moderator,
self.student_cohort.id
)
self._assert_comments_service_called_with_group_id(self.student_cohort.id)

def test_cohorted_topic_moderator_with_invalid_group_id(self):
invalid_id = self.student_cohort.id + self.moderator_cohort.id
response = self.call_view("cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
assert response.status_code == 500

def test_cohorted_topic_enrollment_track_invalid_group_id(self):
CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
CourseModeFactory.create(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
discussion_settings = CourseDiscussionSettings.get(self.course.id)
discussion_settings.update({
'divided_discussions': ['cohorted_topic'],
'division_scheme': CourseDiscussionSettings.ENROLLMENT_TRACK,
'always_divide_inline_discussions': True,
})

invalid_id = -1000
response = self.call_view("cohorted_topic", self.moderator, invalid_id) # lint-amnesty, pylint: disable=assignment-from-no-return
assert response.status_code == 500


class NonCohortedTopicGroupIdTestMixinV2(GroupIdAssertionMixinV2):
"""
Provides test cases to verify that views pass the correct `group_id` to
the comments service when requesting content in non-cohorted discussions for forum v2.
"""
def call_view(self, commentable_id, user, group_id, pass_group_id=True):
"""
Call the view for the implementing test class, constructing a request
from the parameters.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass

def test_non_cohorted_topic_student_without_group_id(self):
self.call_view(
"non_cohorted_topic",
self.student,
'',
pass_group_id=False
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_student_none_group_id(self):
self.call_view("non_cohorted_topic", self.student, '')
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_student_with_own_group_id(self):
self.call_view(
"non_cohorted_topic",
self.student,
self.student_cohort.id
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_student_with_other_group_id(self):
self.call_view(
"non_cohorted_topic",
self.student,
self.moderator_cohort.id
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_moderator_without_group_id(self):
self.call_view(
"non_cohorted_topic",
self.moderator,
"",
pass_group_id=False,
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_moderator_none_group_id(self):
self.call_view("non_cohorted_topic", self.moderator, '')
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_moderator_with_own_group_id(self):
self.call_view(
"non_cohorted_topic",
self.moderator,
self.moderator_cohort.id,
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_moderator_with_other_group_id(self):
self.call_view(
"non_cohorted_topic",
self.moderator,
self.student_cohort.id,
)
self._assert_comments_service_called_without_group_id()

def test_non_cohorted_topic_moderator_with_invalid_group_id(self):
invalid_id = self.student_cohort.id + self.moderator_cohort.id
self.call_view("non_cohorted_topic", self.moderator, invalid_id)
self._assert_comments_service_called_without_group_id()

def test_team_discussion_id_not_cohorted(self):
team = CourseTeamFactory(
course_id=self.course.id,
topic_id='topic-id'
)

team.add_user(self.student)
self.call_view(team.discussion_topic_id, self.student, '')

self._assert_comments_service_called_without_group_id()
106 changes: 106 additions & 0 deletions lms/djangoapps/discussion/django_comment_client/tests/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Mixin for django_comment_client tests.
"""

from unittest import mock


class MockForumApiMixin:
"""Mixin to mock forum_api across different test cases with a single mock instance."""

users_map = {}

@classmethod
def setUpClassAndForumMock(cls):
"""
Set up the class and apply the forum_api mock.
"""
cls.mock_forum_api = mock.Mock()

# TODO: Remove this after moving all APIs
cls.flag_v2_patcher = mock.patch(
"openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled"
)
cls.mock_enable_forum_v2 = cls.flag_v2_patcher.start()
cls.mock_enable_forum_v2.return_value = True

patch_targets = [
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api",
"openedx.core.djangoapps.django_comment_common.comment_client.comment.forum_api",
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api",
"openedx.core.djangoapps.django_comment_common.comment_client.course.forum_api",
"openedx.core.djangoapps.django_comment_common.comment_client.subscriptions.forum_api",
"openedx.core.djangoapps.django_comment_common.comment_client.user.forum_api",
]
cls.forum_api_patchers = [
mock.patch(target, cls.mock_forum_api) for target in patch_targets
]
for patcher in cls.forum_api_patchers:
patcher.start()

@classmethod
def disposeForumMocks(cls):
"""Stop patches after tests complete."""
cls.flag_v2_patcher.stop()

for patcher in cls.forum_api_patchers:
patcher.stop()

def set_mock_return_value(self, function_name, return_value):
"""
Set a return value for a specific method in forum_api mock.

Args:
function_name (str): The method name in the mock to set a return value for.
return_value (Any): The return value for the method.
"""
setattr(
self.mock_forum_api, function_name, mock.Mock(return_value=return_value)
)

def set_mock_side_effect(self, function_name, side_effect_fn):
"""
Set a side effect for a specific method in forum_api mock.

Args:
function_name (str): The method name in the mock to set a side effect for.
side_effect_fn (Callable): A function to be called when the mock is called.
"""
setattr(
self.mock_forum_api, function_name, mock.Mock(side_effect=side_effect_fn)
)

def check_mock_called_with(self, function_name, index, *parms, **kwargs):
"""
Check if a specific method in forum_api mock was called with the given parameters.

Args:
function_name (str): The method name in the mock to check.
parms (tuple): The parameters to check the method was called with.
"""
call_args = getattr(self.mock_forum_api, function_name).call_args_list[index]
assert call_args == mock.call(*parms, **kwargs)

def check_mock_called(self, function_name):
"""
Check if a specific method in the forum_api mock was called.

Args:
function_name (str): The method name in the mock to check.

Returns:
bool: True if the method was called, False otherwise.
"""
return getattr(self.mock_forum_api, function_name).called

def get_mock_func_calls(self, function_name):
"""
Returns a list of call arguments for a specific method in the mock_forum_api.

Args:
function_name (str): The name of the method in the mock_forum_api to retrieve call arguments for.

Returns:
list: A list of call arguments for the specified method.
"""
return getattr(self.mock_forum_api, function_name).call_args_list
5 changes: 4 additions & 1 deletion lms/djangoapps/discussion/rest_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,10 @@ def get_thread_list(
except ValueError:
pass

if (group_id is None) and not context["has_moderation_privilege"]:
if (group_id is None) and (
not context["has_moderation_privilege"]
or request.user.id in context["ta_user_ids"]
):
group_id = get_group_id_for_user(request.user, CourseDiscussionSettings.get(course.id))

query_params = {
Expand Down
Loading
Loading