# Copyright 2018 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for suggestion registry classes."""

from __future__ import absolute_import  # pylint: disable=import-only-modules
from __future__ import unicode_literals  # pylint: disable=import-only-modules

import datetime
import os

from core.domain import config_services
from core.domain import exp_domain
from core.domain import exp_fetchers
from core.domain import exp_services
from core.domain import fs_services
from core.domain import html_validation_service
from core.domain import question_domain
from core.domain import skill_services
from core.domain import state_domain
from core.domain import suggestion_registry
from core.domain import suggestion_services
from core.platform import models
from core.tests import test_utils
import feconf
import python_utils
import utils

(suggestion_models,) = models.Registry.import_models([models.NAMES.suggestion])


class MockInvalidSuggestion(suggestion_registry.BaseSuggestion):

    def __init__(self):  # pylint: disable=super-init-not-called
        pass


class BaseSuggestionUnitTests(test_utils.GenericTestBase):
    """Tests for the BaseSuggestion class."""

    def setUp(self):
        super(BaseSuggestionUnitTests, self).setUp()
        self.base_suggestion = MockInvalidSuggestion()

    def test_base_class_accept_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement accept.'):
            self.base_suggestion.accept()

    def test_base_class_get_change_list_for_accepting_suggestion_raises_error(
            self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement '
            'get_change_list_for_accepting_suggestion.'):
            self.base_suggestion.get_change_list_for_accepting_suggestion()

    def test_base_class_pre_accept_validate_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' pre_accept_validate.'):
            self.base_suggestion.pre_accept_validate()

    def test_base_class_populate_old_value_of_change_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' populate_old_value_of_change.'):
            self.base_suggestion.populate_old_value_of_change()

    def test_base_class_pre_update_validate_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' pre_update_validate.'):
            self.base_suggestion.pre_update_validate({})

    def test_base_class_get_all_html_content_strings(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' get_all_html_content_strings.'):
            self.base_suggestion.get_all_html_content_strings()

    def test_base_class_get_target_entity_html_strings(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' get_target_entity_html_strings.'):
            self.base_suggestion.get_target_entity_html_strings()

    def test_base_class_convert_html_in_suggestion_change(self):
        def conversion_fn():
            """Temporary function."""
            pass
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseSuggestion should implement'
            ' convert_html_in_suggestion_change.'):
            self.base_suggestion.convert_html_in_suggestion_change(
                conversion_fn)


class SuggestionEditStateContentUnitTests(test_utils.GenericTestBase):
    """Tests for the SuggestionEditStateContent class."""

    AUTHOR_EMAIL = 'author@example.com'
    REVIEWER_EMAIL = 'reviewer@example.com'
    ASSIGNED_REVIEWER_EMAIL = 'assigned_reviewer@example.com'
    fake_date = datetime.datetime(2016, 4, 10, 0, 0, 0, 0)

    def setUp(self):
        super(SuggestionEditStateContentUnitTests, self).setUp()

        self.signup(self.AUTHOR_EMAIL, 'author')
        self.author_id = self.get_user_id_from_email(self.AUTHOR_EMAIL)
        self.signup(self.REVIEWER_EMAIL, 'reviewer')
        self.reviewer_id = self.get_user_id_from_email(self.REVIEWER_EMAIL)
        self.suggestion_dict = {
            'suggestion_id': 'exploration.exp1.thread1',
            'suggestion_type': (
                feconf.SUGGESTION_TYPE_EDIT_STATE_CONTENT),
            'target_type': feconf.ENTITY_TYPE_EXPLORATION,
            'target_id': 'exp1',
            'target_version_at_submission': 1,
            'status': suggestion_models.STATUS_ACCEPTED,
            'author_name': 'author',
            'final_reviewer_id': self.reviewer_id,
            'change': {
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                'state_name': 'state_1',
                'new_value': 'new suggestion content',
                'old_value': None
            },
            'score_category': 'content.Algebra',
            'language_code': None,
            'last_updated': utils.get_time_in_millisecs(self.fake_date),
            'edited_by_reviewer': False
        }

    def test_create_suggestion_edit_state_content(self):
        expected_suggestion_dict = self.suggestion_dict

        observed_suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertDictEqual(
            observed_suggestion.to_dict(), expected_suggestion_dict)

    def test_validate_suggestion_edit_state_content(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

    def test_get_score_part_helper_methods(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertEqual(suggestion.get_score_type(), 'content')
        self.assertEqual(suggestion.get_score_sub_type(), 'Algebra')

    def test_validate_suggestion_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.suggestion_type = 'invalid_suggestion_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected suggestion_type to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_target_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_type = 'invalid_target_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected target_type to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_target_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_id = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected target_id to be a string'
        ):
            suggestion.validate()

    def test_validate_target_version_at_submission(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_version_at_submission = 'invalid_version'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected target_version_at_submission to be an int'
        ):
            suggestion.validate()

    def test_validate_status(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.status = 'invalid_status'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected status to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_author_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected author_id to be a string'
        ):
            suggestion.validate()

    def test_validate_author_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = self.PSEUDONYMOUS_ID
        suggestion.validate()

        suggestion.author_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected author_id to be in a valid user ID format'
        ):
            suggestion.validate()

    def test_validate_final_reviewer_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected final_reviewer_id to be a string'
        ):
            suggestion.validate()

    def test_validate_final_reviewer_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = self.PSEUDONYMOUS_ID
        suggestion.validate()

        suggestion.final_reviewer_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected final_reviewer_id to be in a valid user ID format'
        ):
            suggestion.validate()

    def test_validate_score_category(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected score_category to be a string'
        ):
            suggestion.validate()

    def test_validate_score_category_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'score.score_type.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected score_category to be of the form'
            ' score_type.score_sub_type'
        ):
            suggestion.validate()

        suggestion.score_category = 'invalid_score_category'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected score_category to be of the form'
            ' score_type.score_sub_type'
        ):
            suggestion.validate()

    def test_validate_score_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'invalid_score_type.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the first part of score_category to be among allowed'
            ' choices'
        ):
            suggestion.validate()

    def test_validate_change(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change = {}
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to be an ExplorationChange'
        ):
            suggestion.validate()

    def test_validate_score_type_content(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'question.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the first part of score_category to be content'
        ):
            suggestion.validate()

    def test_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.change.cmd = 'invalid_cmd'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected cmd to be edit_state_property'
        ):
            suggestion.validate()

    def test_validate_change_property_name(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.property_name = 'invalid_property'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected property_name to be content'
        ):
            suggestion.validate()

    def test_validate_language_code_fails_when_language_codes_do_not_match(
            self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.language_code = 'wrong_language_code'

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected language_code to be None, received wrong_language_code'
        ):
            suggestion.validate()

    def test_pre_accept_validate_state_name(self):
        self.save_new_default_exploration('exp1', self.author_id)
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        exp_services.update_exploration(
            self.author_id, 'exp1', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': 'State A',
                })
            ], 'Added state')
        suggestion.change.state_name = 'State A'

        suggestion.pre_accept_validate()

        suggestion.change.state_name = 'invalid_state_name'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected invalid_state_name to be a valid state name'
        ):
            suggestion.pre_accept_validate()

    def test_populate_old_value_of_change_with_invalid_state(self):
        self.save_new_default_exploration('exp1', self.author_id)
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.change.state_name = 'invalid_state_name'

        self.assertIsNone(suggestion.change.old_value)

        suggestion.populate_old_value_of_change()

        self.assertIsNone(suggestion.change.old_value)

    def test_pre_update_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        change = {
            'cmd': exp_domain.CMD_ADD_STATE,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': suggestion.change.state_name,
            'new_value': 'new suggestion content',
            'old_value': None
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The following extra attributes are present: new_value, '
            'old_value, property_name'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_property_name(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_PARAM_CHANGES,
            'state_name': suggestion.change.state_name,
            'new_value': 'new suggestion content',
            'old_value': None
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change property_name must be equal to content'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_state_name(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'invalid_state',
            'new_value': 'new suggestion content',
            'old_value': None
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change state_name must be equal to state_1'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_new_value(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        new_content = state_domain.SubtitledHtml(
            'content', '<p>new suggestion html</p>').to_dict()

        suggestion.change.new_value = new_content

        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': suggestion.change.state_name,
            'new_value': new_content,
            'old_value': None
        }
        with self.assertRaisesRegexp(
            utils.ValidationError, 'The new html must not match the old html'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_non_equal_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change cmd must be equal to edit_state_property'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                'property_name': 'title',
                'new_value': 'Exploration 1 Albert title'
            }))

    def test_get_all_html_content_strings(self):
        change_dict = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'state_1',
            'new_value': {
                'content_id': 'content',
                'html': 'new suggestion content'
            },
            'old_value': None
        }
        suggestion = suggestion_registry.SuggestionEditStateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, change_dict,
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        actual_outcome_list = suggestion.get_all_html_content_strings()
        expected_outcome_list = [u'new suggestion content']
        self.assertEqual(expected_outcome_list, actual_outcome_list)

    def test_convert_html_in_suggestion_change(self):
        html_content = (
            '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a'
            'mp;quot;+,-,-,+&amp;quot;"></oppia-noninteractive-math>')
        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>')

        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'Introduction',
            'new_value': {
                'content_id': 'content',
                'html': '<p>suggestion</p>'
            },
            'old_value': {
                'content_id': 'content',
                'html': html_content
            }
        }
        suggestion = suggestion_registry.SuggestionEditStateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, change,
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        suggestion.convert_html_in_suggestion_change(
            html_validation_service.
            add_math_content_to_math_rte_components)
        self.assertEqual(
            suggestion.change.old_value['html'], expected_html_content)

    def test_get_target_entity_html_strings_returns_expected_strings(self):
        change_dict = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'state_1',
            'new_value': {
                'content_id': 'content',
                'html': 'new suggestion content'
            },
            'old_value': {
                'content_id': 'content',
                'html': 'Old content.'
            }
        }
        suggestion = suggestion_registry.SuggestionEditStateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, change_dict,
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        actual_outcome_list = suggestion.get_target_entity_html_strings()
        expected_outcome_list = [u'Old content.']
        self.assertEqual(expected_outcome_list, actual_outcome_list)

    def test_get_target_entity_html_with_none_old_value(self):
        change_dict = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': 'state_1',
            'new_value': {
                'content_id': 'content',
                'html': 'new suggestion content'
            },
            'old_value': None
        }
        suggestion = suggestion_registry.SuggestionEditStateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, change_dict,
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        actual_outcome_list = suggestion.get_target_entity_html_strings()
        self.assertEqual(actual_outcome_list, [])


