def test_deleted_activity_is_removed_from_featured_list(self):
        rights_manager.publish_exploration(self.owner_id, self.EXP_ID_0)
        rights_manager.publish_exploration(self.owner_id, self.EXP_ID_1)
        rights_manager.publish_collection(self.owner_id, self.COL_ID_2)
        activity_services.update_featured_activity_references([
            self._create_exploration_reference(self.EXP_ID_0),
            self._create_collection_reference(self.COL_ID_2)])

        self._compare_lists(
            activity_services.get_featured_activity_references(), [
                self._create_exploration_reference(self.EXP_ID_0),
                self._create_collection_reference(self.COL_ID_2)])

        # Deleting an unfeatured activity does not affect the featured list.
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_1)
        self._compare_lists(
            activity_services.get_featured_activity_references(), [
                self._create_exploration_reference(self.EXP_ID_0),
                self._create_collection_reference(self.COL_ID_2)])

        # Deleting a featured activity removes it from the featured list.
        collection_services.delete_collection(self.owner_id, self.COL_ID_2)
        self._compare_lists(
            activity_services.get_featured_activity_references(), [
                self._create_exploration_reference(self.EXP_ID_0)])
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_0)
        self._compare_lists(
            activity_services.get_featured_activity_references(), [])
    def test_migration_job_skips_deleted_collection(self):
        """Tests that the collection migration job skips deleted collection
        and does not attempt to migrate.
        """
        collection = collection_domain.Collection.create_default_collection(
            self.COLLECTION_ID, 'A title', 'A Category', 'An Objective')
        collection_services.save_new_collection(self.albert_id, collection)

        # Note: This creates a summary based on the upgraded model (which is
        # fine). A summary is needed to delete the collection.
        collection_services.create_collection_summary(
            self.COLLECTION_ID, None)

        # Delete the exploration before migration occurs.
        collection_services.delete_collection(
            self.albert_id, self.COLLECTION_ID)

        # Ensure the exploration is deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)

        # Start migration job on sample collection.
        job_id = (
            collection_jobs_one_off.CollectionMigrationJob.create_new())
        collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id)

        # This running without errors indicates the deleted collection is
        # being ignored.
        self.process_and_flush_pending_tasks()

        # Ensure the exploration is still deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)
예제 #3
0
    def test_deleted_collection(self):
        with self.swap(
            subscription_services, 'subscribe_to_thread', self._null_fn
            ), self.swap(
                subscription_services, 'subscribe_to_exploration', self._null_fn
            ), self.swap(
                subscription_services, 'subscribe_to_collection', self._null_fn
            ):
            # User A creates and saves a new collection.
            self.save_new_default_collection(
                self.COLLECTION_ID_1, self.user_a_id)

            # User A deletes the collection.
            collection_services.delete_collection(
                self.user_a_id, self.COLLECTION_ID_1)

            # User A deletes the exploration from earlier.
            exp_services.delete_exploration(self.user_a_id, self.EXP_ID_1)

        self._run_one_off_job()

        # User A is not subscribed to the collection.
        user_a_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_a_id, strict=False)
        self.assertEqual(user_a_subscriptions_model, None)
예제 #4
0
    def test_deleted_collection(self):
        with self.swap(
                subscription_services, 'subscribe_to_thread', self._null_fn
            ), self.swap(
                subscription_services, 'subscribe_to_exploration',
                self._null_fn
            ), self.swap(
                subscription_services, 'subscribe_to_collection',
                self._null_fn):
            # User A creates and saves a new collection.
            self.save_new_default_collection(
                self.COLLECTION_ID_1, self.user_a_id)

            # User A deletes the collection.
            collection_services.delete_collection(
                self.user_a_id, self.COLLECTION_ID_1)

            # User A deletes the exploration from earlier.
            exp_services.delete_exploration(self.user_a_id, self.EXP_ID_1)

        self._run_one_off_job()

        # User A is not subscribed to the collection.
        user_a_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_a_id, strict=False)
        self.assertEqual(user_a_subscriptions_model, None)
예제 #5
0
    def test_basic_computation_works_if_collection_is_deleted(self):
        with self._get_test_context():
            self.save_new_default_collection(COLLECTION_ID,
                                             USER_ID,
                                             title=COLLECTION_TITLE)
            last_updated_ms_before_deletion = (
                self._get_most_recent_collection_snapshot_created_on_ms(
                    COLLECTION_ID))
            collection_services.delete_collection(USER_ID, COLLECTION_ID)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT), 1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (ModifiedRecentUpdatesAggregator.
                                    get_recent_notifications(USER_ID)[1])
            self.assertEqual(len(recent_notifications), 1)
            self.assertEqual(sorted(recent_notifications[0].keys()), [
                'activity_id', 'activity_title', 'author_id',
                'last_updated_ms', 'subject', 'type'
            ])
            self.assertDictContainsSubset(
                {
                    'type': feconf.UPDATE_TYPE_COLLECTION_COMMIT,
                    'activity_id': COLLECTION_ID,
                    'activity_title': COLLECTION_TITLE,
                    'author_id': USER_ID,
                    'subject': feconf.COMMIT_MESSAGE_COLLECTION_DELETED,
                }, recent_notifications[0])
            self.assertLess(last_updated_ms_before_deletion,
                            recent_notifications[0]['last_updated_ms'])
