Exemplo n.º 1
0
 def test_get_suggestions_list(self):
     self.login(self.OWNER_EMAIL)
     suggestions = self.get_json(
         feconf.CREATOR_DASHBOARD_DATA_URL)['created_suggestions_list']
     self.assertEqual(suggestions, [])
     change_dict = {
         'cmd': 'edit_state_property',
         'property_name': 'content',
         'state_name': 'Introduction',
         'new_value': ''
     }
     self.save_new_default_exploration('exploration_id', self.owner_id)
     suggestion_services.create_suggestion('edit_exploration_state_content',
                                           'exploration', 'exploration_id',
                                           1, self.owner_id, change_dict,
                                           '')
     suggestions = self.get_json(
         feconf.CREATOR_DASHBOARD_DATA_URL)['created_suggestions_list'][0]
     change_dict['old_value'] = {'content_id': 'content', 'html': ''}
     self.assertEqual(suggestions['change'], change_dict)
     # Test to check if suggestions populate old value of the change.
     self.assertEqual(suggestions['change']['old_value']['content_id'],
                      'content')
     self.logout()
Exemplo n.º 2
0
    def test_reject_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):
                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.'):
            suggestion_services.reject_suggestion(suggestion, self.reviewer_id,
                                                  'reject review message')

        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.reject_suggestion(suggestion, self.reviewer_id,
                                                  'reject review message')
        suggestion = suggestion_services.get_suggestion_by_id(
            self.suggestion_id)
        self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
Exemplo n.º 3
0
    def test_create_and_reject_suggestion(self):
        with self.swap(feedback_models.GeneralFeedbackThreadModel,
                       'generate_new_thread_id',
                       self.mock_generate_new_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.reject_suggestion(suggestion, self.reviewer_id,
                                              'Reject message')

        exploration = exp_fetchers.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, 'Reject message')
        self.assertEqual(exploration.states['State 1'].content.html,
                         '<p>old content</p>')

        self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
Exemplo n.º 4
0
    def test_reject_suggestion_successfully(self):
        with self.swap(feedback_models.GeneralFeedbackThreadModel,
                       'generate_new_thread_id',
                       self.mock_generate_new_thread_id):
            with self.swap(exp_fetchers, '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)
        suggestion_services.reject_suggestion(suggestion, self.reviewer_id,
                                              'reject review message')
        suggestion = suggestion_services.get_suggestion_by_id(
            self.suggestion_id)
        self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
        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, 'reject review message')
Exemplo n.º 5
0
    def test_post_feedback_threads_with_updated_suggestion_status_raises_400(
            self):
        self.login(self.OWNER_EMAIL_1)
        csrf_token = self.get_new_csrf_token()

        new_content = state_domain.SubtitledHtml(
            'content', '<p>new content html</p>').to_dict()
        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'Welcome!',
            'new_value': new_content
        }
        suggestion_services.create_suggestion(
            suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT,
            suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID, 1,
            self.owner_id_1, change, 'sample description', None)

        thread_id = suggestion_services.query_suggestions([
            ('author_id', self.owner_id_1), ('target_id', self.EXP_ID)
        ])[0].suggestion_id

        thread_url = '%s/%s' % (feconf.FEEDBACK_THREAD_URL_PREFIX, thread_id)
        response = self.post_json(thread_url, {
            'text': 'Message 1',
            'updated_subject': None,
            'updated_status': 'open'
        },
                                  csrf_token=csrf_token,
                                  expected_status_int=400)

        self.assertEqual(
            response['error'],
            'Suggestion thread status cannot be changed manually.')

        self.logout()
Exemplo n.º 6
0
    def test_handler_does_not_return_in_review_content(self):
        change_dict = {
            'cmd': 'add_written_translation',
            'state_name': 'Introduction',
            'content_id': 'content',
            'language_code': 'hi',
            'content_html': '',
            'translation_html': '<p>Translation for content.</p>',
            'data_format': 'html'
        }
        suggestion_services.create_suggestion(
            feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT,
            feconf.ENTITY_TYPE_EXPLORATION, '0', 1, self.owner_id, change_dict,
            'description')

        output = self.get_json('/gettranslatabletexthandler',
                               params={
                                   'language_code': 'hi',
                                   'exp_id': '0'
                               })

        expected_output = {
            'version': 1,
            'state_names_to_content_id_mapping': {
                'End State': {
                    'content': {
                        'content': '',
                        'data_format': 'html',
                        'content_type': 'content',
                        'interaction_id': None,
                        'rule_type': None
                    }
                }
            }
        }
        self.assertEqual(output, expected_output)
    def test_create_and_accept_suggestion(self):
        with self.swap(feedback_models.GeneralFeedbackThreadModel,
                       'generate_new_thread_id', self.generate_thread_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.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)

        with self.swap(feconf, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True):
            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)