class SuggestionTranslateContentUnitTests(test_utils.GenericTestBase):
    """Tests for the SuggestionEditStateContent class."""

    AUTHOR_EMAIL = 'author@example.com'
    REVIEWER_EMAIL = 'reviewer@example.com'
    ASSIGNED_REVIEWER_EMAIL = 'assigned_reviewer@example.com'
    fake_date = datetime.datetime(2016, 4, 10, 0, 0, 0, 0)

    def setUp(self):
        super(SuggestionTranslateContentUnitTests, self).setUp()

        self.signup(self.AUTHOR_EMAIL, 'author')
        self.author_id = self.get_user_id_from_email(self.AUTHOR_EMAIL)
        self.signup(self.REVIEWER_EMAIL, 'reviewer')
        self.reviewer_id = self.get_user_id_from_email(self.REVIEWER_EMAIL)
        self.suggestion_dict = {
            'suggestion_id': 'exploration.exp1.thread1',
            'suggestion_type': (
                feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT),
            'target_type': feconf.ENTITY_TYPE_EXPLORATION,
            'target_id': 'exp1',
            'target_version_at_submission': 1,
            'status': suggestion_models.STATUS_ACCEPTED,
            'author_name': 'author',
            'final_reviewer_id': self.reviewer_id,
            'change': {
                'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
                'state_name': 'Introduction',
                'content_id': 'content',
                'language_code': 'hi',
                'content_html': '<p>This is a content.</p>',
                'translation_html': '<p>This is translated html.</p>',
                'data_format': 'html'
            },
            'score_category': 'translation.Algebra',
            'language_code': 'hi',
            'last_updated': utils.get_time_in_millisecs(self.fake_date),
            'edited_by_reviewer': False
        }

    def test_pre_update_validate_fails_for_invalid_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], self.fake_date)

        change = {
            'cmd': exp_domain.CMD_ADD_STATE,
            'state_name': 'Introduction'
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change cmd must be equal to %s' % (
                exp_domain.CMD_ADD_WRITTEN_TRANSLATION)
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_state_name(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], self.fake_date)
        change = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'State 1',
            'content_id': 'content',
            'language_code': 'hi',
            'content_html': '<p>This is a content.</p>',
            'translation_html': '<p>This is the updated translated html.</p>',
            'data_format': 'html'
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change state_name must be equal to Introduction'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_language_code(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], self.fake_date)
        change = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'Introduction',
            'content_id': 'content',
            'language_code': 'en',
            'content_html': '<p>This is a content.</p>',
            'translation_html': '<p>This is the updated translated html.</p>',
            'data_format': 'html'
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The language code must be equal to hi'
        ):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))

    def test_pre_update_validate_change_content_html(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], self.fake_date)
        change = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'Introduction',
            'content_id': 'content',
            'language_code': 'en',
            'content_html': '<p>This is the changed content.</p>',
            'translation_html': '<p>This is the updated translated html.</p>',
            'data_format': 'html'
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change content_html must be equal to <p>This is a ' +
            'content.</p>'
        ):
            suggestion.pre_update_validate(
                exp_domain.ExplorationChange(change))

    def test_create_suggestion_add_translation(self):
        expected_suggestion_dict = self.suggestion_dict

        observed_suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertDictEqual(
            observed_suggestion.to_dict(), expected_suggestion_dict)

    def test_validate_suggestion_add_translation(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

    def test_get_score_part_helper_methods(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertEqual(suggestion.get_score_type(), 'translation')
        self.assertEqual(suggestion.get_score_sub_type(), 'Algebra')

    def test_validate_suggestion_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.suggestion_type = 'invalid_suggestion_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected suggestion_type to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_target_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_type = 'invalid_target_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected target_type to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_target_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_id = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected target_id to be a string'
        ):
            suggestion.validate()

    def test_validate_target_version_at_submission(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.target_version_at_submission = 'invalid_version'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected target_version_at_submission to be an int'
        ):
            suggestion.validate()

    def test_validate_status(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.status = 'invalid_status'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected status to be among allowed choices'
        ):
            suggestion.validate()

    def test_validate_author_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected author_id to be a string'
        ):
            suggestion.validate()

    def test_validate_author_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected author_id to be in a valid user ID format.'
        ):
            suggestion.validate()

    def test_validate_final_reviewer_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected final_reviewer_id to be a string'
        ):
            suggestion.validate()

    def test_validate_final_reviewer_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected final_reviewer_id to be in a valid user ID format'
        ):
            suggestion.validate()

    def test_validate_score_category(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected score_category to be a string'
        ):
            suggestion.validate()

    def test_validate_score_category_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'score.score_type.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected score_category to be of the form'
            ' score_type.score_sub_type'
        ):
            suggestion.validate()

        suggestion.score_category = 'invalid_score_category'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected score_category to be of the form'
            ' score_type.score_sub_type'
        ):
            suggestion.validate()

    def test_validate_score_type(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'invalid_score_type.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the first part of score_category to be among allowed'
            ' choices'
        ):
            suggestion.validate()

    def test_validate_change(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change = {}
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to be an ExplorationChange'
        ):
            suggestion.validate()

    def test_validate_score_type_translation(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'question.score_sub_type'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the first part of score_category to be translation'
        ):
            suggestion.validate()

    def test_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.cmd = 'invalid_cmd'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected cmd to be add_written_translation'
        ):
            suggestion.validate()

    def test_validate_language_code_fails_when_language_codes_do_not_match(
            self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        expected_language_code = (
            expected_suggestion_dict['change']['language_code']
        )
        suggestion.validate()

        suggestion.language_code = 'wrong_language_code'

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected language_code to be %s, '
            'received wrong_language_code' % expected_language_code
        ):
            suggestion.validate()

    def test_validate_language_code_fails_when_language_code_is_set_to_none(
            self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.language_code = None

        with self.assertRaisesRegexp(
            utils.ValidationError, 'language_code cannot be None'
        ):
            suggestion.validate()

    def test_validate_change_with_invalid_language_code_fails_validation(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.language_code = 'invalid_code'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Invalid language_code: invalid_code'
        ):
            suggestion.validate()

    def test_pre_accept_validate_state_name(self):
        self.save_new_default_exploration('exp1', self.author_id)
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        exp_services.update_exploration(
            self.author_id, 'exp1', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': 'State A',
                }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                    'new_value': {
                        'content_id': 'content',
                        'html': '<p>This is a content.</p>'
                    },
                    'state_name': 'State A',
                })
            ], 'Added state')
        suggestion.change.state_name = 'State A'

        suggestion.pre_accept_validate()

        suggestion.change.state_name = 'invalid_state_name'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected invalid_state_name to be a valid state name'
        ):
            suggestion.pre_accept_validate()

    def test_pre_accept_validate_content_html(self):
        self.save_new_default_exploration('exp1', self.author_id)
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        exp_services.update_exploration(
            self.author_id, 'exp1', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': 'State A',
                }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                    'new_value': {
                        'content_id': 'content',
                        'html': '<p>This is a content.</p>'
                    },
                    'state_name': 'State A',
                })
            ], 'Added state')
        suggestion.change.state_name = 'State A'

        suggestion.pre_accept_validate()

        suggestion.change.content_html = 'invalid content_html'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The Exploration content has changed since this translation '
            'was submitted.'
        ):
            suggestion.pre_accept_validate()

    def test_accept_suggestion_adds_translation_in_exploration(self):
        self.save_new_default_exploration('exp1', self.author_id)

        exploration = exp_fetchers.get_exploration_by_id('exp1')
        self.assertEqual(exploration.get_translation_counts(), {})

        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.accept(
            'Accepted suggestion by translator: Add translation change.')

        exploration = exp_fetchers.get_exploration_by_id('exp1')

        self.assertEqual(exploration.get_translation_counts(), {
            'hi': 1
        })

    def test_accept_suggestion_with_psedonymous_author_adds_translation(self):
        self.save_new_default_exploration('exp1', self.author_id)

        exploration = exp_fetchers.get_exploration_by_id('exp1')
        self.assertEqual(exploration.get_translation_counts(), {})

        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionTranslateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.PSEUDONYMOUS_ID,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.accept(
            'Accepted suggestion by translator: Add translation change.')

        exploration = exp_fetchers.get_exploration_by_id('exp1')

        self.assertEqual(exploration.get_translation_counts(), {
            'hi': 1
        })

    def test_get_all_html_content_strings(self):
        suggestion = suggestion_registry.SuggestionTranslateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, self.suggestion_dict['change'],
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        actual_outcome_list = suggestion.get_all_html_content_strings()
        expected_outcome_list = [
            u'<p>This is translated html.</p>', u'<p>This is a content.</p>']
        self.assertEqual(expected_outcome_list, actual_outcome_list)

    def test_get_target_entity_html_strings_returns_expected_strings(self):
        suggestion = suggestion_registry.SuggestionTranslateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, self.suggestion_dict['change'],
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)

        actual_outcome_list = suggestion.get_target_entity_html_strings()
        expected_outcome_list = [self.suggestion_dict['change']['content_html']]
        self.assertEqual(expected_outcome_list, actual_outcome_list)

    def test_convert_html_in_suggestion_change(self):
        html_content = (
            '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a'
            'mp;quot;+,-,-,+&amp;quot;"></oppia-noninteractive-math>')
        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>')
        change_dict = {
            'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
            'state_name': 'Introduction',
            'content_id': 'content',
            'language_code': 'hi',
            'content_html': html_content,
            'translation_html': '<p>This is translated html.</p>',
            'data_format': 'html'
        }
        suggestion = suggestion_registry.SuggestionTranslateContent(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, change_dict,
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], False, self.fake_date)
        suggestion.convert_html_in_suggestion_change(
            html_validation_service.add_math_content_to_math_rte_components)
        self.assertEqual(
            suggestion.change.content_html, expected_html_content)


