feat!: remove cs_comments_service support for forum's subscription APIs

- This will force the use of the new v2 forum's APIs for subscriptions.
This commit is contained in:
Ali-Salman29
2025-07-28 12:05:48 +02:00
committed by David Ormsbee
parent cdf5083544
commit d29171c046
8 changed files with 741 additions and 707 deletions

View File

@@ -527,23 +527,6 @@ class ViewsTestCase(
# create_thread_helper verifies that extra data are passed through to the comments service
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request, extra_response_data={'context': ThreadContext.STANDALONE})
@ddt.data(
('follow_thread', 'thread_followed'),
('unfollow_thread', 'thread_unfollowed'),
)
@ddt.unpack
def test_follow_unfollow_thread_signals(self, view_name, signal, mock_is_forum_v2_enabled, mock_request):
self.create_thread_helper(mock_is_forum_v2_enabled, mock_request)
with self.assert_discussion_signals(signal):
response = self.client.post(
reverse(
view_name,
kwargs={"course_id": str(self.course_id), "thread_id": 'i4x-MITx-999-course-Robot_Super_Course'}
)
)
assert response.status_code == 200
def test_delete_thread(self, mock_is_forum_v2_enabled, mock_request):
mock_is_forum_v2_enabled.return_value = False
self._set_mock_request_data(mock_request, {
@@ -1421,27 +1404,6 @@ class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleSto
)
assert response.status_code == status_code
@ddt.data(*ddt_permissions_args)
@ddt.unpack
def test_threads_actions(self, user, commentable_id, status_code, mock_is_forum_v2_enabled, mock_request):
"""
Verify that voting, flagging, and following of threads is limited to members of the team or users with
'edit_content' permission.
"""
commentable_id = getattr(self, commentable_id)
self._setup_mock(
user, mock_is_forum_v2_enabled, mock_request,
{"closed": False, "commentable_id": commentable_id, "body": "dummy body", "course_id": str(self.course.id)}
)
for action in ["follow_thread", "unfollow_thread"]:
response = self.client.post(
reverse(
action,
kwargs={"course_id": str(self.course.id), "thread_id": "dummy_thread"}
)
)
assert response.status_code == status_code
TEAM_COMMENTABLE_ID = 'test-team-discussion'
@@ -1482,50 +1444,6 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
cls.student.roles.add(Role.objects.get(name="Student", course_id=cls.course.id))
CourseAccessRoleFactory(course_id=cls.course.id, user=cls.student, role='Wizard')
@patch('eventtracking.tracker.emit')
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
def test_response_event(self, mock_is_forum_v2_enabled, mock_request, mock_emit):
"""
Check to make sure an event is fired when a user responds to a thread.
"""
event_receiver = Mock()
FORUM_THREAD_RESPONSE_CREATED.connect(event_receiver)
mock_is_forum_v2_enabled.return_value = False
self._set_mock_request_data(mock_request, {
"closed": False,
"commentable_id": 'test_commentable_id',
'thread_id': 'test_thread_id',
})
request = RequestFactory().post("dummy_url", {"body": "Test comment", 'auto_subscribe': True})
request.user = self.student
request.view_name = "create_comment"
views.create_comment(request, course_id=str(self.course.id), thread_id='test_thread_id')
event_name, event = mock_emit.call_args[0]
assert event_name == 'edx.forum.response.created'
assert event['body'] == 'Test comment'
assert event['commentable_id'] == 'test_commentable_id'
assert event['user_forums_roles'] == ['Student']
assert event['user_course_roles'] == ['Wizard']
assert event['discussion']['id'] == 'test_thread_id'
assert event['options']['followed'] is True
event_receiver.assert_called_once()
self.assertDictContainsSubset(
{
"signal": FORUM_THREAD_RESPONSE_CREATED,
"sender": None,
},
event_receiver.call_args.kwargs
)
self.assertIn(
"thread",
event_receiver.call_args.kwargs
)
@patch('eventtracking.tracker.emit')
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
@@ -1570,108 +1488,6 @@ class ForumEventTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockReque
event_receiver.call_args.kwargs
)
@patch('eventtracking.tracker.emit')
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
@ddt.data((
'create_thread',
'edx.forum.thread.created', {
'thread_type': 'discussion',
'body': 'Test text',
'title': 'Test',
'auto_subscribe': True
},
{'commentable_id': TEAM_COMMENTABLE_ID}
), (
'create_comment',
'edx.forum.response.created',
{'body': 'Test comment', 'auto_subscribe': True},
{'thread_id': 'test_thread_id'}
), (
'create_sub_comment',
'edx.forum.comment.created',
{'body': 'Another comment'},
{'comment_id': 'dummy_comment_id'}
))
@ddt.unpack
def test_team_events(self, view_name, event_name, view_data, view_kwargs, mock_is_forum_v2_enabled, mock_request, mock_emit):
user = self.student
team = CourseTeamFactory.create(discussion_topic_id=TEAM_COMMENTABLE_ID)
CourseTeamMembershipFactory.create(team=team, user=user)
event_receiver = Mock()
forum_event = views.TRACKING_LOG_TO_EVENT_MAPS.get(event_name)
forum_event.connect(event_receiver)
mock_is_forum_v2_enabled.return_value = False
self._set_mock_request_data(mock_request, {
'closed': False,
'commentable_id': TEAM_COMMENTABLE_ID,
'thread_id': 'test_thread_id',
})
request = RequestFactory().post('dummy_url', view_data)
request.user = user
request.view_name = view_name
getattr(views, view_name)(request, course_id=str(self.course.id), **view_kwargs)
name, event = mock_emit.call_args[0]
assert name == event_name
assert event['team_id'] == team.team_id
self.assertDictContainsSubset(
{
"signal": forum_event,
"sender": None,
},
event_receiver.call_args.kwargs
)
self.assertIn(
"thread",
event_receiver.call_args.kwargs
)
@ddt.data('follow_thread', 'unfollow_thread',)
@patch('eventtracking.tracker.emit')
@patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True)
@patch('openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled', autospec=True)
def test_thread_followed_event(self, view_name, mock_is_forum_v2_enabled, mock_request, mock_emit):
event_receiver = Mock()
for signal in views.TRACKING_LOG_TO_EVENT_MAPS.values():
signal.connect(event_receiver)
mock_is_forum_v2_enabled.return_value = False
self._set_mock_request_data(mock_request, {
'closed': False,
'commentable_id': 'test_commentable_id',
'username': 'test_user',
})
request = RequestFactory().post('dummy_url', {})
request.user = self.student
request.view_name = view_name
view_function = getattr(views, view_name)
kwargs = dict(course_id=str(self.course.id))
kwargs['thread_id'] = 'thread_id'
view_function(request, **kwargs)
assert mock_emit.called
event_name, event_data = mock_emit.call_args[0]
action_name = 'followed' if view_name == 'follow_thread' else 'unfollowed'
expected_action_value = True if view_name == 'follow_thread' else False
assert event_name == f'edx.forum.thread.{action_name}'
assert event_data['commentable_id'] == 'test_commentable_id'
assert event_data['id'] == 'thread_id'
assert event_data['followed'] == expected_action_value
assert event_data['user_forums_roles'] == ['Student']
assert event_data['user_course_roles'] == ['Wizard']
# In case of events that doesn't have a correspondig Open edX events signal
# we need to check that none of the openedx signals is called.
# This is tested for all the events that are not tested above.
event_receiver.assert_not_called()
class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):