Exemplo n.º 8
0
    def _create_translation_suggestion(self):
        """Creates a translation suggestion."""
        add_translation_change_dict = {
            'cmd': exp_domain.CMD_ADD_TRANSLATION,
            'state_name': feconf.DEFAULT_INIT_STATE_NAME,
            'content_id': feconf.DEFAULT_NEW_STATE_CONTENT_ID,
            'language_code': self.language_code,
            'content_html': feconf.DEFAULT_INIT_STATE_CONTENT_STR,
            'translation_html': self.default_translation_html
        }

        return suggestion_services.create_suggestion(
            suggestion_models.SUGGESTION_TYPE_TRANSLATE_CONTENT,
            suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id,
            feconf.CURRENT_STATE_SCHEMA_VERSION, self.author_id,
            add_translation_change_dict, 'test description')
Exemplo n.º 9
0
    def _create_translation_suggestion_with_language_code(self, language_code):
        """Creates a translation suggestion in the given language_code."""
        add_translation_change_dict = {
            'cmd': exp_domain.CMD_ADD_TRANSLATION,
            'state_name': feconf.DEFAULT_INIT_STATE_NAME,
            'content_id': feconf.DEFAULT_NEW_STATE_CONTENT_ID,
            'language_code': language_code,
            'content_html': feconf.DEFAULT_INIT_STATE_CONTENT_STR,
            'translation_html': '<p>This is the translated content.</p>'
        }

        return suggestion_services.create_suggestion(
            feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT,
            feconf.ENTITY_TYPE_EXPLORATION, self.target_id,
            feconf.CURRENT_STATE_SCHEMA_VERSION, self.author_id,
            add_translation_change_dict, 'test description')
Exemplo n.º 10
0
    def post(self):
        """Handles POST requests."""
        if (self.normalized_payload.get('suggestion_type') ==
                feconf.SUGGESTION_TYPE_EDIT_STATE_CONTENT):
            raise self.InvalidInputException(
                'Content suggestion submissions are no longer supported.')

        suggestion = suggestion_services.create_suggestion(
            self.normalized_payload.get('suggestion_type'),
            self.normalized_payload.get('target_type'),
            self.normalized_payload.get('target_id'),
            self.normalized_payload.get('target_version_at_submission'),
            self.user_id,
            self.normalized_payload.get('change'),
            self.normalized_payload.get('description'))

        suggestion_change = suggestion.change
        if (
                suggestion_change.cmd == 'add_written_translation' and
                suggestion_change.data_format in
                (
                    state_domain.WrittenTranslation
                    .DATA_FORMAT_SET_OF_NORMALIZED_STRING,
                    state_domain.WrittenTranslation
                    .DATA_FORMAT_SET_OF_UNICODE_STRING
                )
        ):
            self.render_json(self.values)
            return

        # Images for question suggestions are already stored in the server
        # before actually the question is submitted. Therefore no need of
        # uploading images when the suggestion type is 'add_question'. But this
        # is not good, since when the user cancels a question suggestion after
        # adding an image, there is no method to remove the uploaded image.
        # See more - https://github.com/oppia/oppia/issues/14298
        if self.normalized_payload.get(
            'suggestion_type') != (feconf.SUGGESTION_TYPE_ADD_QUESTION):
            _upload_suggestion_images(
                self.normalized_payload.get('files'),
                suggestion,
                suggestion.get_new_image_filenames_added_in_suggestion())

        self.render_json(self.values)