class SuggestionAddQuestionTest(test_utils.GenericTestBase):
    """Tests for the SuggestionAddQuestion class."""

    AUTHOR_EMAIL = 'author@example.com'
    REVIEWER_EMAIL = 'reviewer@example.com'
    ASSIGNED_REVIEWER_EMAIL = 'assigned_reviewer@example.com'
    fake_date = datetime.datetime(2016, 4, 10, 0, 0, 0, 0)

    def setUp(self):
        super(SuggestionAddQuestionTest, self).setUp()

        self.signup(self.AUTHOR_EMAIL, 'author')
        self.author_id = self.get_user_id_from_email(self.AUTHOR_EMAIL)
        self.signup(self.REVIEWER_EMAIL, 'reviewer')
        self.reviewer_id = self.get_user_id_from_email(self.REVIEWER_EMAIL)
        self.suggestion_dict = {
            'suggestion_id': 'skill1.thread1',
            'suggestion_type': feconf.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': feconf.ENTITY_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': 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': ['skill_1'],
                    'inapplicable_skill_misconception_ids': ['skillid12345-1']
                },
                'skill_id': 'skill_1',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.topic_1',
            'language_code': 'en',
            'last_updated': utils.get_time_in_millisecs(self.fake_date),
            'edited_by_reviewer': False
        }

    def test_create_suggestion_add_question(self):
        expected_suggestion_dict = self.suggestion_dict

        observed_suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertDictEqual(
            observed_suggestion.to_dict(), expected_suggestion_dict)

    def test_validate_suggestion_edit_state_content(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

    def test_get_score_part_helper_methods(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertEqual(suggestion.get_score_type(), 'question')
        self.assertEqual(suggestion.get_score_sub_type(), 'topic_1')

    def test_validate_score_type(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.score_category = 'content.score_sub_type'

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the first part of score_category to be "question"'
        ):
            suggestion.validate()

    def test_validate_change_type(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change = 'invalid_change'

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected change to be an instance of QuestionSuggestionChange'
        ):
            suggestion.validate()

    def test_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.cmd = None

        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to contain cmd'
        ):
            suggestion.validate()

    def test_validate_change_cmd_type(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.cmd = 'invalid_cmd'

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected cmd to be create_new_fully_specified_question'
        ):
            suggestion.validate()

    def test_validate_change_question_dict(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.change.question_dict = None

        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to contain question_dict'
        ):
            suggestion.validate()

    def test_validate_change_question_state_data_schema_version(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        # We are not setting value in suggestion.change.question_dict
        # directly since pylint produces unsupported-assignment-operation
        # error. The detailed analysis for the same can be checked
        # in this issue: https://github.com/oppia/oppia/issues/7008.
        question_dict = suggestion.change.question_dict
        question_dict['question_state_data_schema_version'] = 0
        suggestion.change.question_dict = question_dict

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected question state schema version to be %s, '
            'received 0' % feconf.CURRENT_STATE_SCHEMA_VERSION
        ):
            suggestion.validate()

    def test_validate_change_skill_difficulty_none(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.change.skill_difficulty = None

        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to contain skill_difficulty'
        ):
            suggestion.validate()

    def test_validate_change_skill_difficulty_invalid_value(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.change.skill_difficulty = 0.4

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected change skill_difficulty to be one of '
        ):
            suggestion.validate()

    def test_pre_accept_validate_change_skill_id(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        skill_id = skill_services.get_new_skill_id()
        self.save_new_skill(skill_id, self.author_id, description='description')
        suggestion.change.skill_id = skill_id

        suggestion.pre_accept_validate()

        suggestion.change.skill_id = None

        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected change to contain skill_id'
        ):
            suggestion.pre_accept_validate()

    def test_pre_accept_validate_change_invalid_skill_id(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        skill_id = skill_services.get_new_skill_id()
        self.save_new_skill(skill_id, self.author_id, description='description')
        suggestion.change.skill_id = skill_id

        suggestion.pre_accept_validate()

        suggestion.change.skill_id = skill_services.get_new_skill_id()

        with self.assertRaisesRegexp(
            utils.ValidationError, 'The skill with the given id doesn\'t exist.'
        ):
            suggestion.pre_accept_validate()

    def test_get_change_list_for_accepting_suggestion(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertIsNone(suggestion.get_change_list_for_accepting_suggestion())

    def test_populate_old_value_of_change(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        self.assertIsNone(suggestion.populate_old_value_of_change())

    def test_cannot_accept_suggestion_with_invalid_skill_id(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.change.skill_id = skill_services.get_new_skill_id()

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The skill with the given id doesn\'t exist.'
        ):
            suggestion.accept('commit message')

    def test_pre_update_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        change = {
            'cmd': question_domain.CMD_UPDATE_QUESTION_PROPERTY,
            'property_name': question_domain.QUESTION_PROPERTY_LANGUAGE_CODE,
            'new_value': 'bn',
            'old_value': 'en'
        }
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change cmd must be equal to '
            'create_new_fully_specified_question'
        ):
            suggestion.pre_update_validate(
                question_domain.QuestionChange(change))

    def test_pre_update_validate_change_skill_id(self):
        expected_suggestion_dict = self.suggestion_dict

        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        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)
            },
            'skill_id': 'skill_2'
        }

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'The new change skill_id must be equal to skill_1'
        ):
            suggestion.pre_update_validate(
                question_domain.QuestionChange(change))

    def test_pre_update_validate_complains_if_nothing_changed(self):
        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)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.3
        }

        suggestion = suggestion_registry.SuggestionAddQuestion(
            'exploration.exp1.thread1', 'exp1', 1,
            suggestion_models.STATUS_ACCEPTED, self.author_id,
            self.reviewer_id, change,
            'question.topic_1', 'en', self.fake_date)

        new_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)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.3
        }

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'At least one of the new skill_difficulty or question_dict '
            'should be changed.'):
            suggestion.pre_update_validate(
                question_domain.QuestionSuggestionChange(new_change))

    def test_pre_update_validate_accepts_a_change_in_skill_difficulty_only(
            self):
        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)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.3
        }

        suggestion = suggestion_registry.SuggestionAddQuestion(
            'exploration.exp1.thread1', 'exp1', 1,
            suggestion_models.STATUS_ACCEPTED, self.author_id,
            self.reviewer_id, change,
            'question.topic_1', 'en', self.fake_date)

        new_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)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.6
        }

        self.assertEqual(
            suggestion.pre_update_validate(
                question_domain.QuestionSuggestionChange(new_change)), None)

    def test_pre_update_validate_accepts_a_change_in_state_data_only(self):
        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)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.3
        }

        suggestion = suggestion_registry.SuggestionAddQuestion(
            'exploration.exp1.thread1', 'exp1', 1,
            suggestion_models.STATUS_ACCEPTED, self.author_id,
            self.reviewer_id, change,
            'question.topic_1', 'en', self.fake_date)

        new_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': 'hi',
                'question_state_data_schema_version': (
                    feconf.CURRENT_STATE_SCHEMA_VERSION)
            },
            'skill_id': 'skill_1',
            'skill_difficulty': 0.3
        }

        self.assertEqual(
            suggestion.pre_update_validate(
                question_domain.QuestionSuggestionChange(new_change)), None)

    def test_validate_author_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = 0
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected author_id to be a string'):
            suggestion.validate()

    def test_validate_author_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.author_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected author_id to be in a valid user ID format.'):
            suggestion.validate()

    def test_validate_final_reviewer_id(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected final_reviewer_id to be a string'):
            suggestion.validate()

    def test_validate_final_reviewer_id_format(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)

        suggestion.validate()

        suggestion.final_reviewer_id = ''
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected final_reviewer_id to be in a valid user ID format'):
            suggestion.validate()

    def test_validate_language_code_fails_when_language_codes_do_not_match(
            self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        expected_question_dict = (
            expected_suggestion_dict['change']['question_dict']
        )
        suggestion.validate()

        expected_question_dict['language_code'] = 'wrong_language_code'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected question language_code.wrong_language_code. to be same '
            'as suggestion language_code.en.'
        ):
            suggestion.validate()

    def test_validate_language_code_fails_when_language_code_is_set_to_none(
            self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionAddQuestion(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'],
            expected_suggestion_dict['language_code'], False, self.fake_date)
        suggestion.validate()

        suggestion.language_code = None

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected language_code to be en, received None'):
            suggestion.validate()

    def test_get_all_html_conztent_strings(self):
        suggestion = suggestion_registry.SuggestionAddQuestion(
            self.suggestion_dict['suggestion_id'],
            self.suggestion_dict['target_id'],
            self.suggestion_dict['target_version_at_submission'],
            self.suggestion_dict['status'], self.author_id,
            self.reviewer_id, self.suggestion_dict['change'],
            self.suggestion_dict['score_category'],
            self.suggestion_dict['language_code'], self.fake_date)

        actual_outcome_list = suggestion.get_all_html_content_strings()
        expected_outcome_list = [
            u'', u'<p>This is a hint.</p>', u'<p>This is a solution.</p>', u'']
        self.assertEqual(expected_outcome_list, actual_outcome_list)

    def test_convert_html_in_suggestion_change(self):
        html_content = (
            '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a'
            'mp;quot;+,-,-,+&amp;quot;"></oppia-noninteractive-math>')
        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>')
        answer_group = {
            'outcome': {
                'dest': None,
                'feedback': {
                    'content_id': 'feedback_1',
                    'html': ''
                },
                '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': html_content
            },
            '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': [{
                            'html': 'option 1',
                            'content_id': 'ca_choices_0'
                        }]
                    },
                    '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': feconf.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': feconf.ENTITY_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'],
                    'inapplicable_skill_misconception_ids': ['skillid12345-1']
                },
                'skill_id': 'skill_1',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.skill1',
            'language_code': 'en',
            'last_updated': utils.get_time_in_millisecs(self.fake_date)
        }
        suggestion = suggestion_registry.SuggestionAddQuestion(
            suggestion_dict['suggestion_id'], suggestion_dict['target_id'],
            suggestion_dict['target_version_at_submission'],
            suggestion_dict['status'], self.author_id, self.reviewer_id,
            suggestion_dict['change'], suggestion_dict['score_category'],
            suggestion_dict['language_code'], False, self.fake_date)
        suggestion.convert_html_in_suggestion_change(
            html_validation_service.add_math_content_to_math_rte_components)
        self.assertEqual(
            suggestion.change.question_dict['question_state_data']['content'][
                'html'], expected_html_content)

    def test_accept_suggestion_with_images(self):
        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;img.svg&amp;quot;}">'
            '</oppia-noninteractive-math>')
        question_state_dict = self._create_valid_question_data(
            'default_state').to_dict()
        question_state_dict['content']['html'] = html_content
        with python_utils.open_file(
            os.path.join(feconf.TESTS_DATA_DIR, 'test_svg.svg'),
            'rb', encoding=None) as f:
            raw_image = f.read()
        image_context = feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS
        fs_services.save_original_and_compressed_versions_of_image(
            'img.svg', image_context, 'skill1',
            raw_image, 'image', False)
        self.save_new_skill('skill1', self.author_id, description='description')

        suggestion_dict = {
            'suggestion_id': 'skill1.thread1',
            'suggestion_type': feconf.SUGGESTION_TYPE_ADD_QUESTION,
            'target_type': feconf.ENTITY_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'],
                    'inapplicable_skill_misconception_ids': []
                },
                'skill_id': 'skill1',
                'skill_difficulty': 0.3,
            },
            'score_category': 'question.skill1',
            'language_code': 'en',
            'last_updated': utils.get_time_in_millisecs(self.fake_date)
        }
        suggestion = suggestion_registry.SuggestionAddQuestion(
            suggestion_dict['suggestion_id'], suggestion_dict['target_id'],
            suggestion_dict['target_version_at_submission'],
            suggestion_dict['status'], self.author_id, self.reviewer_id,
            suggestion_dict['change'], suggestion_dict['score_category'],
            suggestion_dict['language_code'], False, self.fake_date)
        suggestion.accept('commit_message')

    def test_contructor_updates_state_shema_in_change_cmd(self):
        score_category = (
            suggestion_models.SCORE_TYPE_QUESTION +
            suggestion_models.SCORE_CATEGORY_DELIMITER + 'skill_id')
        change = {
            'cmd': (
                question_domain
                .CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
            'question_dict': {
                'question_state_data': self.VERSION_27_STATE_DICT,
                'question_state_data_schema_version': 27,
                'language_code': 'en',
                'linked_skill_ids': ['skill_id'],
                'inapplicable_skill_misconception_ids': []
            },
            'skill_id': 'skill_id',
            'skill_difficulty': 0.3
        }
        self.assertEqual(
            change['question_dict']['question_state_data_schema_version'], 27)

        suggestion = suggestion_registry.SuggestionAddQuestion(
            'suggestionId', 'target_id', 1, suggestion_models.STATUS_IN_REVIEW,
            self.author_id, None, change, score_category, 'en', False,
            self.fake_date)
        self.assertEqual(
            suggestion.change.question_dict[
                'question_state_data_schema_version'],
            feconf.CURRENT_STATE_SCHEMA_VERSION)

    def test_contructor_raise_exception_for_invalid_state_shema_version(self):
        score_category = (
            suggestion_models.SCORE_TYPE_QUESTION +
            suggestion_models.SCORE_CATEGORY_DELIMITER + 'skill_id')
        change = {
            'cmd': (
                question_domain
                .CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
            'question_dict': {
                'question_state_data': self.VERSION_27_STATE_DICT,
                'question_state_data_schema_version': None,
                'language_code': 'en',
                'linked_skill_ids': ['skill_id'],
                'inapplicable_skill_misconception_ids': []
            },
            'skill_id': 'skill_id',
            'skill_difficulty': 0.3
        }
        self.assertEqual(
            change['question_dict']['question_state_data_schema_version'], None)

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected state schema version to be in between 25'
        ):
            suggestion_registry.SuggestionAddQuestion(
                'suggestionId', 'target_id', 1,
                suggestion_models.STATUS_IN_REVIEW, self.author_id, None,
                change, score_category, 'en', False, self.fake_date)


class MockInvalidVoiceoverApplication(
        suggestion_registry.BaseVoiceoverApplication):

    def __init__(self):  # pylint: disable=super-init-not-called
        pass


class BaseVoiceoverApplicationUnitTests(test_utils.GenericTestBase):
    """Tests for the BaseVoiceoverApplication class."""

    def setUp(self):
        super(BaseVoiceoverApplicationUnitTests, self).setUp()
        self.base_voiceover_application = MockInvalidVoiceoverApplication()

    def test_base_class_init_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseVoiceoverApplication should implement '
            '__init__.'):
            suggestion_registry.BaseVoiceoverApplication()

    def test_base_class_accept_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseVoiceoverApplication should implement accept.'):
            self.base_voiceover_application.accept()

    def test_base_class_reject_raises_error(self):
        with self.assertRaisesRegexp(
            NotImplementedError,
            'Subclasses of BaseVoiceoverApplication should implement reject.'):
            self.base_voiceover_application.reject()