예제 #6
0
    def test_basic_computation_with_deleted_collection(self):
        observed_log_messages = []

        def _mock_logging_function(msg, *args):
            """Mocks logging.error()."""
            observed_log_messages.append(msg % args)

        logging_swap = self.swap(logging, 'error', _mock_logging_function)
        self.save_new_default_collection(
            COLLECTION_ID, USER_ID, title=COLLECTION_TITLE)
        collection_services.delete_collection(
            USER_ID, COLLECTION_ID, force_deletion=True)

        with self.swap(
            user_jobs_continuous, 'DashboardRecentUpdatesAggregator',
            MockRecentUpdatesAggregator):

            (
                user_jobs_continuous.DashboardRecentUpdatesAggregator
                .start_computation())
            self.assertEqual(
                self.count_jobs_in_mapreduce_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)

            with logging_swap:
                self.process_and_flush_pending_mapreduce_tasks()

            self.assertEqual(
                self.count_jobs_in_mapreduce_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            self.assertEqual(
                observed_log_messages,
                ['Could not find collection %s' % COLLECTION_ID])
예제 #7
0
    def test_basic_computation_works_if_collection_is_deleted(self):
        with self._get_test_context():
            self.save_new_default_collection(
                COLLECTION_ID, USER_ID, title=COLLECTION_TITLE)
            last_updated_ms_before_deletion = (
                self._get_most_recent_collection_snapshot_created_on_ms(
                    COLLECTION_ID))
            collection_services.delete_collection(USER_ID, COLLECTION_ID)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT),
                1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    USER_ID)[1])
            self.assertEqual(len(recent_notifications), 1)
            self.assertEqual(sorted(recent_notifications[0].keys()), [
                'activity_id', 'activity_title', 'author_id',
                'last_updated_ms', 'subject', 'type'])
            self.assertDictContainsSubset({
                'type': feconf.UPDATE_TYPE_COLLECTION_COMMIT,
                'activity_id': COLLECTION_ID,
                'activity_title': COLLECTION_TITLE,
                'author_id': USER_ID,
                'subject': feconf.COMMIT_MESSAGE_COLLECTION_DELETED,
            }, recent_notifications[0])
            self.assertLess(
                last_updated_ms_before_deletion,
                recent_notifications[0]['last_updated_ms'])
    def test_deleted_activity_is_removed_from_featured_list(self):
        rights_manager.publish_exploration(self.owner, self.EXP_ID_0)
        rights_manager.publish_exploration(self.owner, self.EXP_ID_1)
        rights_manager.publish_collection(self.owner, self.COL_ID_2)
        activity_services.update_featured_activity_references([
            self._create_exploration_reference(self.EXP_ID_0),
            self._create_collection_reference(self.COL_ID_2)
        ])

        self._compare_lists(
            activity_services.get_featured_activity_references(), [
                self._create_exploration_reference(self.EXP_ID_0),
                self._create_collection_reference(self.COL_ID_2)
            ])

        # Deleting an unfeatured activity does not affect the featured list.
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_1)
        self._compare_lists(
            activity_services.get_featured_activity_references(), [
                self._create_exploration_reference(self.EXP_ID_0),
                self._create_collection_reference(self.COL_ID_2)
            ])

        # Deleting a featured activity removes it from the featured list.
        collection_services.delete_collection(self.owner_id, self.COL_ID_2)
        self._compare_lists(
            activity_services.get_featured_activity_references(),
            [self._create_exploration_reference(self.EXP_ID_0)])
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_0)
        self._compare_lists(
            activity_services.get_featured_activity_references(), [])
예제 #9
0
    def test_deleting_collection_does_not_delete_subscription(self):
        self.save_new_default_collection(COLLECTION_ID, self.owner_id)
        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id), [COLLECTION_ID])

        collection_services.delete_collection(self.owner_id, COLLECTION_ID)

        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id), [COLLECTION_ID])
    def test_migration_job_skips_deleted_collection(self):
        """Tests that the collection migration job skips deleted collection
        and does not attempt to migrate.
        """
        collection = collection_domain.Collection.create_default_collection(
            self.COLLECTION_ID,
            title='A title',
            category='A Category',
            objective='An Objective')
        collection_services.save_new_collection(self.albert_id, collection)

        # Note: This creates a summary based on the upgraded model (which is
        # fine). A summary is needed to delete the collection.
        collection_services.create_collection_summary(self.COLLECTION_ID, None)

        # Delete the exploration before migration occurs.
        collection_services.delete_collection(self.albert_id,
                                              self.COLLECTION_ID)

        # Ensure the exploration is deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)

        # Start migration job on sample collection.
        job_id = (collection_jobs_one_off.CollectionMigrationJob.create_new())
        collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id)

        # This running without errors indicates the deleted collection is
        # being ignored.
        self.process_and_flush_pending_tasks()

        # Ensure the exploration is still deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)