Exemplo n.º 11
0
    def _create_question_suggestion(self):
        """Creates a question suggestion."""
        add_question_change_dict = {
            'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
            'question_dict': {
                'question_state_data':
                self._create_valid_question_data('default_state').to_dict(),
                'language_code':
                constants.DEFAULT_LANGUAGE_CODE,
                'question_state_data_schema_version':
                (feconf.CURRENT_STATE_SCHEMA_VERSION),
                'linked_skill_ids': ['skill_1'],
                'inapplicable_skill_misconception_ids': ['skillid12345-1']
            },
            'skill_id': self.skill_id,
            'skill_difficulty': 0.3
        }

        return suggestion_services.create_suggestion(
            feconf.SUGGESTION_TYPE_ADD_QUESTION, feconf.ENTITY_TYPE_SKILL,
            self.skill_id, feconf.CURRENT_STATE_SCHEMA_VERSION, self.author_id,
            add_question_change_dict, 'test description')
    def test_fixes_invalid_unicode_translations(self):
        exp_id = 'EXP_ID'
        exploration = exp_domain.Exploration.create_default_exploration(
            exp_id, title='title', category='category', language_code='bn')
        self.set_interaction_for_state(
            exploration.states[exploration.init_state_name], 'Continue')
        exp_services.save_new_exploration(self.albert_id, exploration)
        add_translation_change_dict = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'Introduction',
            'content_id': 'ca_buttonText_0',
            'language_code': 'bn',
            'content_html': 'Continue',
            'translation_html': '<p>চালিয়ে যান</p>',
            'data_format': 'html'
        }

        invalid_suggestion = suggestion_services.create_suggestion(
            feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT,
            feconf.ENTITY_TYPE_EXPLORATION, exp_id, exploration.version,
            self.albert_id, add_translation_change_dict, 'test description')
        self.assertEqual(invalid_suggestion.change.data_format, 'html')
        self.assertEqual(invalid_suggestion.change.translation_html,
                         '<p>চালিয়ে যান</p>')

        expected_output = [
            u'[u\'UPDATED\', [u\'%s | ca_buttonText_0\']]' %
            invalid_suggestion.suggestion_id, u'[u\'PROCESSED\', 1]'
        ]
        self._run_job_and_verify_output(expected_output)

        valid_suggestion = suggestion_services.get_suggestion_by_id(
            invalid_suggestion.suggestion_id)
        self.assertEqual(valid_suggestion.change.data_format, 'unicode')
        self.assertEqual(valid_suggestion.change.translation_html,
                         'চালিয়ে যান')
    def _create_translation_suggestion(self, language_code):
        """Creates a translation suggestion."""
        add_translation_change_dict = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'state_1',
            'content_id': 'content',
            'language_code': language_code,
            'content_html': '<p>This is html to translate.</p>',
            'translation_html': '<p>This is translated html.</p>',
            'data_format': 'html'
        }

        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):
                translation_suggestion = (
                    suggestion_services.create_suggestion(
                        feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT,
                        feconf.ENTITY_TYPE_EXPLORATION, self.target_id,
                        self.target_version_at_submission, self.author_id,
                        add_translation_change_dict, 'test description'))

        return translation_suggestion
    def test_migration_job_does_not_convert_up_to_date_suggestion(self):
        suggestion_change = {
            'cmd': (question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
            'question_dict': {
                'question_state_data':
                self._create_valid_question_data('default_state').to_dict(),
                'language_code':
                'en',
                'question_state_data_schema_version':
                (feconf.CURRENT_STATE_SCHEMA_VERSION),
                'linked_skill_ids': [self.skill_id],
                'inapplicable_skill_misconception_ids': []
            },
            'skill_id': self.skill_id,
            'skill_difficulty': 0.3
        }

        suggestion = suggestion_services.create_suggestion(
            feconf.SUGGESTION_TYPE_ADD_QUESTION, feconf.ENTITY_TYPE_SKILL,
            self.skill_id, 1, self.albert_id, suggestion_change,
            'test description')

        self.assertEqual(
            suggestion.change.
            question_dict['question_state_data_schema_version'],
            feconf.CURRENT_STATE_SCHEMA_VERSION)

        expected_output = [u'[u\'SUCCESS\', 1]']
        self._run_job_and_verify_output(expected_output)

        updated_suggestion = suggestion_services.get_suggestion_by_id(
            suggestion.suggestion_id)
        self.assertEqual(
            updated_suggestion.change.
            question_dict['question_state_data_schema_version'],
            feconf.CURRENT_STATE_SCHEMA_VERSION)
Exemplo n.º 15
0
    def post(self):
        """Handles POST requests."""
        if (self.payload.get('suggestion_type') ==
                feconf.SUGGESTION_TYPE_EDIT_STATE_CONTENT):
            raise self.InvalidInputException(
                'Content suggestion submissions are no longer supported.')

        try:
            suggestion = suggestion_services.create_suggestion(
                self.payload.get('suggestion_type'),
                self.payload.get('target_type'), self.payload.get('target_id'),
                self.payload.get('target_version_at_submission'), self.user_id,
                self.payload.get('change'), self.payload.get('description'))
        except utils.ValidationError as e:
            raise self.InvalidInputException(e)

        suggestion_change = suggestion.change
        if (suggestion_change.cmd == 'add_written_translation'
                and (suggestion_change.data_format == state_domain.
                     WrittenTranslation.DATA_FORMAT_SET_OF_NORMALIZED_STRING
                     or suggestion_change.data_format == state_domain.
                     WrittenTranslation.DATA_FORMAT_SET_OF_UNICODE_STRING)):
            self.render_json(self.values)
            return

        # TODO(#10513) : Find a way to save the images before the suggestion is
        # created.
        suggestion_image_context = suggestion.image_context

        new_image_filenames = (
            suggestion.get_new_image_filenames_added_in_suggestion())
        for filename in new_image_filenames:
            image = self.request.get(filename)
            if not image:
                logging.exception(
                    'Image not provided for file with name %s when the '
                    ' suggestion with target id %s was created.' %
                    (filename, suggestion.target_id))
                raise self.InvalidInputException(
                    'No image data provided for file with name %s.' %
                    (filename))
            try:
                file_format = (
                    image_validation_services.validate_image_and_filename(
                        image, filename))
            except utils.ValidationError as e:
                raise self.InvalidInputException('%s' % (e))
            image_is_compressible = (file_format
                                     in feconf.COMPRESSIBLE_IMAGE_FORMATS)
            fs_services.save_original_and_compressed_versions_of_image(
                filename, suggestion_image_context, suggestion.target_id,
                image, 'image', image_is_compressible)

        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,
                                suggestion_image_context, suggestion.target_id,
                                target_image_filenames)

        self.render_json(self.values)
