def test_contributors_with_only_reverts_not_counted(self): """Test that contributors who have only done reverts do not have their user id appear in the contributor list. """ # Sign up two users. self.signup(self.EMAIL_A, self.USERNAME_A) user_a_id = self.get_user_id_from_email(self.EMAIL_A) self.signup(self.EMAIL_B, self.USERNAME_B) user_b_id = self.get_user_id_from_email(self.EMAIL_B) # Have one user make two commits. exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Original Title') exploration_model = exp_models.ExplorationModel.get( self.EXP_ID, strict=True, version=None) exploration_model.title = 'New title' exploration_model.commit( user_a_id, 'Changed title.', []) # Have the second user revert version 2 to version 1 exp_services.revert_exploration(user_b_id, self.EXP_ID, 2, 1) # Run the job to compute the contributor ids. job_id = ( exp_jobs_one_off.ExpSummariesContributorsOneOffJob.create_new()) exp_jobs_one_off.ExpSummariesContributorsOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify that the committer list does not contain the user # who only reverted. exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) self.assertEqual([user_a_id], exploration_summary.contributor_ids)
def setUp(self): """Populate the database of explorations and their summaries. The sequence of events is: - (1) Albert creates EXP_ID_1. - (2) Bob edits the title of EXP_ID_1. - (3) Albert creates EXP_ID_2. - (4) Albert edits the title of EXP_ID_1. - (5) Albert edits the title of EXP_ID_2. - (6) Bob reverts Albert's last edit to EXP_ID_1. - Bob tries to publish EXP_ID_2, and is denied access. - (7) Albert publishes EXP_ID_2. - (8) Albert creates EXP_ID_3 - (9) Albert publishes EXP_ID_3 - (10) Albert deletes EXP_ID_3 """ super(ExplorationDisplayableSummaries, self).setUp() self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL) self.bob_id = self.get_user_id_from_email(self.BOB_EMAIL) self.signup(self.ALBERT_EMAIL, self.ALBERT_NAME) self.signup(self.BOB_EMAIL, self.BOB_NAME) self.save_new_valid_exploration(self.EXP_ID_1, self.albert_id) exp_services.update_exploration( self.bob_id, self.EXP_ID_1, [{ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 1 title' }], 'Changed title.') self.save_new_valid_exploration(self.EXP_ID_2, self.albert_id) exp_services.update_exploration( self.albert_id, self.EXP_ID_1, [{ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 1 Albert title' }], 'Changed title to Albert1 title.') exp_services.update_exploration( self.albert_id, self.EXP_ID_2, [{ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 2 Albert title' }], 'Changed title to Albert2 title.') exp_services.revert_exploration(self.bob_id, self.EXP_ID_1, 3, 2) with self.assertRaisesRegexp( Exception, 'This exploration cannot be published' ): rights_manager.publish_exploration(self.bob_id, self.EXP_ID_2) rights_manager.publish_exploration(self.albert_id, self.EXP_ID_2) self.save_new_valid_exploration(self.EXP_ID_3, self.albert_id) rights_manager.publish_exploration(self.albert_id, self.EXP_ID_3) exp_services.delete_exploration(self.albert_id, self.EXP_ID_3)
def test_reverts_not_counted(self): # Let USER A make 3 non-revert commits. exploration = self.save_new_valid_exploration( self.EXP_ID, self.user_a_id, title='Exploration Title') exp_services.update_exploration(self.user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }) ], 'Changed title.') exp_services.update_exploration(self.user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }) ], 'Changed Objective.') # Let USER A revert version 3 to version 2. exp_services.revert_exploration(self.user_a_id, self.EXP_ID, 3, 2) output = self._run_one_off_job() self.assertEqual([['SUCCESS', 1]], output) # Check that USER A's number of contributions is equal to 2. exploration_summary = exp_fetchers.get_exploration_summary_by_id( exploration.id) self.assertEqual([self.user_a_id], exploration_summary.contributor_ids) self.assertEqual({self.user_a_id: 2}, exploration_summary.contributors_summary)
def test_contributors_with_only_reverts_not_counted(self): """Test that contributors who have only done reverts do not have their user id appear in the contributor list. """ # Sign up two users. self.signup(self.EMAIL_A, self.USERNAME_A) user_a_id = self.get_user_id_from_email(self.EMAIL_A) self.signup(self.EMAIL_B, self.USERNAME_B) user_b_id = self.get_user_id_from_email(self.EMAIL_B) # Have one user make two commits. exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Original Title') change_list = [{ 'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY, 'property_name': 'title', 'new_value': 'New title' }] exp_services.update_exploration( user_a_id, self.EXP_ID, change_list, 'Changed title.') # Have the second user revert version 2 to version 1 exp_services.revert_exploration(user_b_id, self.EXP_ID, 2, 1) # Run the job to compute the contributor ids. job_id = ( exp_jobs_one_off.ExpSummariesContributorsOneOffJob.create_new()) exp_jobs_one_off.ExpSummariesContributorsOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify that the committer list does not contain the user # who only reverted. exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) self.assertEqual([user_a_id], exploration_summary.contributor_ids)
def test_contributors_with_only_reverts_not_included(self): # Let USER A make three commits. exploration = self.save_new_valid_exploration( self.EXP_ID, self.user_a_id, title='Exploration Title 1') exp_services.update_exploration(self.user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }) ], 'Changed title.') exp_services.update_exploration(self.user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }) ], 'Changed Objective.') # Let the second user revert version 3 to version 2. exp_services.revert_exploration(self.user_b_id, self.EXP_ID, 3, 2) output = self._run_one_off_job() self.assertEqual([['SUCCESS', 1]], output) exploration_summary = exp_fetchers.get_exploration_summary_by_id( exploration.id) self.assertEqual([self.user_a_id], exploration_summary.contributor_ids) self.assertEqual({self.user_a_id: 2}, exploration_summary.contributors_summary)
def test_that_mapreduce_job_works(self): """Test that mapreduce job is working as expected.""" with self.swap(feconf, 'ENABLE_STATE_ID_MAPPING', True): exploration = self.save_new_valid_exploration( self.EXP_ID, self.owner_id) exp_services.update_exploration(self.owner_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_ADD_STATE, 'state_name': 'new state', }) ], 'Add state name') exp_services.update_exploration(self.owner_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_ADD_STATE, 'state_name': 'new state 2', }), exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_DELETE_STATE, 'state_name': 'new state' }) ], 'Modify states') exp_services.revert_exploration(self.owner_id, self.EXP_ID, 3, 1) job_id = exp_jobs_one_off.ExplorationStateIdMappingJob.create_new() exp_jobs_one_off.ExplorationStateIdMappingJob.enqueue(job_id) self.process_and_flush_pending_tasks() expected_mapping = {exploration.init_state_name: 0} mapping = exp_services.get_state_id_mapping(self.EXP_ID, 1) self.assertEqual(mapping.exploration_id, self.EXP_ID) self.assertEqual(mapping.exploration_version, 1) self.assertEqual(mapping.largest_state_id_used, 0) self.assertDictEqual(mapping.state_names_to_ids, expected_mapping) expected_mapping = {exploration.init_state_name: 0, 'new state': 1} mapping = exp_services.get_state_id_mapping(self.EXP_ID, 2) self.assertEqual(mapping.exploration_id, self.EXP_ID) self.assertEqual(mapping.exploration_version, 2) self.assertEqual(mapping.largest_state_id_used, 1) self.assertDictEqual(mapping.state_names_to_ids, expected_mapping) expected_mapping = {exploration.init_state_name: 0, 'new state 2': 2} mapping = exp_services.get_state_id_mapping(self.EXP_ID, 3) self.assertEqual(mapping.exploration_id, self.EXP_ID) self.assertEqual(mapping.exploration_version, 3) self.assertEqual(mapping.largest_state_id_used, 2) self.assertDictEqual(mapping.state_names_to_ids, expected_mapping) expected_mapping = {exploration.init_state_name: 0} mapping = exp_services.get_state_id_mapping(self.EXP_ID, 4) self.assertEqual(mapping.exploration_id, self.EXP_ID) self.assertEqual(mapping.exploration_version, 4) self.assertEqual(mapping.largest_state_id_used, 2) self.assertDictEqual(mapping.state_names_to_ids, expected_mapping)
def post(self, exploration_id): """Handles POST requests.""" current_version = self.normalized_payload.get('current_version') revert_to_version = self.normalized_payload.get('revert_to_version') if revert_to_version >= current_version: raise self.InvalidInputException( 'Cannot revert to version %s from version %s.' % (revert_to_version, current_version)) exp_services.discard_draft(exploration_id, self.user_id) exp_services.revert_exploration(self.user_id, exploration_id, current_version, revert_to_version) self.render_json({})
def test_versioning_with_reverting(self): exploration = self.save_new_valid_exploration( self.EXP_ID, self.OWNER_ID) # In version 1, the title was 'A title'. # In version 2, the title becomes 'V2 title'. exploration.title = 'V2 title' exp_services._save_exploration( self.OWNER_ID, exploration, 'Changed title.', []) # In version 3, a new state is added. exploration = exp_services.get_exploration_by_id(self.EXP_ID) exploration.add_states(['New state']) exp_services._save_exploration( 'committer_id_v3', exploration, 'Added new state', []) # It is not possible to revert from anything other than the most # current version. with self.assertRaisesRegexp(Exception, 'too old'): exp_services.revert_exploration( 'committer_id_v4', self.EXP_ID, 2, 1) # Version 4 is a reversion to version 1. exp_services.revert_exploration('committer_id_v4', self.EXP_ID, 3, 1) exploration = exp_services.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.title, 'A title') self.assertEqual(len(exploration.states), 1) self.assertEqual(exploration.version, 4) snapshots_metadata = exp_services.get_exploration_snapshots_metadata( self.EXP_ID, 5) commit_dict_4 = { 'committer_id': 'committer_id_v4', 'commit_message': 'Reverted exploration to version 1', 'version_number': 4, } commit_dict_3 = { 'committer_id': 'committer_id_v3', 'commit_message': 'Added new state', 'version_number': 3, } self.assertEqual(len(snapshots_metadata), 4) self.assertDictContainsSubset( commit_dict_4, snapshots_metadata[0]) self.assertDictContainsSubset(commit_dict_3, snapshots_metadata[1]) self.assertGreaterEqual( snapshots_metadata[0]['created_on'], snapshots_metadata[1]['created_on'])
def test_reverted_exploration_maintains_classifier_model_mapping( self) -> None: """Test if the classifier model mapping is maintained when an exploration is reverted. """ state_name = 'Home' exploration = exp_fetchers.get_exploration_by_id(self.exp_id) interaction_id = exploration.states[state_name].interaction.id algorithm_id = feconf.INTERACTION_CLASSIFIER_MAPPING[interaction_id][ 'algorithm_id'] # Make changes to the exploration. change_list = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY, 'property_name': 'title', 'new_value': 'A new title' }) ] with self.swap(feconf, 'ENABLE_ML_CLASSIFIERS', True): exp_services.update_exploration( # type: ignore[no-untyped-call] feconf.SYSTEM_COMMITTER_ID, self.exp_id, change_list, '') current_exploration = exp_fetchers.get_exploration_by_id(self.exp_id) # Store the job before reverting the exploration. old_job = classifier_services.get_classifier_training_job( self.exp_id, current_exploration.version, state_name, algorithm_id) # Ruling out the possibility of None for mypy type checking. assert old_job is not None old_job_id = old_job.job_id # Revert the exploration. with self.swap(feconf, 'ENABLE_ML_CLASSIFIERS', True): exp_services.revert_exploration( # type: ignore[no-untyped-call] feconf.SYSTEM_COMMITTER_ID, self.exp_id, current_exploration.version, current_exploration.version - 1) # Verify if classifier model mapping is maintained using the job ID. reverted_exploration = exp_fetchers.get_exploration_by_id(self.exp_id) self.assertEqual(reverted_exploration.version, current_exploration.version + 1) new_job = classifier_services.get_classifier_training_job( self.exp_id, reverted_exploration.version, state_name, algorithm_id) # Ruling out the possibility of None for mypy type checking. assert new_job is not None new_job_id = new_job.job_id self.assertEqual(old_job_id, new_job_id)
def test_contributors_with_only_reverts_not_included(self): """Test that if only reverts are made by contributor then the contributions summary shouldn’t contain that contributor’s ID. """ user_a_id = self.get_user_id_from_email(self.EMAIL_A) user_b_id = self.get_user_id_from_email(self.EMAIL_B) # Let USER A make three commits. exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Exploration Title 1') exp_services.update_exploration(user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }) ], 'Changed title.') exp_services.update_exploration(user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }) ], 'Changed Objective.') # Let the second user revert version 3 to version 2. exp_services.revert_exploration(user_b_id, self.EXP_ID, 3, 2) # Run the job to compute the contributors summary. job_id = exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.create_new() # pylint: disable=line-too-long exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.enqueue( job_id) self.process_and_flush_pending_tasks() exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) # Check that the contributors_summary does not contains user_b_id. self.assertNotIn(user_b_id, exploration_summary.contributors_summary) # Check that the User A has only 2 commits after user b has reverted # to version 2. self.assertEquals(2, exploration_summary.contributors_summary[user_a_id]) # pylint: disable=line-too-long
def test_contributors_with_only_reverts_not_included(self): """Test that if only reverts are made by contributor then the contributions summary shouldn’t contain that contributor’s ID """ user_a_id = self.get_user_id_from_email(self.EMAIL_A) user_b_id = self.get_user_id_from_email(self.EMAIL_B) # Let USER A make three commits. exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Exploration Title 1') exp_services.update_exploration( user_a_id, self.EXP_ID, [{ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }], 'Changed title.') exp_services.update_exploration( user_a_id, self.EXP_ID, [{ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }], 'Changed Objective.') # Let the second user revert version 3 to version 2 exp_services.revert_exploration(user_b_id, self.EXP_ID, 3, 2) # Run the job to compute the contributors summary. job_id = exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.create_new() # pylint: disable=line-too-long exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) # Check that the contributors_summary does not contains user_b_id self.assertNotIn(user_b_id, exploration_summary.contributors_summary) # Check that the User A has only 2 commits after user b has reverted # to version 2 self.assertEquals(2, exploration_summary.contributors_summary[user_a_id]) # pylint: disable=line-too-long
def test_reverts_not_counted(self): """Test that if both non-revert commits and revert are made by contributor then the contributions summary shows only non-revert commits for that contributor. However, the commits made after the version to which we have reverted shouldn't be counted either. """ user_a_id = self.get_user_id_from_email(self.EMAIL_A) # Let USER A make 3 non-revert commits. exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Exploration Title') exp_services.update_exploration(user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }) ], 'Changed title.') exp_services.update_exploration(user_a_id, self.EXP_ID, [ exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }) ], 'Changed Objective.') # Let USER A revert version 3 to version 2. exp_services.revert_exploration(user_a_id, self.EXP_ID, 3, 2) # Run the job to compute the contributor summary. job_id = exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.create_new() # pylint: disable=line-too-long exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.enqueue( job_id) self.process_and_flush_pending_tasks() # Check that USER A's number of contributions is equal to 2. exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) self.assertEqual(2, exploration_summary.contributors_summary[user_a_id])
def post(self, exploration_id): """Handles POST requests.""" current_version = self.payload.get('current_version') revert_to_version = self.payload.get('revert_to_version') if not isinstance(revert_to_version, int): raise self.InvalidInputException( 'Expected an integer version to revert to; received %s.' % revert_to_version) if not isinstance(current_version, int): raise self.InvalidInputException( 'Expected an integer current version; received %s.' % current_version) if revert_to_version < 1 or revert_to_version >= current_version: raise self.InvalidInputException( 'Cannot revert to version %s from version %s.' % (revert_to_version, current_version)) exp_services.revert_exploration(self.user_id, exploration_id, current_version, revert_to_version) self.render_json({})
def post(self, exploration_id): """Handles POST requests.""" current_version = self.payload.get('current_version') revert_to_version = self.payload.get('revert_to_version') if not isinstance(revert_to_version, int): raise self.InvalidInputException( 'Expected an integer version to revert to; received %s.' % revert_to_version) if not isinstance(current_version, int): raise self.InvalidInputException( 'Expected an integer current version; received %s.' % current_version) if revert_to_version < 1 or revert_to_version >= current_version: raise self.InvalidInputException( 'Cannot revert to version %s from version %s.' % (revert_to_version, current_version)) exp_services.revert_exploration( self.user_id, exploration_id, current_version, revert_to_version) self.render_json({})
def test_reverts_not_counted(self): """Test that if both non-revert commits and revert are made by contributor then the contributions summary shows only non-revert commits for that contributor. However, the commits made after the version to which we have reverted shouldn't be counted either. """ user_a_id = self.get_user_id_from_email(self.EMAIL_A) # Let USER A make 3 non-revert commits exploration = self.save_new_valid_exploration( self.EXP_ID, user_a_id, title='Exploration Title') exp_services.update_exploration( user_a_id, self.EXP_ID, [{ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'New Exploration Title' }], 'Changed title.') exp_services.update_exploration( user_a_id, self.EXP_ID, [{ 'cmd': 'edit_exploration_property', 'property_name': 'objective', 'new_value': 'New Objective' }], 'Changed Objective.') # Let USER A revert version 3 to version 2 exp_services.revert_exploration(user_a_id, self.EXP_ID, 3, 2) # Run the job to compute the contributor summary. job_id = exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.create_new() # pylint: disable=line-too-long exp_jobs_one_off.ExplorationContributorsSummaryOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Check that USER A's number of contributions is equal to 2 exploration_summary = exp_services.get_exploration_summary_by_id( exploration.id) self.assertEqual(2, exploration_summary.contributors_summary[user_a_id])
def test_exploration_revert_updates_stats_to_old_values(self): # Update exploration to version 2. change_list = [{ 'cmd': exp_domain.CMD_ADD_STATE, 'state_name': 'New state', }] exp_services.update_exploration(feconf.SYSTEM_COMMITTER_ID, self.exp_id, change_list, '') self.exploration = exp_services.get_exploration_by_id(self.exp_id) # Create event models for version 2 of the exploration. stats_models.StartExplorationEventLogEntryModel.create( self.exp_id, self.exploration.version, 'Home', 'session_id3', {}, feconf.PLAY_TYPE_NORMAL) stats_models.StateHitEventLogEntryModel.create( self.exp_id, self.exploration.version, 'Home', 'session_id3', {}, feconf.PLAY_TYPE_NORMAL) # Revert exploration to version 1. exp_services.revert_exploration(feconf.SYSTEM_COMMITTER_ID, self.exp_id, 2, 1) self.exploration = exp_services.get_exploration_by_id(self.exp_id) job_id = stats_jobs_one_off.GenerateV1StatisticsJob.create_new() stats_jobs_one_off.GenerateV1StatisticsJob.enqueue(job_id) self.assertEqual( self.count_jobs_in_taskqueue( taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 1) self.process_and_flush_pending_tasks() exploration_stats = stats_services.get_exploration_stats_by_id( self.exp_id, self.exploration.version) self.assertEqual(exploration_stats.num_starts_v1, 2) self.assertEqual(exploration_stats.num_completions_v1, 1) self.assertEqual(exploration_stats.num_actual_starts_v1, 1) self.assertEqual( exploration_stats.state_stats_mapping['Home'].first_hit_count_v1, 2) self.assertEqual( exploration_stats.state_stats_mapping['End'].first_hit_count_v1, 1) self.assertEqual( exploration_stats.state_stats_mapping['Home'].total_hit_count_v1, 3) self.assertEqual( exploration_stats.state_stats_mapping['End'].total_hit_count_v1, 1) self.assertEqual( exploration_stats.state_stats_mapping['Home'].num_completions_v1, 2) self.assertEqual( exploration_stats.state_stats_mapping['End'].num_completions_v1, 1) self.assertEqual( exploration_stats.state_stats_mapping['Home']. total_answers_count_v1, 2) self.assertEqual( exploration_stats.state_stats_mapping['End']. total_answers_count_v1, 0) self.assertEqual( exploration_stats.state_stats_mapping['Home']. useful_feedback_count_v1, 1) self.assertEqual( exploration_stats.state_stats_mapping['End']. useful_feedback_count_v1, 0)
def test_migration_then_reversion_maintains_valid_exploration(self): """This integration test simulates the behavior of the domain layer prior to the introduction of a states schema. In particular, it deals with an exploration that was created before any states schema migrations occur. The exploration is constructed using multiple change lists, then a migration is run. The test thereafter tests if reverting to a version prior to the migration still maintains a valid exploration. It tests both the exploration domain object and the exploration model stored in the datastore for validity. Note: It is important to distinguish between when the test is testing the exploration domain versus its model. It is operating at the domain layer when using exp_fetchers.get_exploration_by_id. Otherwise, it loads the model explicitly using exp_models.ExplorationModel.get and then converts it to an exploration domain object for validation using exp_fetchers.get_exploration_from_model. This is NOT the same process as exp_fetchers.get_exploration_by_id as it skips many steps which include the conversion pipeline (which is crucial to this test). """ exp_id = 'exp_id2' end_state_name = 'End' # Create an exploration with an old states schema version. swap_states_schema_41 = self.swap(feconf, 'CURRENT_STATE_SCHEMA_VERSION', 41) swap_exp_schema_46 = self.swap(exp_domain.Exploration, 'CURRENT_EXP_SCHEMA_VERSION', 46) with swap_states_schema_41, swap_exp_schema_46: self.save_new_valid_exploration(exp_id, self.albert_id, title='Old Title', end_state_name=end_state_name) caching_services.delete_multi( caching_services.CACHE_NAMESPACE_EXPLORATION, None, [exp_id]) # Load the exploration without using the conversion pipeline. All of # these changes are to happen on an exploration with states schema # version 41. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # In version 1, the title was 'Old title'. # In version 2, the title becomes 'New title'. exploration_model.title = 'New title' exploration_model.commit(self.albert_id, 'Changed title.', []) # Version 2 of exploration. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # Store state id mapping model for new exploration. exp_fetchers.get_exploration_from_model(exploration_model) # In version 3, a new state is added. exploration_model.states['New state'] = { 'solicit_answer_details': False, 'written_translations': { 'translations_mapping': { 'content': {}, 'default_outcome': {}, 'ca_placeholder_0': {}, } }, 'recorded_voiceovers': { 'voiceovers_mapping': { 'content': {}, 'default_outcome': {}, 'ca_placeholder_0': {}, } }, 'param_changes': [], 'classifier_model_id': None, 'content': { 'content_id': 'content', 'html': '<p>Unicode Characters ����</p>' }, 'next_content_id_index': 5, 'interaction': { 'answer_groups': [], 'confirmed_unclassified_answers': [], 'customization_args': { 'buttonText': { 'value': { 'content_id': 'ca_placeholder_0', 'unicode_str': 'Click me!', }, }, }, 'default_outcome': { 'dest': end_state_name, 'feedback': { 'content_id': 'default_outcome', 'html': '', }, 'labelled_as_correct': False, 'missing_prerequisite_skill_id': None, 'param_changes': [], 'refresher_exploration_id': None, }, 'hints': [], 'id': 'Continue', 'solution': None, }, } # Properly link in the new state to avoid an invalid exploration. init_state = exploration_model.states[feconf.DEFAULT_INIT_STATE_NAME] init_state['interaction']['default_outcome']['dest'] = 'New state' exploration_model.commit('committer_id_v3', 'Added new state', []) # Version 3 of exploration. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # Version 4 is an upgrade based on the migration job. commit_cmds = [ exp_domain.ExplorationChange({ 'cmd': exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION, 'from_version': str(exploration_model.states_schema_version), 'to_version': str(feconf.CURRENT_STATE_SCHEMA_VERSION) }) ] exp_services.update_exploration( feconf.MIGRATION_BOT_USERNAME, exploration_model.id, commit_cmds, 'Update exploration states from schema version %d to %d.' % (exploration_model.states_schema_version, feconf.CURRENT_STATE_SCHEMA_VERSION)) # Verify the latest version of the exploration has the most up-to-date # states schema version. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) exploration = exp_fetchers.get_exploration_from_model( exploration_model, run_conversion=False) self.assertEqual(exploration.states_schema_version, feconf.CURRENT_STATE_SCHEMA_VERSION) # The exploration should be valid after conversion. exploration.validate(strict=True) # Version 5 is a reversion to version 1. exp_services.revert_exploration('committer_id_v4', exp_id, 4, 1) # The exploration model itself should now be the old version # (pre-migration). exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) self.assertEqual(exploration_model.states_schema_version, 41) # The exploration domain object should be updated since it ran through # the conversion pipeline. exploration = exp_fetchers.get_exploration_by_id(exp_id) # The reversion after migration should still be an up-to-date # exploration. exp_fetchers.get_exploration_by_id will automatically # keep it up-to-date. self.assertEqual(exploration.to_yaml(), self.UPGRADED_EXP_YAML) # The exploration should be valid after reversion. exploration.validate(strict=True) snapshots_metadata = exp_services.get_exploration_snapshots_metadata( exp_id) # These are used to verify the correct history has been recorded after # both migration and reversion. commit_dict_5 = { 'committer_id': 'committer_id_v4', 'commit_message': 'Reverted exploration to version 1', 'version_number': 5, } commit_dict_4 = { 'committer_id': feconf.MIGRATION_BOT_USERNAME, 'commit_message': 'Update exploration states from schema version 41 to %d.' % feconf.CURRENT_STATE_SCHEMA_VERSION, 'commit_cmds': [{ 'cmd': exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION, 'from_version': '41', 'to_version': str(feconf.CURRENT_STATE_SCHEMA_VERSION) }], 'version_number': 4, } # Ensure there have been 5 commits. self.assertEqual(len(snapshots_metadata), 5) # Ensure the correct commit logs were entered during both migration and # reversion. Also, ensure the correct commit command was written during # migration. # These asserts check whether one dict is subset of the other. # The format is assertDictEqual(a, {**a, **b}) where a is the superset # and b is the subset. self.assertDictEqual(snapshots_metadata[3], { **snapshots_metadata[3], **commit_dict_4 }) self.assertDictEqual(snapshots_metadata[4], { **snapshots_metadata[4], **commit_dict_5 }) self.assertLess(snapshots_metadata[3]['created_on_ms'], snapshots_metadata[4]['created_on_ms']) # Ensure that if a converted, then reverted, then converted exploration # is saved, it will be the up-to-date version within the datastore. exp_services.update_exploration(self.albert_id, exp_id, [], 'Resave after reversion') exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) exploration = exp_fetchers.get_exploration_from_model( exploration_model, run_conversion=False) # This exploration should be both up-to-date and valid. self.assertEqual(exploration.to_yaml(), self.UPGRADED_EXP_YAML) exploration.validate()
def test_migration_then_reversion_maintains_valid_exploration(self): """This integration test simulates the behavior of the domain layer prior to the introduction of a states schema. In particular, it deals with an exploration that was created before any states schema migrations occur. The exploration is constructed using multiple change lists, then a migration job is run. The test thereafter tests if reverting to a version prior to the migration still maintains a valid exploration. It tests both the exploration domain object and the exploration model stored in the datastore for validity. Note: It is important to distinguish between when the test is testing the exploration domain versus its model. It is operating at the domain layer when using exp_fetchers.get_exploration_by_id. Otherwise, it loads the model explicitly using exp_models.ExplorationModel.get and then converts it to an exploration domain object for validation using exp_fetchers.get_exploration_from_model. This is NOT the same process as exp_fetchers.get_exploration_by_id as it skips many steps which include the conversion pipeline (which is crucial to this test). """ exp_id = 'exp_id2' # Create a exploration with states schema version 0. self.save_new_exp_with_states_schema_v0(exp_id, self.albert_id, 'Old Title') # Load the exploration without using the conversion pipeline. All of # these changes are to happen on an exploration with states schema # version 0. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # In version 1, the title was 'Old title'. # In version 2, the title becomes 'New title'. exploration_model.title = 'New title' exploration_model.commit(self.albert_id, 'Changed title.', []) # Version 2 of exploration. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # Store state id mapping model for new exploration. exploration = exp_fetchers.get_exploration_from_model( exploration_model) # In version 3, a new state is added. new_state = copy.deepcopy( self.VERSION_0_STATES_DICT[feconf.DEFAULT_INIT_STATE_NAME]) new_state['interaction']['id'] = 'TextInput' exploration_model.states['New state'] = new_state # Properly link in the new state to avoid an invalid exploration. init_state = exploration_model.states[feconf.DEFAULT_INIT_STATE_NAME] init_handler = init_state['interaction']['handlers'][0] init_handler['rule_specs'][0]['dest'] = 'New state' exploration_model.commit('committer_id_v3', 'Added new state', []) # Version 3 of exploration. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) # Store state id mapping model for new exploration. exploration = exp_fetchers.get_exploration_from_model( exploration_model) # Version 4 is an upgrade based on the migration job. # Start migration job on sample exploration. job_id = exp_jobs_one_off.ExplorationMigrationJobManager.create_new() exp_jobs_one_off.ExplorationMigrationJobManager.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify the latest version of the exploration has the most up-to-date # states schema version. exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) exploration = exp_fetchers.get_exploration_from_model( exploration_model, run_conversion=False) self.assertEqual(exploration.states_schema_version, feconf.CURRENT_STATE_SCHEMA_VERSION) # The exploration should be valid after conversion. exploration.validate(strict=True) # Version 5 is a reversion to version 1. exp_services.revert_exploration('committer_id_v4', exp_id, 4, 1) # The exploration model itself should now be the old version # (pre-migration). exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) self.assertEqual(exploration_model.states_schema_version, 0) # The exploration domain object should be updated since it ran through # the conversion pipeline. exploration = exp_fetchers.get_exploration_by_id(exp_id) # The reversion after migration should still be an up-to-date # exploration. exp_fetchers.get_exploration_by_id will automatically # keep it up-to-date. self.assertEqual(exploration.to_yaml(), self.UPGRADED_EXP_YAML) # The exploration should be valid after reversion. exploration.validate(strict=True) snapshots_metadata = exp_services.get_exploration_snapshots_metadata( exp_id) # These are used to verify the correct history has been recorded after # both migration and reversion. commit_dict_5 = { 'committer_id': 'committer_id_v4', 'commit_message': 'Reverted exploration to version 1', 'version_number': 5, } commit_dict_4 = { 'committer_id': feconf.MIGRATION_BOT_USERNAME, 'commit_message': 'Update exploration states from schema version 0 to %d.' % feconf.CURRENT_STATE_SCHEMA_VERSION, 'commit_cmds': [{ 'cmd': exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION, 'from_version': '0', 'to_version': python_utils.UNICODE(feconf.CURRENT_STATE_SCHEMA_VERSION) }], 'version_number': 4, } # Ensure there have been 5 commits. self.assertEqual(len(snapshots_metadata), 5) # Ensure the correct commit logs were entered during both migration and # reversion. Also, ensure the correct commit command was written during # migration. self.assertDictContainsSubset(commit_dict_4, snapshots_metadata[3]) self.assertDictContainsSubset(commit_dict_5, snapshots_metadata[4]) self.assertLess(snapshots_metadata[3]['created_on_ms'], snapshots_metadata[4]['created_on_ms']) # Ensure that if a converted, then reverted, then converted exploration # is saved, it will be the up-to-date version within the datastore. exp_services.update_exploration(self.albert_id, exp_id, [], 'Resave after reversion') exploration_model = exp_models.ExplorationModel.get(exp_id, strict=True, version=None) exploration = exp_fetchers.get_exploration_from_model( exploration_model, run_conversion=False) # This exploration should be both up-to-date and valid. self.assertEqual(exploration.to_yaml(), self.UPGRADED_EXP_YAML) exploration.validate(strict=True)
def setUp(self): """Populate the database of explorations and their summaries. The sequence of events is: - (1) Albert creates EXP_ID_1. - (2) Bob edits the title of EXP_ID_1. - (3) Albert creates EXP_ID_2. - (4) Albert edits the title of EXP_ID_1. - (5) Albert edits the title of EXP_ID_2. - (6) Bob reverts Albert's last edit to EXP_ID_1. - Bob tries to publish EXP_ID_2, and is denied access. - (7) Albert publishes EXP_ID_2. - (8) Albert creates EXP_ID_3 - (9) Albert publishes EXP_ID_3 - (10) Albert deletes EXP_ID_3 - (1) User_3 (has a profile_picture) creates EXP_ID_4. - (2) User_4 edits the title of EXP_ID_4. - (3) User_4 edits the title of EXP_ID_4. """ super(ExplorationDisplayableSummariesTest, self).setUp() self.signup(self.ALBERT_EMAIL, self.ALBERT_NAME) self.signup(self.BOB_EMAIL, self.BOB_NAME) self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL) self.bob_id = self.get_user_id_from_email(self.BOB_EMAIL) self.albert = user_services.get_user_actions_info(self.albert_id) self.bob = user_services.get_user_actions_info(self.bob_id) self.save_new_valid_exploration(self.EXP_ID_1, self.albert_id) exp_services.update_exploration( self.bob_id, self.EXP_ID_1, [exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 1 title' })], 'Changed title.') self.save_new_valid_exploration(self.EXP_ID_2, self.albert_id) exp_services.update_exploration( self.albert_id, self.EXP_ID_1, [exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 1 Albert title' })], 'Changed title to Albert1 title.') exp_services.update_exploration( self.albert_id, self.EXP_ID_2, [exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration 2 Albert title' })], 'Changed title to Albert2 title.') exp_services.revert_exploration(self.bob_id, self.EXP_ID_1, 3, 2) with self.assertRaisesRegex( Exception, 'This exploration cannot be published' ): rights_manager.publish_exploration(self.bob, self.EXP_ID_2) rights_manager.publish_exploration(self.albert, self.EXP_ID_2) self.save_new_valid_exploration(self.EXP_ID_3, self.albert_id) rights_manager.publish_exploration(self.albert, self.EXP_ID_3) exp_services.delete_exploration(self.albert_id, self.EXP_ID_3) self.signup(self.USER_C_EMAIL, self.USER_C_NAME) self.signup(self.USER_D_EMAIL, self.USER_D_NAME) self.user_c_id = self.get_user_id_from_email(self.USER_C_EMAIL) self.user_d_id = self.get_user_id_from_email(self.USER_D_EMAIL) user_services.update_profile_picture_data_url( self.user_c_id, self.USER_C_PROFILE_PICTURE) self.save_new_valid_exploration(self.EXP_ID_4, self.user_c_id) exp_services.update_exploration( self.user_d_id, self.EXP_ID_4, [exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration updated title' })], 'Changed title once.') exp_services.update_exploration( self.user_d_id, self.EXP_ID_4, [exp_domain.ExplorationChange({ 'cmd': 'edit_exploration_property', 'property_name': 'title', 'new_value': 'Exploration updated title again' })], 'Changed title twice.') self.save_new_valid_exploration(self.EXP_ID_5, self.bob_id)