class ExplorationVoiceoverApplicationUnitTest(test_utils.GenericTestBase):
    """Tests for the ExplorationVoiceoverApplication class."""

    def setUp(self):
        super(ExplorationVoiceoverApplicationUnitTest, self).setUp()
        self.signup('author@example.com', 'author')
        self.author_id = self.get_user_id_from_email('author@example.com')

        self.signup('reviewer@example.com', 'reviewer')
        self.reviewer_id = self.get_user_id_from_email('reviewer@example.com')

        self.voiceover_application = (
            suggestion_registry.ExplorationVoiceoverApplication(
                'application_id', 'exp_id', suggestion_models.STATUS_IN_REVIEW,
                self.author_id, None, 'en', 'audio_file.mp3', '<p>Content</p>',
                None))

    def test_validation_with_invalid_target_type_raise_exception(self):
        self.voiceover_application.validate()

        self.voiceover_application.target_type = 'invalid_target'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected target_type to be among allowed choices, '
            'received invalid_target'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_target_id_raise_exception(self):
        self.voiceover_application.validate()

        self.voiceover_application.target_id = 123
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected target_id to be a string'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_status_raise_exception(self):
        self.voiceover_application.validate()

        self.voiceover_application.status = 'invalid_status'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected status to be among allowed choices, '
            'received invalid_status'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_author_id_raise_exception(self):
        self.voiceover_application.validate()

        self.voiceover_application.author_id = 123
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected author_id to be a string'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_final_reviewer_id_raise_exception(self):
        self.assertEqual(
            self.voiceover_application.status,
            suggestion_models.STATUS_IN_REVIEW)
        self.assertEqual(self.voiceover_application.final_reviewer_id, None)
        self.voiceover_application.validate()

        self.voiceover_application.final_reviewer_id = 123
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected final_reviewer_id to be None as the '
            'voiceover application is not yet handled.'
        ):
            self.voiceover_application.validate()

    def test_validation_for_handled_application_with_invalid_final_review(self):
        self.assertEqual(
            self.voiceover_application.status,
            suggestion_models.STATUS_IN_REVIEW)
        self.assertEqual(self.voiceover_application.final_reviewer_id, None)
        self.voiceover_application.validate()

        self.voiceover_application.status = suggestion_models.STATUS_ACCEPTED
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected final_reviewer_id to be a string'
        ):
            self.voiceover_application.validate()

    def test_validation_for_rejected_application_with_no_message(self):
        self.assertEqual(
            self.voiceover_application.status,
            suggestion_models.STATUS_IN_REVIEW)
        self.assertEqual(self.voiceover_application.rejection_message, None)
        self.voiceover_application.validate()

        self.voiceover_application.final_reviewer_id = 'reviewer_id'
        self.voiceover_application.status = suggestion_models.STATUS_REJECTED
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected rejection_message to be a string for a '
            'rejected application'
        ):
            self.voiceover_application.validate()

    def test_validation_for_accepted_application_with_message(self):
        self.assertEqual(
            self.voiceover_application.status,
            suggestion_models.STATUS_IN_REVIEW)
        self.assertEqual(self.voiceover_application.rejection_message, None)
        self.voiceover_application.validate()

        self.voiceover_application.final_reviewer_id = 'reviewer_id'
        self.voiceover_application.status = suggestion_models.STATUS_ACCEPTED
        self.voiceover_application.rejection_message = 'Invalid message'
        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected rejection_message to be None for the accepted '
            'voiceover application, received Invalid message'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_language_code_type_raise_exception(self):
        self.assertEqual(self.voiceover_application.language_code, 'en')
        self.voiceover_application.validate()

        self.voiceover_application.language_code = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected language_code to be a string'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_language_code_raise_exception(self):
        self.assertEqual(self.voiceover_application.language_code, 'en')
        self.voiceover_application.validate()

        self.voiceover_application.language_code = 'invalid language'
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Invalid language_code: invalid language'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_filename_type_raise_exception(self):
        self.assertEqual(self.voiceover_application.filename, 'audio_file.mp3')
        self.voiceover_application.validate()

        self.voiceover_application.filename = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected filename to be a string'
        ):
            self.voiceover_application.validate()

    def test_validation_with_invalid_content_type_raise_exception(self):
        self.assertEqual(self.voiceover_application.content, '<p>Content</p>')
        self.voiceover_application.validate()

        self.voiceover_application.content = 1
        with self.assertRaisesRegexp(
            utils.ValidationError, 'Expected content to be a string'
        ):
            self.voiceover_application.validate()

    def test_to_dict_returns_correct_dict(self):
        self.voiceover_application.accept(self.reviewer_id)
        expected_dict = {
            'voiceover_application_id': 'application_id',
            'target_type': 'exploration',
            'target_id': 'exp_id',
            'status': 'accepted',
            'author_name': 'author',
            'final_reviewer_name': 'reviewer',
            'language_code': 'en',
            'content': '<p>Content</p>',
            'filename': 'audio_file.mp3',
            'rejection_message': None
        }
        self.assertEqual(
            self.voiceover_application.to_dict(), expected_dict)

    def test_is_handled_property_returns_correct_value(self):
        self.assertFalse(self.voiceover_application.is_handled)

        self.voiceover_application.accept(self.reviewer_id)

        self.assertTrue(self.voiceover_application.is_handled)

    def test_accept_voiceover_application(self):
        self.assertEqual(self.voiceover_application.final_reviewer_id, None)
        self.assertEqual(self.voiceover_application.status, 'review')

        self.voiceover_application.accept(self.reviewer_id)

        self.assertEqual(
            self.voiceover_application.final_reviewer_id, self.reviewer_id)
        self.assertEqual(self.voiceover_application.status, 'accepted')

    def test_reject_voiceover_application(self):
        self.assertEqual(self.voiceover_application.final_reviewer_id, None)
        self.assertEqual(self.voiceover_application.status, 'review')

        self.voiceover_application.reject(self.reviewer_id, 'rejection message')

        self.assertEqual(
            self.voiceover_application.final_reviewer_id, self.reviewer_id)
        self.assertEqual(self.voiceover_application.status, 'rejected')
        self.assertEqual(
            self.voiceover_application.rejection_message, 'rejection message')