Exemplo n.º 16
0
    def test_email_is_sent_when_suggestion_created(self):
        """Tests SuggestionEmailHandler functionality."""

        user_id_b = self.user_id_b

        class MockActivityRights:
            def __init__(self,
                         exploration_id,
                         owner_ids,
                         editor_ids,
                         voice_artist_ids,
                         viewer_ids,
                         community_owned=False,
                         cloned_from=None,
                         status=True,
                         viewable_if_private=False,
                         first_published_msec=None):
                # User B ID hardcoded into owner_ids to get email_manager
                # to send email to user B to test functionality.
                self.id = exploration_id
                self.getLintToShutUp = owner_ids
                self.editor_ids = editor_ids
                self.voice_artist_ids = voice_artist_ids
                self.viewer_ids = viewer_ids
                self.community_owned = community_owned
                self.cloned_from = cloned_from
                self.status = status
                self.viewable_if_private = viewable_if_private
                self.first_published_msec = first_published_msec
                self.owner_ids = [user_id_b]

        email_user_b = self.swap(rights_domain, 'ActivityRights',
                                 MockActivityRights)
        with email_user_b, self.can_send_feedback_email_ctx:
            with self.can_send_emails_ctx:
                change = {
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                    'state_name': 'state_1',
                    'new_value': 'new suggestion content'
                }

                # Create suggestion from user A to user B.
                suggestion_services.create_suggestion(
                    feconf.SUGGESTION_TYPE_EDIT_STATE_CONTENT,
                    feconf.ENTITY_TYPE_EXPLORATION, self.exploration.id, 1,
                    self.user_id_a, change, 'test description')
                threadlist = feedback_services.get_all_threads(
                    feconf.ENTITY_TYPE_EXPLORATION, self.exploration.id, True)
                thread_id = threadlist[0].id

                # Enqueue and send suggestion email task.
                payload = {
                    'exploration_id': self.exploration.id,
                    'thread_id': thread_id
                }
                messages = self._get_all_sent_email_messages()
                self.assertEqual(len(messages), 0)
                taskqueue_services.enqueue_task(
                    feconf.TASK_URL_SUGGESTION_EMAILS, payload, 0)
                self.process_and_flush_pending_tasks()

                # Check that user B received message.
                messages = self._get_sent_email_messages(self.USER_B_EMAIL)
                self.assertEqual(len(messages), 1)

                # Check that user B received correct message.
                expected_message = (
                    'Hi userB,\nuserA has submitted a new suggestion'
                    ' for your Oppia exploration, "Title".\nYou can'
                    ' accept or reject this suggestion by visiting'
                    ' the feedback page for your exploration.\n\nTha'
                    'nks!\n- The Oppia Team\n\nYou can change your'
                    ' email preferences via the Preferences page.')
                self.assertEqual(messages[0].body, expected_message)
