Beispiel #1
0
    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)
Beispiel #3
0
    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 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)
Beispiel #5
0
    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)
Beispiel #6
0
    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)
Beispiel #7
0
    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)
Beispiel #8
0
    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({})
Beispiel #9
0
    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'])
Beispiel #10
0
    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)
Beispiel #11
0
    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
Beispiel #13
0
    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])
Beispiel #14
0
    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({})
Beispiel #15
0
    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)
Beispiel #18
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()
Beispiel #19
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 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)