def test_try_upgrade_raises_exception_if_versions_are_invalid(self): with self.assertRaisesRegexp( utils.InvalidInputException, 'Current draft version is greater than the exploration version.'): draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 2, 1, self.EXP_ID) exp_services.update_exploration( self.USER_ID, self.EXP_ID, self.OTHER_CHANGE_LIST, 'Changed exploration title.') exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.version, 2) self.assertIsNone( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))
def test_convert_states_v31_dict_to_v32_dict(self): draft_change_list_v31 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': 'new value' }) ] # Migrate exploration to state schema version 32. self.create_and_migrate_new_exploration('31', '32') # Migrate the draft change list's state schema to the migrated # exploration's schema. In this case there are no changes to the # draft change list since version 32 adds a customization arg # for the "Add" button text in SetInput interaction for the # exploration, for which there should be no changes to drafts. migrated_draft_change_list_v32 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v31, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. draft_change_list_v31_dict_list = [ change.to_dict() for change in draft_change_list_v31 ] migrated_draft_change_list_v32_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v32 ] self.assertEqual( draft_change_list_v31_dict_list, migrated_draft_change_list_v32_dict_list)
def test_convert_states_v28_dict_to_v29_dict(self): draft_change_list_v28 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': 'new value' }) ] # Migrate exploration to state schema version 29. self.create_and_migrate_new_exploration('28', '29') # Migrate the draft change list's state schema to the migrated # exploration's schema. In this case there are no change to the # draft change list since version 29 adds the # solicit_answer_details boolean variable to the exploration # state, for which there should be no changes to drafts. migrated_draft_change_list_v29 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v28, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. draft_change_list_v28_dict_list = [ change.to_dict() for change in draft_change_list_v28 ] migrated_draft_change_list_v29_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v29 ] self.assertEqual( draft_change_list_v28_dict_list, migrated_draft_change_list_v29_dict_list)
def test_convert_states_v30_dict_to_v31_dict(self): draft_change_list_v30 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'recorded_voiceovers', 'new_value': { 'voiceovers_mapping': { 'content': { 'en': { 'file_size_name': 100, 'filename': 'atest.mp3', 'needs_update': False } } } } }) ] # Version 31 adds the duration_secs property. expected_draft_change_list_v31 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'recorded_voiceovers', 'new_value': { 'voiceovers_mapping': { 'content': { 'en': { 'file_size_name': 100, 'filename': 'atest.mp3', 'needs_update': False, 'duration_secs': 0.0 } } } } }) ] # Migrate exploration to state schema version 31. self.create_and_migrate_new_exploration('30', '31') # Migrate the draft change list's state schema to the migrated # exploration's schema. migrated_draft_change_list_v31 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v30, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. expected_draft_change_list_v31_dict_list = [ change.to_dict() for change in expected_draft_change_list_v31 ] migrated_draft_change_list_v31_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v31 ] self.assertEqual( expected_draft_change_list_v31_dict_list, migrated_draft_change_list_v31_dict_list)
def test_try_upgrade_failure_due_to_unimplemented_upgrade_methods(self): exp_services.update_exploration( self.USER_ID, self.EXP_ID, self.EXP_MIGRATION_CHANGE_LIST, 'Ran Exploration Migration job.') exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.version, 2) self.assertIsNone( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))
def test_try_upgrade_failure_due_to_unsupported_commit_type(self): exp_services.update_exploration( self.USER_ID, self.EXP_ID, self.OTHER_CHANGE_LIST, 'Changed exploration title.') exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.version, 2) self.assertIsNone( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))
def test_try_upgrade_success(self): exp_services.update_exploration( self.USER_ID, self.EXP_ID, self.EXP_MIGRATION_CHANGE_LIST, 'Ran Exploration Migration job.') exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID) draft_upgrade_services.DraftUpgradeUtil._convert_states_v0_dict_to_v1_dict = ( # pylint: disable=protected-access,line-too-long classmethod(lambda cls, changelist: changelist)) self.assertEqual(exploration.version, 2) self.assertEqual( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID), self.DRAFT_CHANGELIST)
def test_try_upgrade_from_v32_to_v33(self): exp_id = 'exp1' self.save_new_exp_with_states_schema_v0( exp_id, self.USER_ID, 'Old Title') exploration_model = exp_models.ExplorationModel.get( exp_id, strict=True, version=None) for i in python_utils.RANGE(31): exploration_model.commit( self.USER_ID, 'Changed title %s.' % i, []) exploration_model.commit( self.USER_ID, 'Migrate from v32 to v33', [{ 'cmd': exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION, 'from_version': 32, 'to_version': 33 }]) self.assertEqual( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 32, 33, exp_id), self.DRAFT_CHANGELIST)
def test_convert_states_v27_dict_to_v28_dict(self): draft_change_list_v27 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'content_ids_to_audio_translations', 'state_name': 'State B', 'new_value': 'new value', }) ] # Version 28 adds voiceovers_mapping. expected_draft_change_list_v28 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'recorded_voiceovers', 'state_name': 'State B', 'new_value': {'voiceovers_mapping': 'new value'} }) ] # Migrate exploration to state schema version 28. self.create_and_migrate_new_exploration('27', '28') # Migrate the draft change list's state schema to the migrated # exploration's schema. migrated_draft_change_list_v28 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v27, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. expected_draft_change_list_v28_dict_list = [ change.to_dict() for change in expected_draft_change_list_v28 ] migrated_draft_change_list_v28_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v28 ] self.assertEqual( expected_draft_change_list_v28_dict_list, migrated_draft_change_list_v28_dict_list)
def test_try_upgrade_with_no_version_difference(self): self.assertIsNone( draft_upgrade_services.try_upgrading_draft_to_exp_version( self.DRAFT_CHANGELIST, 1, 1, self.EXP_ID))
def test_convert_states_v29_dict_to_v30_dict(self): draft_change_list_v29 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'answer_groups', 'state_name': 'State 1', 'new_value': { 'rule_specs': [{ 'rule_type': 'Equals', 'inputs': {'x': [ '<p>This is value1 for ItemSelection</p>' ]} }, { 'rule_type': 'Equals', 'inputs': {'x': [ '<p>This is value2 for ItemSelection</p>' ]} }], 'outcome': { 'dest': 'Introduction', 'feedback': { 'content_id': 'feedback', 'html': '<p>Outcome for state1</p>' }, 'param_changes': [], 'labelled_as_correct': False, 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'training_data': [], 'tagged_misconception_id': None } }) ] # Version 30 replaces the tagged_misconception_id in version 29 # with tagged_skill_misconception_id. expected_draft_change_list_v30 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'answer_groups', 'state_name': 'State 1', 'new_value': { 'rule_specs': [{ 'rule_type': 'Equals', 'inputs': {'x': [ '<p>This is value1 for ItemSelection</p>' ]} }, { 'rule_type': 'Equals', 'inputs': {'x': [ '<p>This is value2 for ItemSelection</p>' ]} }], 'outcome': { 'dest': 'Introduction', 'feedback': { 'content_id': 'feedback', 'html': '<p>Outcome for state1</p>' }, 'param_changes': [], 'labelled_as_correct': False, 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'training_data': [], 'tagged_skill_misconception_id': None } }) ] # Migrate exploration to state schema version 30. self.create_and_migrate_new_exploration('29', '30') # Migrate the draft change list's state schema to the migrated # exploration's schema. migrated_draft_change_list_v30 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v29, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. expected_draft_change_list_v30_dict_list = [ change.to_dict() for change in expected_draft_change_list_v30 ] migrated_draft_change_list_v30_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v30 ] self.assertEqual( expected_draft_change_list_v30_dict_list, migrated_draft_change_list_v30_dict_list)
def test_convert_states_v32_dict_to_v33_dict(self): draft_change_list_v32 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state1', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': [ '<p>1</p>', '<p>2</p>', '<p>3</p>', '<p>4</p>' ] } } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state2', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': [ '<p>1</p>', '<p>2</p>', '<p>3</p>', '<p>4</p>' ] }, 'maxAllowableSelectionCount': { 'value': 1 }, 'minAllowableSelectionCount': { 'value': 1 } } }) ] # Version 33 adds a showChoicesInShuffledOrder bool, which doesn't # impact the second ExplorationChange because it will only impact # it if 'choices' is the only key for new_value. expected_draft_change_list_v33 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state1', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': [ '<p>1</p>', '<p>2</p>', '<p>3</p>', '<p>4</p>' ] }, 'showChoicesInShuffledOrder': { 'value': False } } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state2', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': [ '<p>1</p>', '<p>2</p>', '<p>3</p>', '<p>4</p>' ] }, 'maxAllowableSelectionCount': { 'value': 1 }, 'minAllowableSelectionCount': { 'value': 1 } } }) ] # Migrate exploration to state schema version 33. self.create_and_migrate_new_exploration('32', '33') # Migrate the draft change list's state schema to the migrated # exploration's schema. migrated_draft_change_list_v33 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_v32, 1, 2, self.EXP_ID) ) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. expected_draft_change_list_v33_dict_list = [ change.to_dict() for change in expected_draft_change_list_v33 ] migrated_draft_change_list_v33_dict_list = [ change.to_dict() for change in migrated_draft_change_list_v33 ] self.assertEqual( expected_draft_change_list_v33_dict_list, migrated_draft_change_list_v33_dict_list)
def test_convert_states_v33_dict_to_v34_dict(self): html_content = ( '<p>Value</p><oppia-noninteractive-math raw_latex-with-value="&a' 'mp;quot;+,-,-,+&quot;"></oppia-noninteractive-math>') expected_html_content = ( '<p>Value</p><oppia-noninteractive-math math_content-with-value=' '"{&quot;raw_latex&quot;: &quot;+,-,-,+&quot;, &' 'amp;quot;svg_filename&quot;: &quot;&quot;}"></oppia' '-noninteractive-math>') draft_change_list = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state2', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': ['<p>1</p>', '<p>2</p>', html_content, '<p>4</p>'] }, 'maxAllowableSelectionCount': { 'value': 1 }, 'minAllowableSelectionCount': { 'value': 1 } } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'answer_groups', 'state_name': 'State 1', 'new_value': [{ 'rule_specs': [{ 'rule_type': 'Equals', 'inputs': { 'x': [html_content] } }, { 'rule_type': 'ContainsAtLeastOneOf', 'inputs': { 'x': [html_content] } }, { 'rule_type': 'IsProperSubsetOf', 'inputs': { 'x': [html_content] } }, { 'rule_type': 'DoesNotContainAtLeastOneOf', 'inputs': { 'x': [html_content] } }, { 'rule_type': 'Equals', 'inputs': { 'x': 1 } }, { 'rule_type': 'HasElementXAtPositionY', 'inputs': { 'x': html_content, 'y': 2 } }, { 'rule_type': 'IsEqualToOrdering', 'inputs': { 'x': [[html_content]] } }, { 'rule_type': 'HasElementXBeforeElementY', 'inputs': { 'x': html_content, 'y': html_content } }, { 'rule_type': ('IsEqualToOrderingWithOneItemAtIncorrectPosition'), 'inputs': { 'x': [[html_content]] } }], 'outcome': { 'dest': 'Introduction', 'feedback': { 'content_id': 'feedback', 'html': html_content }, 'param_changes': [], 'labelled_as_correct': False, 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'training_data': [], 'tagged_skill_misconception_id': None }] }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': { 'content_id': 'content', 'html': html_content } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'written_translations', 'new_value': { 'translations_mapping': { 'content1': { 'en': { 'html': html_content, 'needs_update': True }, 'hi': { 'html': 'Hey!', 'needs_update': False } }, 'feedback_1': { 'hi': { 'html': html_content, 'needs_update': False }, 'en': { 'html': 'hello!', 'needs_update': False } } } } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'solution', 'new_value': { 'answer_is_exclusive': False, 'correct_answer': 'helloworld!', 'explanation': { 'content_id': 'solution', 'html': html_content }, } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'solution', 'new_value': { 'answer_is_exclusive': True, 'correct_answer': [[html_content], ['<p>2</p>'], ['<p>3</p>'], ['<p>4</p>']], 'explanation': { 'content_id': 'solution', 'html': '<p>This is solution for state1</p>' } } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'default_outcome', 'new_value': { 'param_changes': [], 'feedback': { 'content_id': 'default_outcome', 'html': html_content }, 'dest': 'Introduction', 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None, 'labelled_as_correct': False } }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'hints', 'new_value': [{ 'hint_content': { 'content_id': 'hint1', 'html': html_content } }] }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_RENAME_STATE, 'old_state_name': 'Intro', 'new_state_name': 'Introduction', }) ] self.save_new_valid_exploration(self.EXP_ID, self.USER_ID) exp_services.update_exploration(self.USER_ID, self.EXP_ID, self.EXP_MIGRATION_CHANGE_LIST, 'Ran Exploration Migration job.') exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID) migrated_draft_change_list = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list, 1, exploration.version, self.EXP_ID)) self.assertEqual( migrated_draft_change_list[0].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'state2', 'property_name': 'widget_customization_args', 'new_value': { 'choices': { 'value': [ '<p>1</p>', '<p>2</p>', expected_html_content, '<p>4</p>' ] }, 'maxAllowableSelectionCount': { 'value': 1 }, 'minAllowableSelectionCount': { 'value': 1 } } }).to_dict()) self.assertEqual( migrated_draft_change_list[1].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': 'answer_groups', 'state_name': 'State 1', 'new_value': [{ 'rule_specs': [{ 'rule_type': 'Equals', 'inputs': { 'x': [expected_html_content] } }, { 'rule_type': 'ContainsAtLeastOneOf', 'inputs': { 'x': [expected_html_content] } }, { 'rule_type': 'IsProperSubsetOf', 'inputs': { 'x': [expected_html_content] } }, { 'rule_type': 'DoesNotContainAtLeastOneOf', 'inputs': { 'x': [expected_html_content] } }, { 'rule_type': 'Equals', 'inputs': { 'x': 1 } }, { 'rule_type': 'HasElementXAtPositionY', 'inputs': { 'x': expected_html_content, 'y': 2 } }, { 'rule_type': 'IsEqualToOrdering', 'inputs': { 'x': [[expected_html_content]] } }, { 'rule_type': 'HasElementXBeforeElementY', 'inputs': { 'x': expected_html_content, 'y': expected_html_content } }, { 'rule_type': ('IsEqualToOrderingWithOneItemAtIncorrectPosition'), 'inputs': { 'x': [[expected_html_content]] } }], 'outcome': { 'dest': 'Introduction', 'feedback': { 'content_id': 'feedback', 'html': expected_html_content }, 'param_changes': [], 'labelled_as_correct': False, 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'training_data': [], 'tagged_skill_misconception_id': None }] }).to_dict()) self.assertEqual( migrated_draft_change_list[2].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': { 'content_id': 'content', 'html': expected_html_content } }).to_dict()) self.assertEqual( migrated_draft_change_list[3].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'written_translations', 'new_value': { 'translations_mapping': { 'content1': { 'en': { 'html': expected_html_content, 'needs_update': True }, 'hi': { 'html': 'Hey!', 'needs_update': False } }, 'feedback_1': { 'hi': { 'html': expected_html_content, 'needs_update': False }, 'en': { 'html': 'hello!', 'needs_update': False } } } } }).to_dict()) self.assertEqual( migrated_draft_change_list[4].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'solution', 'new_value': { 'answer_is_exclusive': False, 'correct_answer': 'helloworld!', 'explanation': { 'content_id': 'solution', 'html': expected_html_content }, } }).to_dict()) self.assertEqual( migrated_draft_change_list[5].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'solution', 'new_value': { 'answer_is_exclusive': True, 'correct_answer': [[expected_html_content], ['<p>2</p>'], ['<p>3</p>'], ['<p>4</p>']], 'explanation': { 'content_id': 'solution', 'html': '<p>This is solution for state1</p>' } } }).to_dict()) self.assertEqual( migrated_draft_change_list[6].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'default_outcome', 'new_value': { 'param_changes': [], 'feedback': { 'content_id': 'default_outcome', 'html': expected_html_content }, 'dest': 'Introduction', 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None, 'labelled_as_correct': False } }).to_dict()) self.assertEqual( migrated_draft_change_list[7].to_dict(), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'hints', 'new_value': [{ 'hint_content': { 'content_id': 'hint1', 'html': expected_html_content } }] }).to_dict())
def test_convert_states_v34_dict_to_v35_dict(self): draft_change_list_1_v34 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': 'new value' }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'widget_id', 'new_value': 'MathExpressionInput' }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'answer_groups', 'new_value': [{ 'rule_specs': [{ 'rule_type': 'IsMathematicallyEquivalentTo', 'inputs': { 'x': 'x+y/2' } }], 'outcome': { 'dest': 'Introduction', 'feedback': { 'content_id': 'feedback', 'html': '<p>Content</p>' }, 'param_changes': [], 'labelled_as_correct': False, 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'training_data': [], 'tagged_skill_misconception_id': None }] }) ] draft_change_list_2_v34 = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'state_name': 'Intro', 'property_name': 'content', 'new_value': 'new value' }) ] # Migrate exploration to state schema version 35. self.create_and_migrate_new_exploration('34', '35') migrated_draft_change_list_1_v35 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_1_v34, 1, 2, self.EXP_ID)) self.assertIsNone(migrated_draft_change_list_1_v35) migrated_draft_change_list_2_v35 = ( draft_upgrade_services.try_upgrading_draft_to_exp_version( draft_change_list_2_v34, 1, 2, self.EXP_ID)) # Change draft change lists into a list of dicts so that it is # easy to compare the whole draft change list. draft_change_list_2_v34_dict_list = [ change.to_dict() for change in draft_change_list_2_v34 ] migrated_draft_change_list_2_v35_dict_list = [ change.to_dict() for change in migrated_draft_change_list_2_v35 ] self.assertEqual(draft_change_list_2_v34_dict_list, migrated_draft_change_list_2_v35_dict_list)