Exemplo n.º 17
0
    def test_for_html_in_suggestion_edit_content_with_math_rte(self):
        """Checks that correct number of hints are tabulated when
        there is single exploration.
        """
        html_content = (
            '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a'
            'mp;quot;+,-,-,+&amp;quot;"></oppia-noninteractive-math>')

        state_dict = {
            'classifier_model_id': None,
            'content': {
                'content_id': 'content',
                'html': html_content
            },
            'interaction': {
                'answer_groups': [],
                'confirmed_unclassified_answers': [],
                'customization_args': {},
                'default_outcome': {
                    'dest': 'Introduction',
                    'feedback': {
                        'content_id': 'default_outcome',
                        'html': html_content
                    },
                    'labelled_as_correct': False,
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [],
                'id': None,
                'solution': None,
            },
            'param_changes': [],
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            },
            'solicit_answer_details': False,
            'written_translations': {
                'translations_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            }
        }
        states = {
            'Introduction': state_dict
        }
        exploration = (
            exp_domain.Exploration(
                'exp1', feconf.DEFAULT_EXPLORATION_TITLE,
                feconf.DEFAULT_EXPLORATION_CATEGORY,
                feconf.DEFAULT_EXPLORATION_OBJECTIVE,
                constants.DEFAULT_LANGUAGE_CODE, [], '', '',
                feconf.CURRENT_STATE_SCHEMA_VERSION,
                feconf.DEFAULT_INIT_STATE_NAME, states, {}, [], 0, False,
                False))
        exp_services.save_new_exploration(self.author_id, exploration)
        change_dict = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'Introduction',
            'new_value': {
                'content_id': 'content',
                'html': 'suggestion content'
            },
            'old_value': {
                'content_id': 'content',
                'html': html_content
            }
        }
        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id',
            self.mock_generate_new_exploration_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, change_dict, 'test description')

        job_id = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.create_new())
        suggestion_jobs_one_off.SuggestionMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        actual_output = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.get_output(job_id))
        self.assertRegexpMatches(
            python_utils.UNICODE(actual_output),
            '1 suggestions have Math components in them')
Exemplo n.º 18
0
    def test_for_html_in_suggestion_with_no_math_rte(self):
        html_content = '<p>This has no Math components</p>'
        answer_group = {
            'outcome': {
                'dest': None,
                'feedback': {
                    'content_id': 'feedback_1',
                    'html': html_content
                },
                'labelled_as_correct': True,
                'param_changes': [],
                'refresher_exploration_id': None,
                'missing_prerequisite_skill_id': None
            },
            'rule_specs': [{
                'inputs': {
                    'x': 0
                },
                'rule_type': 'Equals'
            }],
            'training_data': [],
            'tagged_skill_misconception_id': None
        }

        question_state_dict_without_math = {
            'content': {
                'content_id': 'content_1',
                'html': 'Question 1'
            },
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {}
                }
            },
            'written_translations': {
                'translations_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {
                        'en': {
                            'html': html_content,
                            'needs_update': True
                        }
                    }
                }
            },
            'interaction': {
                'answer_groups': [answer_group],
                'confirmed_unclassified_answers': [],
                'customization_args': {
                    'choices': {
                        'value': [html_content]
                    },
                    'showChoicesInShuffledOrder': {
                        'value': True
                    }
                },
                'default_outcome': {
                    'dest': None,
                    'feedback': {
                        'content_id': 'feedback_2',
                        'html': html_content
                    },
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'labelled_as_correct': True,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [{
                    'hint_content': {
                        'content_id': 'hint_1',
                        'html': 'Hint 1'
                    }
                }],
                'solution': {
                    'answer_is_exclusive': False,
                    'correct_answer': 0,
                    'explanation': {
                        'content_id': 'solution',
                        'html': '<p>This is a solution.</p>'
                    }
                },
                'id': 'MultipleChoiceInput'
            },
            'param_changes': [],
            'solicit_answer_details': False,
            'classifier_model_id': None
        }
        suggestion_dict_without_math = {
            'suggestion_id': 'skill2.thread1',
            'suggestion_type': suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': suggestion_models.TARGET_TYPE_SKILL,
            'target_id': 'skill2',
            'target_version_at_submission': 1,
            'status': suggestion_models.STATUS_ACCEPTED,
            'author_name': 'author',
            'final_reviewer_id': self.reviewer_id,
            'change': {
                'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
                'question_dict': {
                    'question_state_data': question_state_dict_without_math,
                    'language_code': 'en',
                    'question_state_data_schema_version': (
                        feconf.CURRENT_STATE_SCHEMA_VERSION),
                    'linked_skill_ids': ['skill_2']
                },
                'skill_id': 'skill_2',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.skill1',
            'last_updated': utils.get_time_in_millisecs(self.fake_date)
        }
        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id', self.mock_generate_new_skill_thread_id):
            suggestion_services.create_suggestion(
                suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
                suggestion_models.TARGET_TYPE_SKILL,
                'skill1', feconf.CURRENT_STATE_SCHEMA_VERSION,
                self.author_id, suggestion_dict_without_math['change'],
                'test description')

        job_id = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.create_new())
        suggestion_jobs_one_off.SuggestionMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        actual_output = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.get_output(job_id))
        self.assertEqual(len(actual_output), 0)
