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:
committed by
David Ormsbee
parent
cdf5083544
commit
d29171c046
@@ -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):
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
488
lms/djangoapps/discussion/rest_api/tests/test_tasks_v2.py
Normal file
488
lms/djangoapps/discussion/rest_api/tests/test_tasks_v2.py
Normal 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)
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user