예제 #11
0
    def test_deleting_collection_does_not_delete_subscription(self):
        self.save_new_default_collection(COLLECTION_ID, self.owner_id)
        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id),
                         [COLLECTION_ID])

        collection_services.delete_collection(self.owner_id, COLLECTION_ID)

        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id),
                         [COLLECTION_ID])
예제 #12
0
    def test_deleting_collection_does_not_delete_subscription(self) -> None:
        self.save_new_default_collection(
            COLLECTION_ID, self.owner_id)  # type: ignore[no-untyped-call]
        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id),
                         [COLLECTION_ID])

        collection_services.delete_collection(
            self.owner_id, COLLECTION_ID)  # type: ignore[no-untyped-call]

        self.assertEqual(self._get_collection_ids_subscribed_to(self.owner_id),
                         [COLLECTION_ID])
예제 #13
0
    def test_migration_job_skips_deleted_collection(self):
        """Tests that the collection migration job skips deleted collection
        and does not attempt to migrate.
        """
        collection = collection_domain.Collection.create_default_collection(
            self.COLLECTION_ID,
            title='A title',
            category='A Category',
            objective='An Objective')
        collection_services.save_new_collection(self.albert_id, collection)

        # Note: This creates a summary based on the upgraded model (which is
        # fine). A summary is needed to delete the collection.
        collection_services.regenerate_collection_and_contributors_summaries(
            self.COLLECTION_ID)

        # Delete the exploration before migration occurs.
        collection_services.delete_collection(self.albert_id,
                                              self.COLLECTION_ID)

        # Ensure the exploration is deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)

        # Start migration job on sample collection.
        job_id = (
            collection_jobs_one_off.CollectionMigrationOneOffJob.create_new())
        collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue(job_id)

        # This running without errors indicates the deleted collection is
        # being ignored.
        self.process_and_flush_pending_mapreduce_tasks()

        # Ensure the exploration is still deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            collection_services.get_collection_by_id(self.COLLECTION_ID)

        output = (collection_jobs_one_off.CollectionMigrationOneOffJob.
                  get_output(job_id))
        expected = [[
            u'collection_deleted', [u'Encountered 1 deleted collections.']
        ]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #14
0
def pre_delete_user(user_id):
    """Prepare user for the full deletion.
        1. Mark all the activities that are private and solely owned by the user
           being deleted as deleted.
        2. Disable all the email preferences.
        3. Mark the user as to be deleted.
        4. Create PendingDeletionRequestModel for the user.

    Args:
        user_id: str. The id of the user to be deleted.
    """
    subscribed_exploration_summaries = (
        exp_fetchers.get_exploration_summaries_subscribed_to(user_id))
    explorations_to_be_deleted_ids = [
        exp_summary.id for exp_summary in subscribed_exploration_summaries
        if exp_summary.is_private()
        and exp_summary.is_solely_owned_by_user(user_id)
    ]
    # TODO(#8301): Implement delete_explorations to make this efficient.
    for exp_id in explorations_to_be_deleted_ids:
        exp_services.delete_exploration(user_id, exp_id)

    subscribed_collection_summaries = (
        collection_services.get_collection_summaries_subscribed_to(user_id))
    collections_to_be_deleted_ids = [
        col_summary.id for col_summary in subscribed_collection_summaries
        if col_summary.is_private()
        and col_summary.is_solely_owned_by_user(user_id)
    ]
    # TODO(#8301): Implement delete_collections to make this efficient.
    for col_id in collections_to_be_deleted_ids:
        collection_services.delete_collection(user_id, col_id)

    # Set all the user's email preferences to False in order to disable all
    # ordinary emails that could be sent to the users.
    user_services.update_email_preferences(user_id, False, False, False, False)

    user_services.mark_user_for_deletion(
        user_id,
        explorations_to_be_deleted_ids,
        collections_to_be_deleted_ids,
    )
    def test_get_activity_progress(self):
        # Add activities to the completed section.
        learner_progress_services.mark_exploration_as_completed(
            self.user_id, self.EXP_ID_0)
        learner_progress_services.mark_collection_as_completed(
            self.user_id, self.COL_ID_0)

        # Add activities to the incomplete section.
        state_name = 'state name'
        version = 1
        learner_progress_services.mark_exploration_as_incomplete(
            self.user_id, self.EXP_ID_1, state_name, version)
        learner_progress_services.mark_collection_as_incomplete(
            self.user_id, self.COL_ID_1)

        # Add activities to the playlist section.
        learner_progress_services.add_exp_to_learner_playlist(
            self.user_id, self.EXP_ID_3)
        learner_progress_services.add_collection_to_learner_playlist(
            self.user_id, self.COL_ID_3)

        # Get the progress of the user.
        activity_progress = learner_progress_services.get_activity_progress(
            self.user_id)

        incomplete_exp_summaries = (
            activity_progress[0].incomplete_exp_summaries)
        incomplete_collection_summaries = (
            activity_progress[0].incomplete_collection_summaries)
        completed_exp_summaries = (
            activity_progress[0].completed_exp_summaries)
        completed_collection_summaries = (
            activity_progress[0].completed_collection_summaries)
        exploration_playlist_summaries = (
            activity_progress[0].exploration_playlist_summaries)
        collection_playlist_summaries = (
            activity_progress[0].collection_playlist_summaries)

        self.assertEqual(len(incomplete_exp_summaries), 1)
        self.assertEqual(len(incomplete_collection_summaries), 1)
        self.assertEqual(len(completed_exp_summaries), 1)
        self.assertEqual(len(completed_collection_summaries), 1)
        self.assertEqual(len(exploration_playlist_summaries), 1)
        self.assertEqual(len(collection_playlist_summaries), 1)

        self.assertEqual(incomplete_exp_summaries[0].title, 'Sillat Suomi')
        self.assertEqual(incomplete_collection_summaries[0].title,
                         'Introduce Oppia')
        self.assertEqual(completed_exp_summaries[0].title,
                         'Bridges in England')
        self.assertEqual(completed_collection_summaries[0].title, 'Bridges')
        self.assertEqual(exploration_playlist_summaries[0].title,
                         'Welcome Oppia')
        self.assertEqual(collection_playlist_summaries[0].title,
                         'Welcome Oppia Collection')

        # Delete an exploration in the completed section.
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_0)
        # Delete an exploration in the incomplete section.
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_1)
        # Delete an exploration in the playlist section.
        exp_services.delete_exploration(self.owner_id, self.EXP_ID_3)
        # Add an exploration to a collection that has already been completed.
        collection_services.update_collection(
            self.owner_id, self.COL_ID_0, [{
                'cmd': collection_domain.CMD_ADD_COLLECTION_NODE,
                'exploration_id': self.EXP_ID_2
            }], 'Add new exploration')

        # Get the progress of the user.
        activity_progress = learner_progress_services.get_activity_progress(
            self.user_id)

        # Check that the exploration is no longer present in the incomplete
        # section.
        self.assertEqual(len(activity_progress[0].incomplete_exp_summaries), 0)
        # Check that the dashboard records the exploration deleted in the
        # completed section.
        self.assertEqual(activity_progress[1]['completed_explorations'], 1)
        # Check that the dashboard records the exploration deleted in the
        # incomplete section.
        self.assertEqual(activity_progress[1]['incomplete_explorations'], 1)
        # Check that the dashboard records the exploration deleted in the
        # playlist section.
        self.assertEqual(activity_progress[1]['exploration_playlist'], 1)

        incomplete_collection_summaries = (
            activity_progress[0].incomplete_collection_summaries)

        # Check that the collection to which a new exploration has been added
        # has been moved to the incomplete section.
        self.assertEqual(len(incomplete_collection_summaries), 2)
        self.assertEqual(incomplete_collection_summaries[1].title, 'Bridges')
        # Check that the dashboard has recorded the change in the collection.
        self.assertEqual(activity_progress[2], ['Bridges'])

        # Now suppose the user has completed the collection. It should be added
        # back to the completed section.
        learner_progress_services.mark_collection_as_completed(
            self.user_id, self.COL_ID_0)

        # Delete a collection in the completed section.
        collection_services.delete_collection(self.owner_id, self.COL_ID_0)
        # Delete a collection in the incomplete section.
        collection_services.delete_collection(self.owner_id, self.COL_ID_1)
        # Delete a collection in the playlist section.
        collection_services.delete_collection(self.owner_id, self.COL_ID_3)

        # Get the progress of the user.
        activity_progress = learner_progress_services.get_activity_progress(
            self.user_id)

        # Check that the dashboard records the collection deleted in the
        # completed section.
        self.assertEqual(activity_progress[1]['completed_collections'], 1)
        # Check that the dashboard records the collection deleted in the
        # incomplete section.
        self.assertEqual(activity_progress[1]['incomplete_collections'], 1)
        # Check that the dashboard records the collection deleted in the
        # playlist section.
        self.assertEqual(activity_progress[1]['collection_playlist'], 1)