Exemplo n.º 19
0
    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'])
Exemplo n.º 20
0
    def test_for_html_in_suggestions_with_math_rte(self):
        """Checks that correct number of hints are tabulated when
        there is single exploration.
        """
        html_content = (
            '<p>Value</p><oppia-noninteractive-math math_content-with-value='
            '"{&amp;quot;raw_latex&amp;quot;: &amp;quot;+,-,-,+&amp;quot;, &'
            'amp;quot;svg_filename&amp;quot;: &amp;quot;&amp;quot;}"></oppia'
            '-noninteractive-math>')

        state_dict = {
            'classifier_model_id': None,
            'content': {
                'content_id': 'content',
                'html': html_content
            },
            'interaction': {
                'answer_groups': [],
                'confirmed_unclassified_answers': [],
                'customization_args': {},
                'default_outcome': {
                    'dest': 'Introduction',
                    'feedback': {
                        'content_id': 'default_outcome',
                        'html': html_content
                    },
                    'labelled_as_correct': False,
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [],
                'id': None,
                'solution': None,
            },
            'param_changes': [],
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            },
            'solicit_answer_details': False,
            'written_translations': {
                'translations_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            }
        }
        states = {
            'Introduction': state_dict
        }
        exploration = (
            exp_domain.Exploration(
                'exp1', feconf.DEFAULT_EXPLORATION_TITLE,
                feconf.DEFAULT_EXPLORATION_CATEGORY,
                feconf.DEFAULT_EXPLORATION_OBJECTIVE,
                constants.DEFAULT_LANGUAGE_CODE, [], '', '',
                feconf.CURRENT_STATE_SCHEMA_VERSION,
                feconf.DEFAULT_INIT_STATE_NAME, states, {}, [], 0, False,
                False))
        exp_services.save_new_exploration(self.author_id, exploration)
        add_translation_change_dict = {
            'cmd': 'add_translation',
            'state_name': 'Introduction',
            'content_id': 'content',
            'language_code': 'hi',
            'content_html': html_content,
            'translation_html': html_content
        }
        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id',
            self.mock_generate_new_exploration_thread_id):
            suggestion_services.create_suggestion(
                suggestion_models.SUGGESTION_TYPE_TRANSLATE_CONTENT,
                suggestion_models.TARGET_TYPE_EXPLORATION,
                self.target_id, self.target_version_at_submission,
                self.author_id, add_translation_change_dict, 'test description')
        answer_group = {
            'outcome': {
                'dest': None,
                'feedback': {
                    'content_id': 'feedback_1',
                    'html': html_content
                },
                'labelled_as_correct': True,
                'param_changes': [],
                'refresher_exploration_id': None,
                'missing_prerequisite_skill_id': None
            },
            'rule_specs': [{
                'inputs': {
                    'x': 0
                },
                'rule_type': 'Equals'
            }],
            'training_data': [],
            'tagged_skill_misconception_id': None
        }
        question_state_dict_with_math = {
            'content': {
                'content_id': 'content_1',
                'html': 'Question 1'
            },
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {}
                }
            },
            'written_translations': {
                'translations_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {
                        'en': {
                            'html': html_content,
                            'needs_update': True
                        }
                    }
                }
            },
            'interaction': {
                'answer_groups': [answer_group],
                'confirmed_unclassified_answers': [],
                'customization_args': {
                    'choices': {
                        'value': [html_content]
                    },
                    'showChoicesInShuffledOrder': {
                        'value': True
                    }
                },
                'default_outcome': {
                    'dest': None,
                    'feedback': {
                        'content_id': 'feedback_2',
                        'html': html_content
                    },
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'labelled_as_correct': True,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [{
                    'hint_content': {
                        'content_id': 'hint_1',
                        'html': 'Hint 1'
                    }
                }],
                'solution': {
                    'answer_is_exclusive': False,
                    'correct_answer': 0,
                    'explanation': {
                        'content_id': 'solution',
                        'html': '<p>This is a solution.</p>'
                    }
                },
                'id': 'MultipleChoiceInput'
            },
            'param_changes': [],
            'solicit_answer_details': False,
            'classifier_model_id': None
        }
        suggestion_dict_with_math = {
            'suggestion_id': 'skill2.thread1',
            'suggestion_type': suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': suggestion_models.TARGET_TYPE_SKILL,
            'target_id': 'skill2',
            'target_version_at_submission': 1,
            'status': suggestion_models.STATUS_ACCEPTED,
            'author_name': 'author',
            'final_reviewer_id': self.reviewer_id,
            'change': {
                'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
                'question_dict': {
                    'question_state_data': question_state_dict_with_math,
                    'language_code': 'en',
                    'question_state_data_schema_version': (
                        feconf.CURRENT_STATE_SCHEMA_VERSION),
                    'linked_skill_ids': ['skill_2']
                },
                'skill_id': 'skill_2',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.skill1',
            'last_updated': utils.get_time_in_millisecs(self.fake_date)
        }
        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id', self.mock_generate_new_skill_thread_id):
            suggestion_services.create_suggestion(
                suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
                suggestion_models.TARGET_TYPE_SKILL,
                'skill1', feconf.CURRENT_STATE_SCHEMA_VERSION,
                self.author_id, suggestion_dict_with_math['change'],
                'test description')

        job_id = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.create_new())
        suggestion_jobs_one_off.SuggestionMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        actual_output = (
            suggestion_jobs_one_off.
            SuggestionMathRteAuditOneOffJob.get_output(job_id))
        self.assertRegexpMatches(
            python_utils.UNICODE(actual_output),
            '2 suggestions have Math components in them')
