Skip to content

Commit 1bed70b

Browse files
feat!: remove cs_comments_service support for forums pin API
This will force the use of the new v2 forums API for pinning/unpinning.
1 parent 588840a commit 1bed70b

File tree

4 files changed

+321
-84
lines changed

4 files changed

+321
-84
lines changed

lms/djangoapps/discussion/django_comment_client/base/tests.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -210,22 +210,6 @@ def test_flag(self, mock_is_forum_v2_enabled, mock_request):
210210
response = self.call_view("un_flag_abuse_for_thread", mock_is_forum_v2_enabled, mock_request)
211211
self._assert_json_response_contains_group_info(response)
212212

213-
def test_pin(self, mock_is_forum_v2_enabled, mock_request):
214-
response = self.call_view(
215-
"pin_thread",
216-
mock_is_forum_v2_enabled,
217-
mock_request,
218-
user=self.moderator
219-
)
220-
self._assert_json_response_contains_group_info(response)
221-
response = self.call_view(
222-
"un_pin_thread",
223-
mock_is_forum_v2_enabled,
224-
mock_request,
225-
user=self.moderator
226-
)
227-
self._assert_json_response_contains_group_info(response)
228-
229213
def test_openclose(self, mock_is_forum_v2_enabled, mock_request):
230214
response = self.call_view(
231215
"openclose_thread",
@@ -1191,42 +1175,6 @@ def setUp(self):
11911175
self.mock_get_course_id_by_thread = patcher.start()
11921176
self.addCleanup(patcher.stop)
11931177

1194-
def test_pin_thread_as_student(self, mock_is_forum_v2_enabled, mock_request):
1195-
mock_is_forum_v2_enabled.return_value = False
1196-
self._set_mock_request_data(mock_request, {})
1197-
self.client.login(username=self.student.username, password=self.password)
1198-
response = self.client.post(
1199-
reverse("pin_thread", kwargs={"course_id": str(self.course.id), "thread_id": "dummy"})
1200-
)
1201-
assert response.status_code == 401
1202-
1203-
def test_pin_thread_as_moderator(self, mock_is_forum_v2_enabled, mock_request):
1204-
mock_is_forum_v2_enabled.return_value = False
1205-
self._set_mock_request_data(mock_request, {})
1206-
self.client.login(username=self.moderator.username, password=self.password)
1207-
response = self.client.post(
1208-
reverse("pin_thread", kwargs={"course_id": str(self.course.id), "thread_id": "dummy"})
1209-
)
1210-
assert response.status_code == 200
1211-
1212-
def test_un_pin_thread_as_student(self, mock_is_forum_v2_enabled, mock_request):
1213-
mock_is_forum_v2_enabled.return_value = False
1214-
self._set_mock_request_data(mock_request, {})
1215-
self.client.login(username=self.student.username, password=self.password)
1216-
response = self.client.post(
1217-
reverse("un_pin_thread", kwargs={"course_id": str(self.course.id), "thread_id": "dummy"})
1218-
)
1219-
assert response.status_code == 401
1220-
1221-
def test_un_pin_thread_as_moderator(self, mock_is_forum_v2_enabled, mock_request):
1222-
mock_is_forum_v2_enabled.return_value = False
1223-
self._set_mock_request_data(mock_request, {})
1224-
self.client.login(username=self.moderator.username, password=self.password)
1225-
response = self.client.post(
1226-
reverse("un_pin_thread", kwargs={"course_id": str(self.course.id), "thread_id": "dummy"})
1227-
)
1228-
assert response.status_code == 200
1229-
12301178
def _set_mock_request_thread_and_comment(self, mock_is_forum_v2_enabled, mock_request, thread_data, comment_data):
12311179
def handle_request(*args, **kwargs):
12321180
url = args[1]
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# pylint: skip-file
2+
"""Tests for django comment client views."""
3+
4+
import json
5+
import logging
6+
from contextlib import contextmanager
7+
from unittest import mock
8+
from unittest.mock import ANY, Mock, patch
9+
10+
import ddt
11+
from django.contrib.auth.models import User
12+
from django.core.management import call_command
13+
from django.test.client import RequestFactory
14+
from django.urls import reverse
15+
from eventtracking.processors.exceptions import EventEmissionExit
16+
from opaque_keys.edx.keys import CourseKey
17+
from openedx_events.learning.signals import (
18+
FORUM_THREAD_CREATED,
19+
FORUM_THREAD_RESPONSE_CREATED,
20+
FORUM_RESPONSE_COMMENT_CREATED,
21+
)
22+
23+
from common.djangoapps.course_modes.models import CourseMode
24+
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
25+
from common.djangoapps.student.roles import CourseStaffRole, UserBasedRole
26+
from common.djangoapps.student.tests.factories import (
27+
CourseAccessRoleFactory,
28+
CourseEnrollmentFactory,
29+
UserFactory,
30+
)
31+
from common.djangoapps.track.middleware import TrackMiddleware
32+
from common.djangoapps.track.views import segmentio
33+
from common.djangoapps.track.views.tests.base import (
34+
SEGMENTIO_TEST_USER_ID,
35+
SegmentIOTrackingTestCaseBase,
36+
)
37+
from common.djangoapps.util.testing import UrlResetMixin
38+
from common.test.utils import MockSignalHandlerMixin, disable_signal
39+
from lms.djangoapps.discussion.django_comment_client.base import views
40+
from lms.djangoapps.discussion.django_comment_client.tests.group_id import (
41+
CohortedTopicGroupIdTestMixin,
42+
GroupIdAssertionMixin,
43+
NonCohortedTopicGroupIdTestMixin,
44+
)
45+
from lms.djangoapps.discussion.django_comment_client.tests.unicode import (
46+
UnicodeTestMixin,
47+
)
48+
from lms.djangoapps.discussion.django_comment_client.tests.utils import (
49+
CohortedTestCase,
50+
ForumsEnableMixin,
51+
)
52+
from lms.djangoapps.teams.tests.factories import (
53+
CourseTeamFactory,
54+
CourseTeamMembershipFactory,
55+
)
56+
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
57+
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
58+
from openedx.core.djangoapps.django_comment_common.comment_client import Thread
59+
from openedx.core.djangoapps.django_comment_common.models import (
60+
FORUM_ROLE_STUDENT,
61+
CourseDiscussionSettings,
62+
Role,
63+
assign_role,
64+
)
65+
from openedx.core.djangoapps.django_comment_common.utils import (
66+
ThreadContext,
67+
seed_permissions_roles,
68+
)
69+
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
70+
from openedx.core.lib.teams_config import TeamsConfig
71+
from xmodule.modulestore import ModuleStoreEnum
72+
from xmodule.modulestore.django import modulestore
73+
from xmodule.modulestore.tests.django_utils import (
74+
TEST_DATA_SPLIT_MODULESTORE,
75+
ModuleStoreTestCase,
76+
SharedModuleStoreTestCase,
77+
)
78+
from xmodule.modulestore.tests.factories import (
79+
CourseFactory,
80+
BlockFactory,
81+
check_mongo_calls,
82+
)
83+
84+
from .event_transformers import ForumThreadViewedEventTransformer
85+
from lms.djangoapps.discussion.django_comment_client.tests.mixins import (
86+
MockForumApiMixin,
87+
)
88+
89+
90+
@disable_signal(views, "thread_edited")
91+
@disable_signal(views, "thread_voted")
92+
@disable_signal(views, "thread_deleted")
93+
class ThreadActionGroupIdTestCase(
94+
CohortedTestCase, GroupIdAssertionMixin, MockForumApiMixin
95+
):
96+
"""Test case for thread actions with group ID assertions."""
97+
98+
@classmethod
99+
def setUpClass(cls):
100+
"""Set up class and forum mock."""
101+
super().setUpClass()
102+
super().setUpClassAndForumMock()
103+
104+
@classmethod
105+
def tearDownClass(cls):
106+
"""Stop patches after tests complete."""
107+
super().tearDownClass()
108+
super().disposeForumMocks()
109+
110+
def call_view(
111+
self, view_name, mock_function, user=None, post_params=None, view_args=None
112+
):
113+
"""Call a view with the given parameters."""
114+
thread_response = {
115+
"user_id": str(self.student.id),
116+
"group_id": self.student_cohort.id,
117+
"closed": False,
118+
"type": "thread",
119+
"commentable_id": "non_team_dummy_id",
120+
"body": "test body",
121+
}
122+
123+
self.set_mock_return_value("get_course_id_by_thread", self.course.id)
124+
self.set_mock_return_value("get_thread", thread_response)
125+
self.set_mock_return_value(mock_function, thread_response)
126+
127+
request = RequestFactory().post("dummy_url", post_params or {})
128+
request.user = user or self.student
129+
request.view_name = view_name
130+
131+
return getattr(views, view_name)(
132+
request,
133+
course_id=str(self.course.id),
134+
thread_id="dummy",
135+
**(view_args or {})
136+
)
137+
138+
def test_pin_thread(self):
139+
"""Test pinning a thread."""
140+
response = self.call_view("pin_thread", "pin_thread", user=self.moderator)
141+
assert response.status_code == 200
142+
self._assert_json_response_contains_group_info(response)
143+
144+
response = self.call_view("un_pin_thread", "unpin_thread", user=self.moderator)
145+
assert response.status_code == 200
146+
self._assert_json_response_contains_group_info(response)
147+
148+
149+
@disable_signal(views, "comment_endorsed")
150+
class ViewPermissionsTestCase(
151+
ForumsEnableMixin,
152+
UrlResetMixin,
153+
SharedModuleStoreTestCase,
154+
MockForumApiMixin,
155+
):
156+
"""Test case for view permissions."""
157+
158+
@classmethod
159+
def setUpClass(cls): # pylint: disable=super-method-not-called
160+
"""Set up class and forum mock."""
161+
super().setUpClassAndForumMock()
162+
163+
with super().setUpClassAndTestData():
164+
cls.course = CourseFactory.create()
165+
166+
@classmethod
167+
def tearDownClass(cls):
168+
"""Stop patches after tests complete."""
169+
super().tearDownClass()
170+
super().disposeForumMocks()
171+
172+
@classmethod
173+
def setUpTestData(cls):
174+
"""Set up test data."""
175+
super().setUpTestData()
176+
177+
seed_permissions_roles(cls.course.id)
178+
179+
cls.password = "test password"
180+
cls.student = UserFactory.create(password=cls.password)
181+
cls.moderator = UserFactory.create(password=cls.password)
182+
183+
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
184+
CourseEnrollmentFactory(user=cls.moderator, course_id=cls.course.id)
185+
186+
cls.moderator.roles.add(
187+
Role.objects.get(name="Moderator", course_id=cls.course.id)
188+
)
189+
190+
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
191+
def setUp(self):
192+
"""Set up the test case."""
193+
super().setUp()
194+
195+
# Set return values dynamically using the mixin method
196+
self.set_mock_return_value("get_course_id_by_comment", self.course.id)
197+
self.set_mock_return_value("get_course_id_by_thread", self.course.id)
198+
self.set_mock_return_value("get_thread", {})
199+
self.set_mock_return_value("pin_thread", {})
200+
self.set_mock_return_value("unpin_thread", {})
201+
202+
def test_pin_thread_as_student(self):
203+
"""Test pinning a thread as a student."""
204+
self.client.login(username=self.student.username, password=self.password)
205+
response = self.client.post(
206+
reverse(
207+
"pin_thread",
208+
kwargs={"course_id": str(self.course.id), "thread_id": "dummy"},
209+
)
210+
)
211+
assert response.status_code == 401
212+
213+
def test_pin_thread_as_moderator(self):
214+
"""Test pinning a thread as a moderator."""
215+
self.client.login(username=self.moderator.username, password=self.password)
216+
response = self.client.post(
217+
reverse(
218+
"pin_thread",
219+
kwargs={"course_id": str(self.course.id), "thread_id": "dummy"},
220+
)
221+
)
222+
assert response.status_code == 200
223+
224+
def test_un_pin_thread_as_student(self):
225+
"""Test unpinning a thread as a student."""
226+
self.client.login(username=self.student.username, password=self.password)
227+
response = self.client.post(
228+
reverse(
229+
"un_pin_thread",
230+
kwargs={"course_id": str(self.course.id), "thread_id": "dummy"},
231+
)
232+
)
233+
assert response.status_code == 401
234+
235+
def test_un_pin_thread_as_moderator(self):
236+
"""Test unpinning a thread as a moderator."""
237+
self.client.login(username=self.moderator.username, password=self.password)
238+
response = self.client.post(
239+
reverse(
240+
"un_pin_thread",
241+
kwargs={"course_id": str(self.course.id), "thread_id": "dummy"},
242+
)
243+
)
244+
assert response.status_code == 200
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Mixin for django_comment_client tests.
3+
"""
4+
5+
from unittest import mock
6+
7+
8+
class MockForumApiMixin:
9+
"""Mixin to mock forum_api across different test cases with a single mock instance."""
10+
11+
@classmethod
12+
def setUpClass(cls):
13+
"""Apply a single forum_api mock at the class level."""
14+
cls.setUpClassAndForumMock()
15+
16+
@classmethod
17+
def setUpClassAndForumMock(cls):
18+
"""
19+
Set up the class and apply the forum_api mock.
20+
"""
21+
cls.mock_forum_api = mock.Mock()
22+
23+
# TODO: Remove this after moving all APIs
24+
cls.flag_v2_patcher = mock.patch(
25+
"openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled"
26+
)
27+
cls.mock_enable_forum_v2 = cls.flag_v2_patcher.start()
28+
cls.mock_enable_forum_v2.return_value = True
29+
30+
patch_targets = [
31+
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api",
32+
"openedx.core.djangoapps.django_comment_common.comment_client.comment.forum_api",
33+
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api",
34+
"openedx.core.djangoapps.django_comment_common.comment_client.course.forum_api",
35+
"openedx.core.djangoapps.django_comment_common.comment_client.subscriptions.forum_api",
36+
"openedx.core.djangoapps.django_comment_common.comment_client.user.forum_api",
37+
]
38+
cls.forum_api_patchers = [
39+
mock.patch(target, cls.mock_forum_api) for target in patch_targets
40+
]
41+
for patcher in cls.forum_api_patchers:
42+
patcher.start()
43+
44+
@classmethod
45+
def disposeForumMocks(cls):
46+
"""Stop patches after tests complete."""
47+
cls.flag_v2_patcher.stop()
48+
49+
for patcher in cls.forum_api_patchers:
50+
patcher.stop()
51+
52+
@classmethod
53+
def tearDownClass(cls):
54+
"""Stop patches after tests complete."""
55+
cls.disposeForumMocks()
56+
57+
def set_mock_return_value(self, function_name, return_value):
58+
"""
59+
Set a return value for a specific method in forum_api mock.
60+
61+
Args:
62+
function_name (str): The method name in the mock to set a return value for.
63+
return_value (Any): The return value for the method.
64+
"""
65+
setattr(
66+
self.mock_forum_api, function_name, mock.Mock(return_value=return_value)
67+
)

0 commit comments

Comments
 (0)