def test_resubmit_accepted_suggestion_failure(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.mock_generate_new_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.swap(suggestion_registry.SuggestionEditStateContent, 'accept', self.mock_accept_does_nothing): suggestion_services.accept_suggestion( suggestion, self.reviewer_id, self.COMMIT_MESSAGE, 'review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp( Exception, 'The suggestion was accepted. Only rejected suggestions ' 'can be resubmitted.'): suggestion_services.resubmit_rejected_suggestion( suggestion, 'resubmit summary message', self.author_id)
def put(self, target_id, suggestion_id): if suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_SKILL: raise self.InvalidInputException( 'This handler allows actions only on suggestions to skills.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The skill id provided does not match the skill id present as ' 'part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if action == suggestion_models.ACTION_TYPE_ACCEPT: # Question suggestions do not use commit messages. suggestion_services.accept_suggestion( suggestion, self.user_id, 'UNUSED_COMMIT_MESSAGE', self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def put(self, target_id, suggestion_id): if (suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_EXPLORATION): raise self.InvalidInputException( 'This handler allows actions only' ' on suggestions to explorations.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The exploration id provided does ' 'not match the exploration id ' 'present as part of the ' 'suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.author_id == self.user_id: raise self.UnauthorizedUserException( 'You cannot accept/reject your' ' own suggestion.') if action == suggestion_models.ACTION_TYPE_ACCEPT: suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def test_accept_suggestion_invalid_suggestion_failure(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) # Invalidating the suggestion. suggestion.score_category = 'invalid_score_category' with self.assertRaisesRegexp( utils.ValidationError, 'Expected score_category to be of the form ' 'score_type.score_sub_type, received ' 'invalid_score_category'): suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id)
def put(self, target_id, suggestion_id): if not constants.USE_NEW_SUGGESTION_FRAMEWORK: raise self.PageNotFoundException if not feconf.ENABLE_NEW_STRUCTURES: raise self.PageNotFoundException if len(suggestion_id.split('.')) != 3: raise self.InvalidInputException( 'Invalid format for suggestion_id. It must contain 3 parts' ' separated by \'.\'') if suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_TOPIC: raise self.InvalidInputException( 'This handler allows actions only on suggestions to topics.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The topic id provided does not match the topic id present as ' 'part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if action == suggestion_models.ACTION_TYPE_ACCEPT: suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def put(self, target_id, suggestion_id): if not constants.ENABLE_NEW_STRUCTURE_VIEWER_UPDATES: raise self.PageNotFoundException if suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_SKILL: raise self.InvalidInputException( 'This handler allows actions only on suggestions to skills.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The skill id provided does not match the skill id present as ' 'part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if action == suggestion_models.ACTION_TYPE_ACCEPT: if ( suggestion.suggestion_type == suggestion_models.SUGGESTION_TYPE_ADD_QUESTION): # The skill_id is passed only at the time of accepting the # suggestion. suggestion.change.skill_id = target_id suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def test_accept_suggestion_handled_suggestion_failure(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, self.score_category, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) suggestion.status = suggestion_models.STATUS_ACCEPTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) suggestion.status = suggestion_models.STATUS_REJECTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
def put(self, exploration_id, suggestion_id): if not constants.USE_NEW_SUGGESTION_FRAMEWORK: raise self.PageNotFoundException if len(suggestion_id.split('.')) != 3: raise self.InvalidInputException( 'Invalid format for suggestion_id.' ' It must contain 3 parts' ' separated by \'.\'') if suggestion_id.split('.')[0] != 'exploration': raise self.InvalidInputException( 'This handler allows actions only' ' on suggestions to explorations.') if suggestion_id.split('.')[1] != exploration_id: raise self.InvalidInputException( 'The exploration id provided does ' 'not match the exploration id ' 'present as part of the ' 'suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if action == self.ACTION_TYPE_ACCEPT: suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == self.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def test_create_and_accept_suggestion_with_message(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.assigned_reviewer_id, None) suggestion_id = 'exploration.' + self.THREAD_ID suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, 'Accept message') exploration = exp_services.get_exploration_by_id(self.EXP_ID) thread_messages = feedback_services.get_messages(self.THREAD_ID) last_message = thread_messages[len(thread_messages) - 1] self.assertEqual(last_message.text, 'Accept message') self.assertEqual(exploration.states['State 1'].content.html, 'new content') self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED)
def test_accept_suggestion_successfully(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.swap(exp_services, 'update_exploration', self.check_commit_message): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(suggestion_registry.SuggestionEditStateContent, 'pre_accept_validate', self.null_function): with self.swap( suggestion_registry.SuggestionEditStateContent, 'get_change_list_for_accepting_suggestion', self.null_function): suggestion_services.accept_suggestion( suggestion, self.reviewer_id, self.COMMIT_MESSAGE, 'review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) self.assertEqual(suggestion.final_reviewer_id, self.reviewer_id) thread_messages = feedback_services.get_messages(self.THREAD_ID) last_message = thread_messages[len(thread_messages) - 1] self.assertEqual(last_message.text, 'review message')
def put(self, target_id, suggestion_id): """Handles PUT requests. Args: target_id: str. The ID of the suggestion target. suggestion_id: str. The ID of the suggestion. """ if suggestion_id.split('.')[0] != feconf.ENTITY_TYPE_SKILL: raise self.InvalidInputException( 'This handler allows actions only on suggestions to skills.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The skill id provided does not match the skill id present as ' 'part of the suggestion_id') action = self.payload.get('action') if action == constants.ACTION_ACCEPT_SUGGESTION: # Question suggestions do not use commit messages. suggestion_services.accept_suggestion( suggestion_id, self.user_id, 'UNUSED_COMMIT_MESSAGE', self.payload.get('review_message')) elif action == constants.ACTION_REJECT_SUGGESTION: suggestion_services.reject_suggestion( suggestion_id, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def get(self): """Handles get requests.""" if feconf.ENABLE_AUTO_ACCEPT_OF_SUGGESTIONS: suggestions = suggestion_services.get_all_stale_suggestions() for suggestion in suggestions: suggestion_services.accept_suggestion( suggestion, feconf.SUGGESTION_BOT_USER_ID, suggestion_models.DEFAULT_SUGGESTION_ACCEPT_MESSAGE, None)
def test_accept_suggestion_and_send_email_to_author(self): enable_recording_of_scores_swap = self.swap( feconf, 'ENABLE_RECORDING_OF_SCORES', True) send_suggestion_review_related_emails_swap = self.swap( feconf, 'SEND_SUGGESTION_REVIEW_RELATED_EMAILS', True) change_list = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_ADD_STATE, 'state_name': 'state 1', }) ] exp_services.update_exploration(self.author_id, self.target_id, change_list, 'Add state.') new_suggestion_content = state_domain.SubtitledHtml( 'content', 'new suggestion content html').to_dict() change_dict = { 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': exp_domain.STATE_PROPERTY_CONTENT, 'state_name': 'state 1', 'new_value': new_suggestion_content } suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, change_dict, 'test description', self.reviewer_id) suggestion = suggestion_services.query_suggestions([ ('author_id', self.author_id), ('target_id', self.target_id) ])[0] self.assertEqual(suggestion.status, suggestion_models.STATUS_IN_REVIEW) self.assertFalse( suggestion_services.check_if_email_has_been_sent_to_user( self.author_id, suggestion.score_category)) suggestion_services.increment_score_for_user(self.author_id, suggestion.score_category, 10) with enable_recording_of_scores_swap, ( send_suggestion_review_related_emails_swap): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, 'review message') suggestion = suggestion_services.query_suggestions([ ('author_id', self.author_id), ('target_id', self.target_id) ])[0] self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) self.assertTrue( suggestion_services.check_if_email_has_been_sent_to_user( self.author_id, suggestion.score_category))
def _accept_suggestion(self, suggestion_id, reviewer_id): """Accepts a suggestion.""" with self.swap(exp_services, 'update_exploration', self.mock_update_exploration): with self.swap(exp_fetchers, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(exp_domain.Exploration, 'get_content_html', self.MockExploration.get_content_html): suggestion_services.accept_suggestion( suggestion_id, reviewer_id, 'commit_message', 'review_message')
def test_accept_suggestion_handled_suggestion_failure(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(constants, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) suggestion.status = suggestion_models.STATUS_ACCEPTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): with self.swap(constants, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) suggestion.status = suggestion_models.STATUS_REJECTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): with self.swap(constants, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
def test_accept_suggestion_no_commit_message_failure(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, self.score_category, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp(Exception, 'Commit message cannot be empty.'): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.EMPTY_COMMIT_MESSAGE, None)
def put(self, target_id, suggestion_id): """Handles PUT requests. Args: target_id: str. The ID of the suggestion target. suggestion_id: str. The ID of the suggestion. """ if suggestion_id.split('.')[0] != feconf.ENTITY_TYPE_SKILL: raise self.InvalidInputException( 'This handler allows actions only on suggestions to skills.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The skill id provided does not match the skill id present as ' 'part of the suggestion_id') action = self.payload.get('action') if action == constants.ACTION_ACCEPT_SUGGESTION: # Question suggestions do not use commit messages. suggestion_services.accept_suggestion( suggestion_id, self.user_id, 'UNUSED_COMMIT_MESSAGE', self.payload.get('review_message')) suggestion = suggestion_services.get_suggestion_by_id( suggestion_id) target_entity_html_list = ( suggestion.get_target_entity_html_strings()) target_image_filenames = ( html_cleaner.get_image_filenames_from_html_strings( target_entity_html_list)) fs_services.copy_images(suggestion.target_type, suggestion.target_id, feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS, suggestion.target_id, target_image_filenames) elif action == constants.ACTION_REJECT_SUGGESTION: suggestion_services.reject_suggestion( suggestion_id, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def put(self, target_id, suggestion_id): """Handles PUT requests. Args: target_id: str. The ID of the suggestion target. suggestion_id: str. The ID of the suggestion. """ if (suggestion_id.split('.')[0] != feconf.ENTITY_TYPE_EXPLORATION): raise self.InvalidInputException( 'This handler allows actions only' ' on suggestions to explorations.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The exploration id provided does not match the exploration id ' 'present as part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.author_id == self.user_id: raise self.UnauthorizedUserException( 'You cannot accept/reject your own suggestion.') if action == constants.ACTION_ACCEPT_SUGGESTION: commit_message = self.payload.get('commit_message') if (commit_message is not None and len(commit_message) > constants.MAX_COMMIT_MESSAGE_LENGTH): raise self.InvalidInputException( 'Commit messages must be at most %s characters long.' % constants.MAX_COMMIT_MESSAGE_LENGTH) suggestion_services.accept_suggestion( suggestion_id, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == constants.ACTION_REJECT_SUGGESTION: suggestion_services.reject_suggestion( suggestion_id, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def test_create_and_accept_suggestion(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID, self.target_version_at_submission, self.author_id, self.change, 'test description', None) suggestion_id = self.THREAD_ID suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) exploration = exp_services.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.states['State 1'].content.html, 'new content') self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED)
def test_accept_suggestion_no_commit_message_failure(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(feconf, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp(Exception, 'Commit message cannot be empty.'): with self.swap(feconf, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.accept_suggestion( suggestion, self.reviewer_id, self.EMPTY_COMMIT_MESSAGE, None)
def test_cron_mail_reviewers_in_rotation_handler(self): self.login(self.ADMIN_EMAIL, is_super_admin=True) reviewer_ids = [] score_categories = [] def _mock_send_mail_to_notify_users_to_review(reviewer_id, score_category): """Mocks email_manager.send_mail_to_notify_users_to_review() as its not possible to send mail with self.testapp_swap, i.e with the URLs defined in main_cron. """ reviewer_ids.append(reviewer_id) score_categories.append(score_category) send_mail_to_notify_users_to_review_swap = self.swap( email_manager, 'send_mail_to_notify_users_to_review', _mock_send_mail_to_notify_users_to_review) self.save_new_valid_exploration('exp_id', self.admin_id, title='A title', category='Algebra') new_content = state_domain.SubtitledHtml( 'content', '<p>new suggestion content</p>').to_dict() change = { 'cmd': 'edit_state_property', 'property_name': 'content', 'state_name': 'Introduction', 'new_value': new_content } suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, 'exp_id', 1, feconf.SYSTEM_COMMITTER_ID, change, 'change title', self.admin_id) exploration = exp_services.get_exploration_by_id('exp_id') self.assertEqual(exploration.states['Introduction'].content.to_dict(), { 'content_id': 'content', 'html': '' }) suggestion = suggestion_services.query_suggestions([ ('author_id', feconf.SYSTEM_COMMITTER_ID), ('target_id', 'exp_id') ])[0] suggestion_services.accept_suggestion( suggestion, self.admin_id, suggestion_models.DEFAULT_SUGGESTION_ACCEPT_MESSAGE, None) exploration = exp_services.get_exploration_by_id('exp_id') self.assertEqual(exploration.states['Introduction'].content.to_dict(), { 'content_id': 'content', 'html': '<p>new suggestion content</p>' }) send_suggestion_review_related_emails_swap = self.swap( feconf, 'SEND_SUGGESTION_REVIEW_RELATED_EMAILS', True) with self.testapp_swap, send_suggestion_review_related_emails_swap, ( send_mail_to_notify_users_to_review_swap): self.get_html_response('/cron/suggestions/notify_reviewers') self.assertEqual(reviewer_ids, [None]) self.assertEqual(score_categories, ['content.Algebra'])