Exemplo n.º 21
0
    def test_skip_migration_for_suggestion_with_new_math_schema(self):
        """Tests that a suggestion already having the new math_schema is not
        migrated.
        """
        expected_html_content = (
            '<p>Value</p><oppia-noninteractive-math math_content-with-value='
            '"{&amp;quot;raw_latex&amp;quot;: &amp;quot;+,-,-,+&amp;quot;, &'
            'amp;quot;svg_filename&amp;quot;: &amp;quot;&amp;quot;}"></oppia'
            '-noninteractive-math>')

        state_dict = {
            'classifier_model_id': None,
            'content': {
                'content_id': 'content',
                'html': expected_html_content
            },
            'interaction': {
                'answer_groups': [],
                'confirmed_unclassified_answers': [],
                'customization_args': {},
                'default_outcome': {
                    'dest': 'Introduction',
                    'feedback': {
                        'content_id': 'default_outcome',
                        'html': expected_html_content
                    },
                    'labelled_as_correct': False,
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [],
                'id': None,
                'solution': None,
            },
            'param_changes': [],
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            },
            'solicit_answer_details': False,
            'written_translations': {
                'translations_mapping': {
                    'content': {},
                    'default_outcome': {}
                }
            }
        }
        states = {
            'Introduction': state_dict
        }
        exploration = (
            exp_domain.Exploration(
                'exp1', feconf.DEFAULT_EXPLORATION_TITLE, 'Algebra',
                feconf.DEFAULT_EXPLORATION_OBJECTIVE,
                constants.DEFAULT_LANGUAGE_CODE, [], '', '',
                feconf.CURRENT_STATE_SCHEMA_VERSION,
                feconf.DEFAULT_INIT_STATE_NAME, states, {}, [], 0, False,
                False))
        exp_services.save_new_exploration(self.author_id, exploration)
        change_dict = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'Introduction',
            'new_value': {
                'content_id': 'content',
                'html': 'new suggestion'
            },
            'old_value': {
                'content_id': 'content',
                'html': expected_html_content
            }
        }

        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id',
            self.mock_generate_new_exploration_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, change_dict, 'test description')

        job_id = (
            suggestion_jobs_one_off.
            SuggestionMathMigrationOneOffJob.create_new())
        suggestion_jobs_one_off.SuggestionMathMigrationOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        actual_output = (
            suggestion_jobs_one_off.
            SuggestionMathMigrationOneOffJob.get_output(job_id))
        self.assertEqual(len(actual_output), 0)