class CommunityContributionStatsUnitTests(test_utils.GenericTestBase):
    """Tests for the CommunityContributionStats class."""

    translation_reviewer_counts_by_lang_code = {
        'hi': 0,
        'en': 1
    }

    translation_suggestion_counts_by_lang_code = {
        'fr': 6,
        'en': 5
    }

    question_reviewer_count = 1
    question_suggestion_count = 4

    negative_count = -1
    non_integer_count = 'non_integer_count'
    sample_language_code = 'en'
    invalid_language_code = 'invalid'

    def _assert_community_contribution_stats_is_in_default_state(self):
        """Checks if the community contribution stats is in its default
        state.
        """
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )

        self.assertEqual(
            (
                community_contribution_stats
                .translation_reviewer_counts_by_lang_code
            ), {})
        self.assertEqual(
            (
                community_contribution_stats
                .translation_suggestion_counts_by_lang_code
            ), {})
        self.assertEqual(
            community_contribution_stats.question_reviewer_count, 0)
        self.assertEqual(
            community_contribution_stats.question_suggestion_count, 0)

    def test_initial_object_with_valid_arguments_has_correct_properties(self):
        community_contribution_stats = (
            suggestion_registry.CommunityContributionStats(
                self.translation_reviewer_counts_by_lang_code,
                self.translation_suggestion_counts_by_lang_code,
                self.question_reviewer_count,
                self.question_suggestion_count
            )
        )
        community_contribution_stats.validate()

        self.assertEqual(
            (
                community_contribution_stats
                .translation_reviewer_counts_by_lang_code
            ),
            self.translation_reviewer_counts_by_lang_code)
        self.assertEqual(
            (
                community_contribution_stats
                .translation_suggestion_counts_by_lang_code
            ),
            self.translation_suggestion_counts_by_lang_code
        )
        self.assertEqual(
            community_contribution_stats.question_reviewer_count,
            self.question_reviewer_count
        )
        self.assertEqual(
            community_contribution_stats.question_suggestion_count,
            self.question_suggestion_count
        )

    def test_set_translation_reviewer_count_for_lang_code_updates_empty_dict(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()

        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code(
                self.sample_language_code, 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_reviewer_counts_by_lang_code
            ),
            {self.sample_language_code: 2}
        )

    def test_set_translation_reviewer_count_for_lang_code_updates_count_value(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()
        (
            community_contribution_stats
            .translation_reviewer_counts_by_lang_code
        ) = {self.sample_language_code: 1}

        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code(
                self.sample_language_code, 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_reviewer_counts_by_lang_code
            ),
            {self.sample_language_code: 2}
        )

    def test_set_translation_reviewer_count_for_lang_code_adds_new_lang_key(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()
        (
            community_contribution_stats
            .translation_reviewer_counts_by_lang_code
        ) = {'en': 1}

        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code('hi', 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_reviewer_counts_by_lang_code
            ),
            {'en': 1, 'hi': 2}
        )

    def test_set_translation_suggestion_count_for_lang_code_updates_empty_dict(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()

        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code(
                self.sample_language_code, 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_suggestion_counts_by_lang_code
            ), {self.sample_language_code: 2}
        )

    def test_set_translation_suggestion_count_for_lang_code_updates_count_value(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()
        (
            community_contribution_stats
            .translation_suggestion_counts_by_lang_code
        ) = {self.sample_language_code: 1}

        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code(
                self.sample_language_code, 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_suggestion_counts_by_lang_code
            ),
            {self.sample_language_code: 2}
        )

    def test_set_translation_suggestion_count_for_lang_code_adds_new_lang_key(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        self._assert_community_contribution_stats_is_in_default_state()
        (
            community_contribution_stats
            .translation_suggestion_counts_by_lang_code
        ) = {'en': 1}

        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code('hi', 2)
        )

        self.assertDictEqual(
            (
                community_contribution_stats
                .translation_suggestion_counts_by_lang_code
            ),
            {'en': 1, 'hi': 2}
        )

    def test_get_translation_language_codes_that_need_reviewers_for_one_lang(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code(
            self.sample_language_code, 1)

        language_codes_that_need_reviewers = (
            stats.get_translation_language_codes_that_need_reviewers()
        )

        self.assertEqual(
            language_codes_that_need_reviewers, {self.sample_language_code})

    def test_get_translation_language_codes_that_need_reviewers_for_multi_lang(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code('hi', 1)
        stats.set_translation_suggestion_count_for_language_code('fr', 1)

        language_codes_that_need_reviewers = (
            stats.get_translation_language_codes_that_need_reviewers()
        )

        self.assertEqual(
            language_codes_that_need_reviewers, {'hi', 'fr'})

    def test_get_translation_language_codes_that_need_reviewers_for_no_lang(
            self):
        stats = suggestion_services.get_community_contribution_stats()

        language_codes_that_need_reviewers = (
            stats.get_translation_language_codes_that_need_reviewers()
        )

        self.assertEqual(
            language_codes_that_need_reviewers, set())

    def test_translation_reviewers_are_needed_if_suggestions_but_no_reviewers(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code(
            self.sample_language_code, 1)

        self.assertTrue(
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

    def test_translation_reviewers_are_needed_if_num_suggestions_past_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code(
            self.sample_language_code, 2)
        stats.set_translation_reviewer_count_for_language_code(
            self.sample_language_code, 1)
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = (
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

        self.assertTrue(reviewers_are_needed)

    def test_translation_reviewers_not_needed_if_num_suggestions_eqs_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code(
            self.sample_language_code, 2)
        stats.set_translation_reviewer_count_for_language_code(
            self.sample_language_code, 2)
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = (
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

        self.assertFalse(reviewers_are_needed)

    def test_translation_reviewers_not_needed_if_num_suggestions_less_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_suggestion_count_for_language_code(
            self.sample_language_code, 1)
        stats.set_translation_reviewer_count_for_language_code(
            self.sample_language_code, 2)
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = (
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

        self.assertFalse(reviewers_are_needed)

    def test_translation_reviewers_not_needed_if_reviewers_and_no_sugestions(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.set_translation_reviewer_count_for_language_code(
            self.sample_language_code, 1)

        self.assertFalse(
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

    def test_translation_reviewers_not_needed_if_no_reviewers_no_sugestions(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        self._assert_community_contribution_stats_is_in_default_state()

        self.assertFalse(
            stats.are_translation_reviewers_needed_for_lang_code(
                self.sample_language_code))

    def test_question_reviewers_are_needed_if_suggestions_zero_reviewers(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.question_suggestion_count = 1

        self.assertTrue(stats.are_question_reviewers_needed())

    def test_question_reviewers_are_needed_if_num_suggestions_past_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.question_suggestion_count = 2
        stats.question_reviewer_count = 1
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = stats.are_question_reviewers_needed()

        self.assertTrue(reviewers_are_needed)

    def test_question_reviewers_not_needed_if_num_suggestions_eqs_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.question_suggestion_count = 2
        stats.question_reviewer_count = 2
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = stats.are_question_reviewers_needed()

        self.assertFalse(reviewers_are_needed)

    def test_question_reviewers_not_needed_if_num_suggestions_less_max(self):
        stats = suggestion_services.get_community_contribution_stats()
        stats.question_suggestion_count = 1
        stats.question_reviewer_count = 2
        config_services.set_property(
            'committer_id', 'max_number_of_suggestions_per_reviewer', 1)

        reviewers_are_needed = stats.are_question_reviewers_needed()

        self.assertFalse(reviewers_are_needed)

    def test_question_reviewers_not_needed_if_no_reviewers_no_sugestions(
            self):
        stats = suggestion_services.get_community_contribution_stats()
        self._assert_community_contribution_stats_is_in_default_state()

        self.assertFalse(stats.are_question_reviewers_needed())

    def test_validate_translation_reviewer_counts_fails_for_negative_counts(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code(
                self.sample_language_code, self.negative_count)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the translation reviewer count to be non-negative for '
            '%s language code, received: %s.' % (
                self.sample_language_code, self.negative_count)
        ):
            community_contribution_stats.validate()

    def test_validate_translation_suggestion_counts_fails_for_negative_counts(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code(
                self.sample_language_code, self.negative_count)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the translation suggestion count to be non-negative for '
            '%s language code, received: %s.' % (
                self.sample_language_code, self.negative_count)
        ):
            community_contribution_stats.validate()

    def test_validate_question_reviewer_count_fails_for_negative_count(self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        community_contribution_stats.question_reviewer_count = (
            self.negative_count
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the question reviewer count to be non-negative, '
            'received: %s.' % (
                community_contribution_stats.question_reviewer_count)
        ):
            community_contribution_stats.validate()

    def test_validate_question_suggestion_count_fails_for_negative_count(self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        community_contribution_stats.question_suggestion_count = (
            self.negative_count
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the question suggestion count to be non-negative, '
            'received: %s.' % (
                community_contribution_stats.question_suggestion_count)
        ):
            community_contribution_stats.validate()

    def test_validate_translation_reviewer_counts_fails_for_non_integer_counts(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code(
                self.sample_language_code, self.non_integer_count)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the translation reviewer count to be an integer for '
            '%s language code, received: %s.' % (
                self.sample_language_code, self.non_integer_count)
        ):
            community_contribution_stats.validate()

    def test_validate_translation_suggestion_counts_fails_for_non_integer_count(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code(
                self.sample_language_code, self.non_integer_count)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the translation suggestion count to be an integer for '
            '%s language code, received: %s.' % (
                self.sample_language_code, self.non_integer_count)
        ):
            community_contribution_stats.validate()

    def test_validate_question_reviewer_count_fails_for_non_integer_count(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        community_contribution_stats.question_reviewer_count = (
            self.non_integer_count
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the question reviewer count to be an integer, '
            'received: %s.' % (
                community_contribution_stats.question_reviewer_count)
        ):
            community_contribution_stats.validate()

    def test_validate_question_suggestion_count_fails_for_non_integer_count(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        community_contribution_stats.question_suggestion_count = (
            self.non_integer_count
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Expected the question suggestion count to be an integer, '
            'received: %s.' % (
                community_contribution_stats.question_suggestion_count)
        ):
            community_contribution_stats.validate()

    def test_validate_translation_reviewer_counts_fails_for_invalid_lang_code(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_reviewer_count_for_language_code(
                self.invalid_language_code, 1)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Invalid language code for the translation reviewer counts: '
            '%s.' % self.invalid_language_code
        ):
            community_contribution_stats.validate()

    def test_validate_translation_suggestion_counts_fails_for_invalid_lang_code(
            self):
        community_contribution_stats = (
            suggestion_services.get_community_contribution_stats()
        )
        (
            community_contribution_stats
            .set_translation_suggestion_count_for_language_code(
                self.invalid_language_code, 1)
        )

        with self.assertRaisesRegexp(
            utils.ValidationError,
            'Invalid language code for the translation suggestion counts: '
            '%s.' % self.invalid_language_code
        ):
            community_contribution_stats.validate()


class ReviewableSuggestionEmailInfoUnitTests(test_utils.GenericTestBase):
    """Tests for the ReviewableSuggestionEmailInfo class."""

    suggestion_type = feconf.SUGGESTION_TYPE_ADD_QUESTION
    language_code = 'en'
    suggestion_content = 'sample question'
    submission_datetime = datetime.datetime.utcnow()

    def test_initial_object_with_valid_arguments_has_correct_properties(self):
        reviewable_suggestion_email_info = (
            suggestion_registry.ReviewableSuggestionEmailInfo(
                self.suggestion_type, self.language_code,
                self.suggestion_content, self.submission_datetime
            )
        )

        self.assertEqual(
            reviewable_suggestion_email_info.suggestion_type,
            self.suggestion_type)
        self.assertEqual(
            reviewable_suggestion_email_info.language_code,
            self.language_code)
        self.assertEqual(
            reviewable_suggestion_email_info.suggestion_content,
            self.suggestion_content)
        self.assertEqual(
            reviewable_suggestion_email_info.submission_datetime,
            self.submission_datetime)