View File

@@ -593,6 +593,22 @@ class ViewsTestCase(
)
assert response.status_code == 200
@ddt.data(
('follow_thread', 'thread_followed'),
('unfollow_thread', 'thread_unfollowed'),
)
@ddt.unpack
def test_follow_unfollow_thread_signals(self, view_name, signal):
self._setup_mock_request("get_thread")
with self.assert_discussion_signals(signal):
response = self.client.post(
reverse(
view_name,
kwargs={"course_id": str(self.course_id), "thread_id": "i4x-MITx-999-course-Robot_Super_Course"}
)
)
assert response.status_code == 200
@disable_signal(views, "comment_endorsed")
class ViewPermissionsTestCase(
@@ -956,6 +972,9 @@ class TeamsPermissionsTestCase(
assert response.status_code == status_code
TEAM_COMMENTABLE_ID = 'test-team-discussion'
@disable_signal(views, "comment_created")
@ddt.ddt
class ForumEventTestCase(
@@ -1038,3 +1057,165 @@ class ForumEventTestCase(
assert event["target_username"] == "gumprecht"
assert event["undo_vote"] == undo
assert event["vote_value"] == "up"
@patch('eventtracking.tracker.emit')
@ddt.data((
'create_thread',
'edx.forum.thread.created', {
'thread_type': 'discussion',
'body': 'Test text',
'title': 'Test',
'auto_subscribe': True
},
{'commentable_id': TEAM_COMMENTABLE_ID}
), (
'create_comment',
'edx.forum.response.created',
{'body': 'Test comment', 'auto_subscribe': True},
{'thread_id': 'test_thread_id'}
), (
'create_sub_comment',
'edx.forum.comment.created',
{'body': 'Another comment'},
{'comment_id': 'dummy_comment_id'}
))
@ddt.unpack
def test_team_events(self, view_name, event_name, view_data, view_kwargs, mock_emit):
user = self.student
team = CourseTeamFactory.create(discussion_topic_id=TEAM_COMMENTABLE_ID)
CourseTeamMembershipFactory.create(team=team, user=user)
cs_thread = make_minimal_cs_thread(
{
"commentable_id": "test_commentable_id",
"username": "gumprecht",
}
)
cs_comment = make_minimal_cs_comment(
{
"closed": False,
"commentable_id": "test_commentable_id",
"username": "gumprecht",
}
)
mock_request_data = {
'closed': False,
'commentable_id': TEAM_COMMENTABLE_ID,
'thread_id': 'test_thread_id',
}
self.set_mock_return_value("create_thread", mock_request_data)
self.set_mock_return_value("get_thread", mock_request_data)
self.set_mock_return_value("create_comment", mock_request_data)
self.set_mock_return_value("create_parent_comment", mock_request_data)
self.set_mock_return_value("get_parent_comment", mock_request_data)
self.set_mock_return_value("create_child_comment", mock_request_data)
self.set_mock_return_value("create_sub_comment", mock_request_data)
event_receiver = Mock()
forum_event = views.TRACKING_LOG_TO_EVENT_MAPS.get(event_name)
forum_event.connect(event_receiver)
request = RequestFactory().post('dummy_url', view_data)
request.user = user
request.view_name = view_name
getattr(views, view_name)(request, course_id=str(self.course.id), **view_kwargs)
name, event = mock_emit.call_args[0]
assert name == event_name
assert event['team_id'] == team.team_id
self.assertDictContainsSubset(
{
"signal": forum_event,
"sender": None,
},
event_receiver.call_args.kwargs
)
self.assertIn(
"thread",
event_receiver.call_args.kwargs
)
@ddt.data('follow_thread', 'unfollow_thread',)
@patch('eventtracking.tracker.emit')
def test_thread_followed_event(self, view_name, mock_emit):
event_receiver = Mock()
for signal in views.TRACKING_LOG_TO_EVENT_MAPS.values():
signal.connect(event_receiver)
mock_request_data = {
'closed': False,
'commentable_id': 'test_commentable_id',
'username': 'test_user',
}
self.set_mock_return_value("get_thread", mock_request_data)
self.set_mock_return_value("follow_thread", mock_request_data)
self.set_mock_return_value("unfollow_thread", mock_request_data)
request = RequestFactory().post('dummy_url', {})
request.user = self.student
request.view_name = view_name
view_function = getattr(views, view_name)
kwargs = dict(course_id=str(self.course.id))
kwargs['thread_id'] = 'thread_id'
view_function(request, **kwargs)
assert mock_emit.called
event_name, event_data = mock_emit.call_args[0]
action_name = 'followed' if view_name == 'follow_thread' else 'unfollowed'
expected_action_value = True if view_name == 'follow_thread' else False
assert event_name == f'edx.forum.thread.{action_name}'
assert event_data['commentable_id'] == 'test_commentable_id'
assert event_data['id'] == 'thread_id'
assert event_data['followed'] == expected_action_value
assert event_data['user_forums_roles'] == ['Student']
assert event_data['user_course_roles'] == ['Wizard']
# In case of events that doesn't have a correspondig Open edX events signal
# we need to check that none of the openedx signals is called.
# This is tested for all the events that are not tested above.
event_receiver.assert_not_called()
@patch('eventtracking.tracker.emit')
def test_response_event(self, mock_emit):
"""
Check to make sure an event is fired when a user responds to a thread.
"""
event_receiver = Mock()
FORUM_THREAD_RESPONSE_CREATED.connect(event_receiver)
mock_request_data = {
"closed": False,
"commentable_id": 'test_commentable_id',
'thread_id': 'test_thread_id',
}
self.set_mock_return_value("get_thread", mock_request_data)
self.set_mock_return_value("create_parent_comment", mock_request_data)
request = RequestFactory().post("dummy_url", {"body": "Test comment", 'auto_subscribe': True})
request.user = self.student
request.view_name = "create_comment"
views.create_comment(request, course_id=str(self.course.id), thread_id='test_thread_id')
event_name, event = mock_emit.call_args[0]
assert event_name == 'edx.forum.response.created'
assert event['body'] == 'Test comment'
assert event['commentable_id'] == 'test_commentable_id'
assert event['user_forums_roles'] == ['Student']
assert event['user_course_roles'] == ['Wizard']
assert event['discussion']['id'] == 'test_thread_id'
assert event['options']['followed'] is True
event_receiver.assert_called_once()
self.assertDictContainsSubset(
{
"signal": FORUM_THREAD_RESPONSE_CREATED,
"sender": None,
},
event_receiver.call_args.kwargs
)
self.assertIn(
"thread",
event_receiver.call_args.kwargs
)

View File

@@ -7,7 +7,7 @@ import itertools
import random
from datetime import datetime, timedelta
from unittest import mock
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from urllib.parse import urlencode, urlparse, urlunparse
import ddt
import httpretty
@@ -1625,18 +1625,6 @@ class CreateThreadTest(
if not expected_error:
self.fail(f"Unexpected validation error: {ex}")
def test_following(self):
self.register_post_thread_response({"id": "test_id", "username": self.user.username})
self.register_subscription_response(self.user)
data = self.minimal_data.copy()
data["following"] = "True"
result = create_thread(self.request, data)
assert result['following'] is True
cs_request = httpretty.last_request()
assert urlparse(cs_request.path).path == f"/api/v1/users/{self.user.id}/subscriptions" # lint-amnesty, pylint: disable=no-member
assert cs_request.method == 'POST'
assert parsed_body(cs_request) == {'source_type': ['thread'], 'source_id': ['test_id']}
def test_course_id_missing(self):
with pytest.raises(ValidationError) as assertion:
create_thread(self.request, {})
@@ -2251,52 +2239,6 @@ class UpdateThreadTest(
assert expected_error
assert err.message_dict == {field: ['This field is not editable.'] for field in data.keys()}
@ddt.data(*itertools.product([True, False], [True, False]))
@ddt.unpack
@mock.patch("eventtracking.tracker.emit")
def test_following(self, old_following, new_following, mock_emit):
"""
Test attempts to edit the "following" field.
old_following indicates whether the thread should be followed at the
start of the test. new_following indicates the value for the "following"
field in the update. If old_following and new_following are the same, no
update should be made. Otherwise, a subscription should be POSTed or
DELETEd according to the new_following value.
"""
if old_following:
self.register_get_user_response(self.user, subscribed_thread_ids=["test_thread"])
self.register_subscription_response(self.user)
self.register_thread()
data = {"following": new_following}
signal_name = "thread_followed" if new_following else "thread_unfollowed"
mock_path = f"openedx.core.djangoapps.django_comment_common.signals.{signal_name}.send"
with mock.patch(mock_path) as signal_patch:
result = update_thread(self.request, "test_thread", data)
if old_following != new_following:
self.assertEqual(signal_patch.call_count, 1)
assert result['following'] == new_following
last_request_path = urlparse(httpretty.last_request().path).path # lint-amnesty, pylint: disable=no-member
subscription_url = f"/api/v1/users/{self.user.id}/subscriptions"
if old_following == new_following:
assert last_request_path != subscription_url
else:
assert last_request_path == subscription_url
assert httpretty.last_request().method == ('POST' if new_following else 'DELETE')
request_data = (
parsed_body(httpretty.last_request()) if new_following else
parse_qs(urlparse(httpretty.last_request().path).query) # lint-amnesty, pylint: disable=no-member
)
request_data.pop("request_id", None)
assert request_data == {'source_type': ['thread'], 'source_id': ['test_thread']}
event_name, event_data = mock_emit.call_args[0]
expected_event_action = 'followed' if new_following else 'unfollowed'
assert event_name == f'edx.forum.thread.{expected_event_action}'
assert event_data['commentable_id'] == 'original_topic'
assert event_data['id'] == 'test_thread'
assert event_data['followed'] == new_following
assert event_data['user_forums_roles'] == ['Student']
def test_invalid_field(self):
self.register_thread()
with pytest.raises(ValidationError) as assertion:

View File

@@ -282,6 +282,18 @@ class CreateThreadTest(
}
self.check_mock_called_with("update_thread_flag", -1, **params)
def test_following(self):
self.register_post_thread_response({"id": "test_id", "username": self.user.username})
self.register_subscription_response(self.user)
data = self.minimal_data.copy()
data["following"] = "True"
result = create_thread(self.request, data)
assert result['following'] is True
self.check_mock_called("create_subscription")
params = {'user_id': str(self.user.id), 'course_id': str(self.course.id), 'source_id': 'test_id'}
self.check_mock_called_with("create_subscription", 0, **params)
@ddt.ddt
@disable_signal(api, "comment_created")
@@ -712,6 +724,49 @@ class UpdateThreadTest(
assert result["vote_count"] == vote_count
self.register_get_user_response(self.user, upvoted_ids=[])
@ddt.data(*itertools.product([True, False], [True, False]))
@ddt.unpack
@mock.patch("eventtracking.tracker.emit")
def test_following(self, old_following, new_following, mock_emit):
"""
Test attempts to edit the "following" field.
old_following indicates whether the thread should be followed at the
start of the test. new_following indicates the value for the "following"
field in the update. If old_following and new_following are the same, no
update should be made. Otherwise, a subscription should be POSTed or
DELETEd according to the new_following value.
"""
if old_following:
self.register_get_user_response(self.user, subscribed_thread_ids=["test_thread"])
self.register_subscription_response(self.user)
self.register_thread()
data = {"following": new_following}
signal_name = "thread_followed" if new_following else "thread_unfollowed"
mock_path = f"openedx.core.djangoapps.django_comment_common.signals.{signal_name}.send"
with mock.patch(mock_path) as signal_patch:
result = update_thread(self.request, "test_thread", data)
if old_following != new_following:
self.assertEqual(signal_patch.call_count, 1)
assert result['following'] == new_following
if old_following == new_following:
assert not self.check_mock_called("create_subscription")
else:
params = {'user_id': str(self.user.id), 'course_id': str(self.course.id), 'source_id': 'test_thread'}
if new_following:
assert self.check_mock_called("create_subscription")
else:
assert self.check_mock_called("delete_subscription")
event_name, event_data = mock_emit.call_args[0]
expected_event_action = 'followed' if new_following else 'unfollowed'
assert event_name == f'edx.forum.thread.{expected_event_action}'
assert event_data['commentable_id'] == 'original_topic'
assert event_data['id'] == 'test_thread'
assert event_data['followed'] == new_following
assert event_data['user_forums_roles'] == ['Student']
@ddt.ddt
@disable_signal(api, "comment_edited")

View File

@@ -2,7 +2,6 @@
Test cases for tasks.py
"""
from unittest import mock
from unittest.mock import Mock
import ddt
import httpretty
@@ -15,7 +14,6 @@ from common.djangoapps.student.tests.factories import StaffFactory, UserFactory
from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory
from lms.djangoapps.discussion.rest_api.tasks import (
send_response_endorsed_notifications,
send_response_notifications,
send_thread_created_notification
)
from lms.djangoapps.discussion.rest_api.tests.utils import ThreadMock, make_minimal_cs_thread
@@ -33,7 +31,6 @@ from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICAT
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from ..discussions_notifications import DiscussionNotificationSender
from .test_views import DiscussionAPIViewTestMixin
@@ -273,420 +270,6 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
self.assertEqual(handler.call_count, 1)
@ddt.ddt
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
Test for the send_response_notifications function
"""
def setUp(self):
super().setUp()
httpretty.reset()
httpretty.enable()
patcher = mock.patch(
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
return_value=False
)
patcher.start()
self.addCleanup(patcher.stop)
self.course = CourseFactory.create()
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
return_value=self.course.id
)
self.mock_get_course_id_by_thread = patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
return_value=self.course.id
)
self.mock_get_course_id_by_comment = patcher.start()
self.addCleanup(patcher.stop)
self.user_1 = UserFactory.create()
CourseEnrollment.enroll(self.user_1, self.course.id)
self.user_2 = UserFactory.create()
CourseEnrollment.enroll(self.user_2, self.course.id)
self.user_3 = UserFactory.create()
CourseEnrollment.enroll(self.user_3, self.course.id)
self.thread = ThreadMock(thread_id=1, creator=self.user_1, title='test thread')
self.thread_2 = ThreadMock(thread_id=2, creator=self.user_2, title='test thread 2')
self.thread_3 = ThreadMock(thread_id=2, creator=self.user_1, title='test thread 3')
for thread in [self.thread, self.thread_2, self.thread_3]:
self.register_get_thread_response({
'id': thread.id,
'course_id': str(self.course.id),
'topic_id': 'abc',
"user_id": thread.user_id,
"username": thread.username,
"thread_type": 'discussion',
"title": thread.title,
"commentable_id": thread.commentable_id,
})
self._register_subscriptions_endpoint()
self.comment = ThreadMock(thread_id=4, creator=self.user_2, title='test comment', body='comment body')
self.register_get_comment_response(
{
'id': self.comment.id,
'thread_id': self.thread.id,
'parent_id': None,
'user_id': self.comment.user_id,
'body': self.comment.body,
}
)
def test_basic(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_not_authenticated(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_send_notification_to_thread_creator(self):
"""
Test that the notification is sent to the thread creator
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
# Post the form or do what it takes to send the signal
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_2.id,
self.comment.id,
parent_id=None
)
self.assertEqual(handler.call_count, 2)
args = handler.call_args_list[0][1]['notification_data']
self.assertEqual([int(user_id) for user_id in args.user_ids], [self.user_1.id])
self.assertEqual(args.notification_type, 'new_response')
expected_context = {
'replier_name': self.user_2.username,
'post_title': 'test thread',
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_2.id,
'response_id': 4,
'topic_id': None,
'thread_id': 1,
'comment_id': None,
}
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args.app_name, 'discussion')
def test_send_notification_to_parent_threads(self):
"""
Test that the notification signal is sent to the parent response creator and
parent thread creator, it checks signal is sent with correct arguments for both
types of notifications.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
self.register_get_comment_response({
'id': self.thread_2.id,
'thread_id': self.thread.id,
'user_id': self.thread_2.user_id
})
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_3.id,
self.comment.id,
parent_id=self.thread_2.id
)
# check if 2 call are made to the handler i.e. one for the response creator and one for the thread creator
self.assertEqual(handler.call_count, 2)
# check if the notification is sent to the thread creator
args_comment = handler.call_args_list[0][1]['notification_data']
args_comment_on_response = handler.call_args_list[1][1]['notification_data']
self.assertEqual([int(user_id) for user_id in args_comment.user_ids], [self.user_1.id])
self.assertEqual(args_comment.notification_type, 'new_comment')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'email_content': self.comment.body,
'author_name': 'dummy\'s',
'author_pronoun': 'dummy\'s',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment.context, expected_context)
self.assertEqual(
args_comment.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment.app_name, 'discussion')
# check if the notification is sent to the parent response creator
self.assertEqual([int(user_id) for user_id in args_comment_on_response.user_ids], [self.user_2.id])
self.assertEqual(args_comment_on_response.notification_type, 'new_comment_on_response')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment_on_response.context, expected_context)
self.assertEqual(
args_comment_on_response.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment_on_response.app_name, 'discussion')
def test_no_signal_on_creators_own_thread(self):
"""
Makes sure that 1 signal is emitted if user creates response on
their own thread.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_1.id,
self.comment.id, parent_id=None
)
self.assertEqual(handler.call_count, 1)
def test_comment_creators_own_response(self):
"""
Check incase post author and response auther is same only send
new comment signal , with your as author_name.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
self.register_get_comment_response({
'id': self.thread_3.id,
'thread_id': self.thread.id,
'user_id': self.thread_3.user_id
})
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_3.id,
parent_id=self.thread_2.id,
comment_id=self.comment.id
)
# check if 1 call is made to the handler i.e. for the thread creator
self.assertEqual(handler.call_count, 2)
# check if the notification is sent to the thread creator
args_comment = handler.call_args_list[0][1]['notification_data']
self.assertEqual(args_comment.user_ids, [self.user_1.id])
self.assertEqual(args_comment.notification_type, 'new_comment')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'dummy\'s',
'author_pronoun': 'your',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'email_content': self.comment.body,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment.context, expected_context)
self.assertEqual(
args_comment.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment.app_name, 'discussion')
@ddt.data(
(None, 'response_on_followed_post'), (1, 'comment_on_followed_post')
)
@ddt.unpack
def test_send_notification_to_followers(self, parent_id, notification_type):
"""
Test that the notification is sent to the followers of the thread
"""
self.register_get_comment_response({
'id': self.thread.id,
'thread_id': self.thread.id,
'user_id': self.thread.user_id
})
handler = Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
# Post the form or do what it takes to send the signal
notification_sender = DiscussionNotificationSender(
self.thread,
self.course,
self.user_2,
parent_id=parent_id,
comment_id=self.comment.id
)
notification_sender.send_response_on_followed_post_notification()
self.assertEqual(handler.call_count, 1)
args = handler.call_args[1]['notification_data']
# only sent to user_3 because user_2 is the one who created the response
self.assertEqual([self.user_3.id], args.user_ids)
self.assertEqual(args.notification_type, notification_type)
expected_context = {
'replier_name': self.user_2.username,
'post_title': 'test thread',
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_2.id,
'response_id': 4 if notification_type == 'response_on_followed_post' else parent_id,
'topic_id': None,
'thread_id': 1,
'comment_id': 4 if not notification_type == 'response_on_followed_post' else None,
}
if parent_id:
expected_context['author_name'] = 'dummy\'s'
expected_context['author_pronoun'] = 'dummy\'s'
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args.app_name, 'discussion')
def _register_subscriptions_endpoint(self):
"""
Registers the endpoint for the subscriptions API
"""
mock_response = {
'collection': [
{
'_id': 1,
'subscriber_id': str(self.user_2.id),
"source_id": self.thread.id,
"source_type": "thread",
},
{
'_id': 2,
'subscriber_id': str(self.user_3.id),
"source_id": self.thread.id,
"source_type": "thread",
},
],
'page': 1,
'num_pages': 1,
'subscriptions_count': 2,
'corrected_text': None
}
self.register_get_subscriptions(self.thread.id, mock_response)
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
Test case to send new_comment notification
"""
def setUp(self):
super().setUp()
httpretty.reset()
httpretty.enable()
patcher = mock.patch(
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
return_value=False
)
patcher.start()
self.addCleanup(patcher.stop)
self.course = CourseFactory.create()
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
return_value=self.course.id
)
self.mock_get_course_id_by_thread = patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
return_value=self.course.id
)
self.mock_get_course_id_by_comment = patcher.start()
self.addCleanup(patcher.stop)
self.user_1 = UserFactory.create()
CourseEnrollment.enroll(self.user_1, self.course.id)
self.user_2 = UserFactory.create()
CourseEnrollment.enroll(self.user_2, self.course.id)
def test_basic(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_not_authenticated(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_new_comment_notification(self):
"""
Tests new comment notification generation
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
thread = ThreadMock(thread_id=1, creator=self.user_1, title='test thread')
response = ThreadMock(thread_id=2, creator=self.user_2, title='test response')
comment = ThreadMock(thread_id=3, creator=self.user_2, title='test comment', body='comment body')
self.register_get_thread_response({
'id': thread.id,
'course_id': str(self.course.id),
'topic_id': 'abc',
"user_id": thread.user_id,
"username": thread.username,
"thread_type": 'discussion',
"title": thread.title,
"commentable_id": thread.commentable_id,
})
self.register_get_comment_response({
'id': response.id,
'thread_id': thread.id,
'user_id': response.user_id
})
self.register_get_comment_response({
'id': comment.id,
'parent_id': response.id,
'user_id': comment.user_id,
'body': comment.body
})
self.register_get_subscriptions(1, {})
send_response_notifications(thread.id, str(self.course.id), self.user_2.id, parent_id=response.id,
comment_id=comment.id)
handler.assert_called_once()
context = handler.call_args[1]['notification_data'].context
self.assertEqual(context['author_name'], 'dummy\'s')
self.assertEqual(context['author_pronoun'], 'their')
self.assertEqual(context['email_content'], comment.body)
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestResponseEndorsedNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""

View File

@@ -0,0 +1,488 @@
"""
Test cases for forum v2 based tasks.py
"""
from unittest import mock
from unittest.mock import Mock
import ddt
import httpretty
from django.conf import settings
from edx_toggles.toggles.testutils import override_waffle_flag
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.discussion.rest_api.tasks import send_response_notifications
from lms.djangoapps.discussion.rest_api.tests.utils import ThreadMock
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from ..discussions_notifications import DiscussionNotificationSender
from .test_views_v2 import DiscussionAPIViewTestMixin
def _get_mfe_url(course_id, post_id):
"""
get discussions mfe url to specific post.
"""
return f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/{str(course_id)}/posts/{post_id}"
@ddt.ddt
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
Test for the send_response_notifications function
"""
def setUp(self):
super().setUp()
httpretty.reset()
httpretty.enable()
self.addCleanup(httpretty.disable)
self.addCleanup(httpretty.reset)
self.course = CourseFactory.create()
# Patch 1
patcher1 = mock.patch(
'openedx.core.djangoapps.django_comment_common.comment_client.thread.is_forum_v2_enabled_for_thread',
autospec=True
)
mock_forum_v2 = patcher1.start()
mock_forum_v2.return_value = (True, str(self.course.id))
self.addCleanup(patcher1.stop)
# Patch 2
patcher2 = mock.patch(
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
return_value=False
)
patcher2.start()
self.addCleanup(patcher2.stop)
# Patch 3
patcher3 = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
return_value=self.course.id
)
self.mock_get_course_id_by_thread = patcher3.start()
self.addCleanup(patcher3.stop)
# Patch 4
patcher4 = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
return_value=self.course.id
)
self.mock_get_course_id_by_comment = patcher4.start()
self.addCleanup(patcher4.stop)
# Patch 5
patcher5 = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.is_forum_v2_enabled_for_comment",
return_value=(True, str(self.course.id))
)
self.mock_is_forum_v2_enabled_for_comment = patcher5.start()
self.addCleanup(patcher5.stop)
self.user_1 = UserFactory.create()
CourseEnrollment.enroll(self.user_1, self.course.id)
self.user_2 = UserFactory.create()
CourseEnrollment.enroll(self.user_2, self.course.id)
self.user_3 = UserFactory.create()
CourseEnrollment.enroll(self.user_3, self.course.id)
self.thread = ThreadMock(thread_id=1, creator=self.user_1, title='test thread')
self.thread_2 = ThreadMock(thread_id=2, creator=self.user_2, title='test thread 2')
self.thread_3 = ThreadMock(thread_id=2, creator=self.user_1, title='test thread 3')
for thread in [self.thread_3, self.thread_2, self.thread]:
self.register_get_thread_response({
'id': thread.id,
'course_id': str(self.course.id),
'topic_id': 'abc',
"user_id": thread.user_id,
"username": thread.username,
"thread_type": 'discussion',
"title": thread.title,
"commentable_id": thread.commentable_id,
})
self._register_subscriptions_endpoint()
self.comment = ThreadMock(thread_id=4, creator=self.user_2, title='test comment', body='comment body')
self.register_get_comment_response(
{
'id': self.comment.id,
'thread_id': self.thread.id,
'parent_id': None,
'user_id': self.comment.user_id,
'body': self.comment.body,
}
)
def test_basic(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_not_authenticated(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_send_notification_to_thread_creator(self):
"""
Test that the notification is sent to the thread creator
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
# Post the form or do what it takes to send the signal
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_2.id,
self.comment.id,
parent_id=None
)
self.assertEqual(handler.call_count, 2)
args = handler.call_args_list[0][1]['notification_data']
self.assertEqual([int(user_id) for user_id in args.user_ids], [self.user_1.id])
self.assertEqual(args.notification_type, 'new_response')
expected_context = {
'replier_name': self.user_2.username,
'post_title': 'test thread',
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_2.id,
'response_id': 4,
'topic_id': None,
'thread_id': 1,
'comment_id': None,
}
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args.app_name, 'discussion')
def test_no_signal_on_creators_own_thread(self):
"""
Makes sure that 1 signal is emitted if user creates response on
their own thread.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_1.id,
self.comment.id, parent_id=None
)
self.assertEqual(handler.call_count, 1)
@ddt.data(
(None, 'response_on_followed_post'), (1, 'comment_on_followed_post')
)
@ddt.unpack
def test_send_notification_to_followers(self, parent_id, notification_type):
"""
Test that the notification is sent to the followers of the thread
"""
self.register_get_comment_response({
'id': self.thread.id,
'thread_id': self.thread.id,
'user_id': self.thread.user_id,
"body": "comment body"
})
handler = Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
# Post the form or do what it takes to send the signal
notification_sender = DiscussionNotificationSender(
self.thread,
self.course,
self.user_2,
parent_id=parent_id,
comment_id=self.comment.id
)
notification_sender.send_response_on_followed_post_notification()
self.assertEqual(handler.call_count, 1)
args = handler.call_args[1]['notification_data']
# only sent to user_3 because user_2 is the one who created the response
self.assertEqual([self.user_3.id], args.user_ids)
self.assertEqual(args.notification_type, notification_type)
expected_context = {
'replier_name': self.user_2.username,
'post_title': 'test thread',
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_2.id,
'response_id': 4 if notification_type == 'response_on_followed_post' else parent_id,
'topic_id': None,
'thread_id': 1,
'comment_id': 4 if not notification_type == 'response_on_followed_post' else None,
}
if parent_id:
expected_context['author_name'] = 'dummy\'s'
expected_context['author_pronoun'] = 'dummy\'s'
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args.app_name, 'discussion')
def test_comment_creators_own_response(self):
"""
Check incase post author and response auther is same only send
new comment signal , with your as author_name.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
self.register_get_comment_response({
'id': self.thread_3.id,
'thread_id': self.thread.id,
'user_id': self.thread_3.user_id,
'body': 'comment body',
})
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_3.id,
parent_id=self.thread_2.id,
comment_id=self.comment.id
)
# check if 1 call is made to the handler i.e. for the thread creator
self.assertEqual(handler.call_count, 2)
# check if the notification is sent to the thread creator
args_comment = handler.call_args_list[0][1]['notification_data']
self.assertEqual(args_comment.user_ids, [self.user_1.id])
self.assertEqual(args_comment.notification_type, 'new_comment')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'dummy\'s',
'author_pronoun': 'your',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'email_content': self.comment.body,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment.context, expected_context)
self.assertEqual(
args_comment.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment.app_name, 'discussion')
def test_send_notification_to_parent_threads(self):
"""
Test that the notification signal is sent to the parent response creator and
parent thread creator, it checks signal is sent with correct arguments for both
types of notifications.
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
self.register_get_comment_response({
'id': self.thread_2.id,
'thread_id': self.thread.id,
'user_id': self.thread_2.user_id,
'body': 'comment body'
})
send_response_notifications(
self.thread.id,
str(self.course.id),
self.user_3.id,
self.comment.id,
parent_id=self.thread_2.id
)
# check if 2 call are made to the handler i.e. one for the response creator and one for the thread creator
self.assertEqual(handler.call_count, 2)
# check if the notification is sent to the thread creator
args_comment = handler.call_args_list[0][1]['notification_data']
args_comment_on_response = handler.call_args_list[1][1]['notification_data']
self.assertEqual([int(user_id) for user_id in args_comment.user_ids], [self.user_1.id])
self.assertEqual(args_comment.notification_type, 'new_comment')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'email_content': self.comment.body,
'author_name': 'dummy\'s',
'author_pronoun': 'dummy\'s',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment.context, expected_context)
self.assertEqual(
args_comment.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment.app_name, 'discussion')
# check if the notification is sent to the parent response creator
self.assertEqual([int(user_id) for user_id in args_comment_on_response.user_ids], [self.user_2.id])
self.assertEqual(args_comment_on_response.notification_type, 'new_comment_on_response')
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'email_content': self.comment.body,
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
'response_id': 2,
'topic_id': None,
'thread_id': 1,
'comment_id': 4,
}
self.assertDictEqual(args_comment_on_response.context, expected_context)
self.assertEqual(
args_comment_on_response.content_url,
_get_mfe_url(self.course.id, self.thread.id)
)
self.assertEqual(args_comment_on_response.app_name, 'discussion')
def _register_subscriptions_endpoint(self):
"""
Registers the endpoint for the subscriptions API
"""
mock_response = {
'collection': [
{
'_id': 1,
'subscriber_id': str(self.user_2.id),
"source_id": self.thread.id,
"source_type": "thread",
},
{
'_id': 2,
'subscriber_id': str(self.user_3.id),
"source_id": self.thread.id,
"source_type": "thread",
},
],
'page': 1,
'num_pages': 1,
'subscriptions_count': 2,
'corrected_text': None
}
self.register_get_subscriptions(self.thread.id, mock_response)
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
Test case to send new_comment notification
"""
def setUp(self):
super().setUp()
httpretty.reset()
httpretty.enable()
patcher = mock.patch(
'openedx.core.djangoapps.discussions.config.waffle.ENABLE_FORUM_V2.is_enabled',
return_value=False
)
patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch(
'openedx.core.djangoapps.django_comment_common.comment_client.thread.is_forum_v2_enabled_for_thread',
autospec=True
)
mock_forum_v2 = patcher.start()
mock_forum_v2.return_value = (True, str(self.course.id))
self.addCleanup(patcher.stop)
self.course = CourseFactory.create()
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.thread.forum_api.get_course_id_by_thread",
return_value=self.course.id
)
self.mock_get_course_id_by_thread = patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.forum_api.get_course_id_by_comment",
return_value=self.course.id
)
self.mock_get_course_id_by_comment = patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch(
"openedx.core.djangoapps.django_comment_common.comment_client.models.is_forum_v2_enabled_for_comment",
return_value=(True, str(self.course.id))
)
self.mock_is_forum_v2_enabled_for_comment = patcher.start()
self.addCleanup(patcher.stop)
self.user_1 = UserFactory.create()
CourseEnrollment.enroll(self.user_1, self.course.id)
self.user_2 = UserFactory.create()
CourseEnrollment.enroll(self.user_2, self.course.id)
def test_basic(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_not_authenticated(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""
def test_new_comment_notification(self):
"""
Tests new comment notification generation
"""
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
thread = ThreadMock(thread_id=1, creator=self.user_1, title='test thread')
response = ThreadMock(thread_id=2, creator=self.user_2, title='test response')
comment = ThreadMock(thread_id=3, creator=self.user_2, title='test comment', body='comment body')
self.register_get_thread_response({
'id': thread.id,
'course_id': str(self.course.id),
'topic_id': 'abc',
"user_id": thread.user_id,
"username": thread.username,
"thread_type": 'discussion',
"title": thread.title,
"commentable_id": thread.commentable_id,
})
self.register_get_comment_response({
'id': response.id,
'thread_id': thread.id,
'user_id': response.user_id
})
self.register_get_comment_response({
'id': comment.id,
'parent_id': response.id,
'user_id': comment.user_id,
'body': comment.body
})
self.register_get_subscriptions(1, {})
send_response_notifications(thread.id, str(self.course.id), self.user_2.id, parent_id=response.id,
comment_id=comment.id)
handler.assert_called_once()
context = handler.call_args[1]['notification_data'].context
self.assertEqual(context['author_name'], 'dummy\'s')
self.assertEqual(context['author_pronoun'], 'their')
self.assertEqual(context['email_content'], comment.body)

View File

@@ -5,7 +5,6 @@ import logging
from . import models, settings, utils
from forum import api as forum_api
from openedx.core.djangoapps.discussions.config.waffle import is_forum_v2_enabled
log = logging.getLogger(__name__)
@@ -36,22 +35,12 @@ class Subscription(models.Model):
utils.strip_blank(utils.strip_none(query_params))
)
course_key = utils.get_course_key(course_id)
if is_forum_v2_enabled(course_key):
response = forum_api.get_thread_subscriptions(
thread_id=thread_id,
page=params["page"],
per_page=params["per_page"],
course_id=str(course_key)
)
else:
response = utils.perform_request(
'get',
cls.url(action='get', params=params) + "/subscriptions",
params,
metric_tags=[],
metric_action='subscription.get',
paged_results=True
)
response = forum_api.get_thread_subscriptions(
thread_id=thread_id,
page=params["page"],
per_page=params["per_page"],
course_id=str(course_key)
)
return utils.SubscriptionsPaginatedResult(
collection=response.get('collection', []),
page=response.get('page', 1),

View File

@@ -52,39 +52,19 @@ class User(models.Model):
def follow(self, source, course_id=None):
course_key = utils.get_course_key(self.attributes.get("course_id") or course_id)
if is_forum_v2_enabled(course_key):
forum_api.create_subscription(
user_id=self.id,
source_id=source.id,
course_id=str(course_key)
)
else:
params = {'source_type': source.type, 'source_id': source.id}
utils.perform_request(
'post',
_url_for_subscription(self.id),
params,
metric_action='user.follow',
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
)
forum_api.create_subscription(
user_id=self.id,
source_id=source.id,
course_id=str(course_key)
)
def unfollow(self, source, course_id=None):
course_key = utils.get_course_key(self.attributes.get("course_id") or course_id)
if is_forum_v2_enabled(course_key):
forum_api.delete_subscription(
user_id=self.id,
source_id=source.id,
course_id=str(course_key)
)
else:
params = {'source_type': source.type, 'source_id': source.id}
utils.perform_request(
'delete',
_url_for_subscription(self.id),
params,
metric_action='user.unfollow',
metric_tags=self._metric_tags + [f'target.type:{source.type}'],
)
forum_api.delete_subscription(
user_id=self.id,
source_id=source.id,
course_id=str(course_key)
)
def vote(self, voteable, value, course_id=None):
course_key = utils.get_course_key(self.attributes.get("course_id") or course_id)