Exemplo n.º 22
0
    def test_migration_skips_suggestions_failing_validation(self):
        html_content = (
            '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a'
            'mp;quot;+,-,-,+&amp;quot;"></oppia-noninteractive-math>')
        answer_group = {
            'outcome': {
                'dest': None,
                'feedback': {
                    'content_id': 'feedback_1',
                    'html': html_content
                },
                'labelled_as_correct': True,
                'param_changes': [],
                'refresher_exploration_id': None,
                'missing_prerequisite_skill_id': None
            },
            'rule_specs': [{
                'inputs': {
                    'x': 0
                },
                'rule_type': 'Equals'
            }],
            'training_data': [],
            'tagged_skill_misconception_id': None
        }
        question_state_dict = {
            'content': {
                'content_id': 'content_1',
                'html': 'Question 1'
            },
            'recorded_voiceovers': {
                'voiceovers_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {}
                }
            },
            'written_translations': {
                'translations_mapping': {
                    'content_1': {},
                    'feedback_1': {},
                    'feedback_2': {},
                    'hint_1': {},
                    'solution': {}
                }
            },
            'interaction': {
                'answer_groups': [answer_group],
                'confirmed_unclassified_answers': [],
                'customization_args': {
                    'choices': {
                        'value': ['option 1']
                    },
                    'showChoicesInShuffledOrder': {
                        'value': True
                    }
                },
                'default_outcome': {
                    'dest': None,
                    'feedback': {
                        'content_id': 'feedback_2',
                        'html': 'Correct Answer'
                    },
                    'param_changes': [],
                    'refresher_exploration_id': None,
                    'labelled_as_correct': True,
                    'missing_prerequisite_skill_id': None
                },
                'hints': [{
                    'hint_content': {
                        'content_id': 'hint_1',
                        'html': 'Hint 1'
                    }
                }],
                'solution': {
                    'answer_is_exclusive': False,
                    'correct_answer': 0,
                    'explanation': {
                        'content_id': 'solution',
                        'html': '<p>This is a solution.</p>'
                    }
                },
                'id': 'MultipleChoiceInput'
            },
            'param_changes': [],
            'solicit_answer_details': False,
            'classifier_model_id': None
        }
        suggestion_dict = {
            'suggestion_id': 'skill1.thread1',
            'suggestion_type': suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': suggestion_models.TARGET_TYPE_SKILL,
            'target_id': 'skill1',
            'target_version_at_submission': 1,
            'status': suggestion_models.STATUS_ACCEPTED,
            'author_name': 'author',
            'final_reviewer_id': self.reviewer_id,
            'change': {
                'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
                'question_dict': {
                    'question_state_data': question_state_dict,
                    'language_code': 'en',
                    'question_state_data_schema_version': (
                        feconf.CURRENT_STATE_SCHEMA_VERSION),
                    'linked_skill_ids': ['skill_1']
                },
                'skill_id': 'skill_1',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.skill1',
            'last_updated': utils.get_time_in_millisecs(self.fake_date)
        }
        with self.swap(
            feedback_models.GeneralFeedbackThreadModel,
            'generate_new_thread_id', self.mock_generate_new_skill_thread_id):
            suggestion_services.create_suggestion(
                suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
                suggestion_models.TARGET_TYPE_SKILL,
                'skill1', feconf.CURRENT_STATE_SCHEMA_VERSION,
                self.author_id, suggestion_dict['change'], 'test description')

        def _mock_get_suggestion_by_id(unused_suggestion_id):
            """Mocks get_suggestion_by_id()."""
            return 'invalid_suggestion'
        get_suggestion_by_id_swap = (
            self.swap(
                suggestion_services, 'get_suggestion_by_id',
                _mock_get_suggestion_by_id))

        with get_suggestion_by_id_swap:
            job_id = (
                suggestion_jobs_one_off.SuggestionMathMigrationOneOffJob.
                create_new())
            (
                suggestion_jobs_one_off.
                SuggestionMathMigrationOneOffJob.enqueue(job_id))
            self.process_and_flush_pending_tasks()
        actual_output = (
            suggestion_jobs_one_off.SuggestionMathMigrationOneOffJob.
            get_output(job_id))
        expected_output = (
            u'[u\'validation_error\', [u"Suggestion skill1.thread1 failed v' +
            'alidation: \'unicode\' object has no attribute \'validate\'"]]')
        self.assertEqual(actual_output, [expected_output])