Repository: oppia/oppia

Issue:
Finish writing remaining backend tests
## Note:  As it was getting hard to maintain the long conversastion thread in this issue we have moved this issue to #6550

_This is intended as a starter project for newer contributors._

We want to implement backend tests for all parts of the code that are missing them. For each file mentioned below, the parts missing can be identified by generating a coverage report (see [this wiki page](https://github.com/oppia/oppia/wiki/Writing-backend-tests)).

A basic understanding of Python and the codebase would be a suggested requirement for this issue. If you have trouble trying to figure out something in the codebase, take a look at [Analyzing the Codebase](https://github.com/oppia/oppia/wiki/Analyzing-the-codebase).

If you'd like to help with this issue, please select a file from the list below, and comment on the issue that you'd be taking it up, and submit a PR after writing tests for the file. You can also submit a PR which has partially completed tests for a file, if the file is too extensive to test in a single PR. Once the PR is submitted, you can tick the checkboxes.

(For guidance on how tests should be structured, please see [this wiki page](https://github.com/oppia/oppia/wiki/Writing-backend-tests#guidelines-for-writing-good-tests).)

----

The following files have been tested partially, but do not have 100% coverage. Any of these files can be taken up and tested comprehensively. (Note that the link after each file points to its coverage report on Codecov.)

 - [ ] core.controllers.admin ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.base ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.classifier ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.creator_dashboard ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) @lilithxxx (19th March 2019)
 - [ ] core.controllers.editor ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.email_dashboard ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.feedback ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.learner_dashboard ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) @manyaagarwal (1 April 2019)
 - [ ] core.controllers.library ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.pages ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))  @netajik 
 - [ ] core.controllers.profile ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.reader ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.resources ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.story_editor ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.suggestion ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 
 - [ ] core.controllers.topic_editor ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers))
 - [ ] core.controllers.translator ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) 

 - [ ] core.domain.acl_decorators ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.activity_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.acl_decorators ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.classifier_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.classifier_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.collection_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.collection_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) 
 - [ ] core.domain.config_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.config_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.event_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.exp_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) 
 - [ ] core.domain.exp_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.exp_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.feedback_jobs_continuous ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.feedback_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.fs_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.html_validation_service ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @ShubhamBansal1997 (7th March 2019)
 - [ ] core.domain.learner_progress_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @ezl-13 
 - [ ] core.domain.obj_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) 
 - [ ] core.domain.param_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @nikhilmudholka  (PR at #6264)
 - [ ] core.domain.question_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.rating_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.recommendation_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.rights_manager ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.search_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.skill_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.skill_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.stats_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.stats_jobs_continuous ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.stats_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) 
 - [ ] core.domain.stats_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.story_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.story_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.subtopic_page_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @anubhavsinha98 (No date of assignment)
 - [ ] core.domain.subtopic_page_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @anubhavsinha98 (No date of assignment)
 - [ ] core.domain.suggestion_registry ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain)) @abhaygupta97 (No date of assignment)
 - [ ] core.domain.suggestion_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.summary_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.topic_domain ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.topic_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.user_jobs_continuous ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.user_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.user_query_jobs_one_off ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.domain.user_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/domain))
 - [ ] core.jobs ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core))

 - [ ] core.platform.email.gae_email_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/platform/email)) @siddhantkhandelwal (PR at #6335)
 - [ ] core.platform.image.gae_image_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/platform))
 - [ ] core.platform.search.gae_search_services ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/platform/search)) @lilithxxx (19th March 2019)

 - [ ] core.storage.base_model.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/base_model))
 - [ ] core.storage.classifier.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/classifier))
 - [ ] core.storage.collection.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/collection)) 
 - [ ] core.storage.email.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/email))  
 - [ ] core.storage.exploration.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/exploration)) @sophiewu6 (25 March 2019)
 - [ ] core.storage.feedback.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/feedback))
 - [ ] core.storage.file.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/file))
 - [ ] core.storage.question.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/question))
 - [ ] core.storage.statistics.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/statistics)) 
 - [ ] core.storage.user.gae_models ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/storage/user))

- [ ] scripts.custom_lint_checks (62%) ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/scripts))

---

**Completed:** 
  - [x] core.controllers.tasks -- @ctao5660
 - [x] core.controllers.collection_viewer ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) @jacoblipech 
 - [x] core.controllers.custom_landing_pages @gurungrahul2
 - [x] core.controllers.concept_card_viewer (92%) @xuanluong #5741
- [x] core.storage.skill.gae_models @sshou14 
- [x] core.controllers.recent_commits @sshou14 - #5551
- [x] core.controllers.skill_editor @xuanluong - #5686
- [x] core.controllers.topic_viewer @xuanluong - #5742
- [x] core.controllers.topics_and_skills_dashboard (96%) -- @xuanluong
- [x] core.domain.activity_domain @apb7 - #5194 
- [x] core.domain.calculation_registry @dawsoneliasen - #5391
 - [x] core.domain.learner_playlist_services @ezl-13 
- [x] core.domain.user_domain @unit-00 - #5380
- [x] core.platform.app_identity.gae_app_identity_services @neoliteconsultant - #5439
- [x] core.platform.memcache.gae_memcache_services @lilithxxx - #5355
 - [x] core.platform.models @varun-tandon  - # 6263
- [x] core.platform.taskqueue.gae_taskqueue_services @nalinbhardwaj
- [x] core.platform.users.gae_current_user_services @hemant0308 - #5563
- [x] core.storage.audit.gae_models @LanJosh - #5512
- [x] core.storage.collection.gae_models @LanJosh  - #5357
- [x] core.storage.config.gae_models  @dawsoneliasen 
- [x] core.storage.exploration.gae_models @sshou14  - #5552
- [X] core.storage.job.gae_models @sshou14 - #5433
- [X] core.storage.story.gae_models  @JoyLubega  - #5644
- [X] core.storage.topic.gae_models @tonadev - #5727
- [X] jinja_utils (87%) @hemant0308 -- #5582
 - [x] core.controllers.collection_editor @jacoblipech (10th March 2019)
 - [x] core.domain.learner_progress_domain -  @dchen97 #5859
 - [x] core.domain.question_domain (93%) - @rafalk342 #5887 
 - [x] core.domain.email_jobs_one_off (91%) @anubhavsinha98 
 - [x] core.platform.email.mailgun_email_services (42%) @ulloaluis #6133
 - [x] core.storage.file.gae_models (100%) @ankita240796 
 - [x] core.domain.collection_jobs_one_off (87%) @abhaygupta97 #6061
- [X] core.domain.email_manager @siddhantkhandelwal #6306 
- [X] schema_utils (96%) @ulloaluis  #6432 
-  [X] core.controllers.learner_dashboard ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) @varun-tandon (Upto 85%)
 - [x] core.controllers.question_editor ([coverage report](https://codecov.io/gh/oppia/oppia/tree/develop/core/controllers)) @ujjwalgulecha (#6217)


Discussion:
Hi @1995YogeshSharma, I'd like to implement backend tests for core.platform.models
Ok, assigned that to you.
I would like to work on **core.platform.users.gae_current_user_services** .
But I have a doubt..
How can we test GAE (Google App Engine) functionalities..??
HI @hemant0308, I have assigned that file to you.

You should go through the wiki pages linked in the issue description. I think that would make you clear on how to implement tests.


Please assign  core.controllers.tasks to me
Done -- go ahead, @mharelick :)
Hi! I would like to implement tests for core.domain.config_services
Hi @FinalHopee , I've assigned you for `core.domain.config_services`.
Another request for -- core.domain.learner_progress_domain
Hi @FinalHopee, assigned you :) 
Can I take a shot at writing the tests for core.domain.calculation_registry ?
Hi @dawsoneliasen, assigned you! 
Hi please assign core.platform.memcache.gae_memcache_services to me
Hi @lilithxxx, welcome to oppia, I think you haven't filled the survey yet, would you mind doing that (asked [here](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) in 2nd bullet) after that I'll assign you for the ` core.platform.memcache.gae_memcache_services`. Thanks!
@DubeySandeep done
NB: I added @LanJosh to core.storage.audit.gae_models and core.storage.collection.gae_models on 20 July, he's planning to get these done (maybe with others!) by 27 July. (This comment is just for keeping track of when the assignment happened.)
@lilithxxx, I have assigned you for `core.platform.memcache.gae_memcache_services`.
Hi hello! May I be assigned `user_domain`?

Done! Assigned you :)
Hi, may I be assign to core.controllers.custom_landing_pages 
Hi @nindyahapsari, :) I've assigned you to `core.controllers.custom_landing_pages`. :)
Hi @prakharchoudhary, any updates on `core.platform.models`?
Hi,

I would like to write tests for core.platform.app_identity.gae_app_identity_services
Hi @neoliteconsultant, I have assigned you to core.platform.app_identity.gae_app_identity_services
Hi, I'd like to write tests for core.storage.job.gae_models.
Sounds good, thanks @sshou14. Assigned you!
I'm almost done with my assigned ticket. Can I write one more test for core.controllers.recent_commits if no one else is working on it? thanks!
Hi @sshou14, sounds good, thanks! Go for it :)  I've assigned you.
can you tell me what we have to do in file 
core.platform.taskqueue.gae_taskqueue_services

i cant understand
I'd like to work on the core.storage.config.gae_models test!
Hi @dawsoneliasen, assigned. Thanks!
Hi! Would like to work on core.platform.taskqueue.gae_taskqueue_services :-)
Hm, I think @nalinbhardwaj has been working on that (see #5451). Pick another one?

(Sorry that the list wasn't updated! I'll fix that.)
@seanlip   core.platform.transactions.gae_transaction_services then?
Sounds good, thanks! Assigned you.
@seanlip Can I take file.gae_models instead of transaction models?
Yep, assigned you. Thanks!
hey, can i work on core.controllers.custom_landing_pages
Deassigning @nindyahapsari from custom_landing_pages. Assigned @binaryman008 . Good luck!
Hi, I'd like to work also on core.storage.exploration.gae_models.
Hi @sshou14,  I've assigned you to `core.storage.exploration.gae_models.` you can start working! :)

Resolving patch:
diff --git a/core/storage/exploration/gae_models.py b/core/storage/exploration/gae_models.py
--- a/core/storage/exploration/gae_models.py
+++ b/core/storage/exploration/gae_models.py
@@ -328,26 +328,6 @@ def get_all_non_private_commits(
         return cls._fetch_page_sorted_by_last_updated(
             query, page_size, urlsafe_start_cursor)
 
-    @classmethod
-    def get_all_exploration_commits(cls, exp_id, latest_version):
-        """Fetches all the commits made on a particular exploration up to latest
-        version of exploration.
-
-        Args:
-            exp_id: str. The exploration id.
-            latest_version: int. Latest version of the exploration.
-
-        Returns:
-            list(ExplorationCommitLogEntryModel). Commit log entry model
-            for each version of the given exploration.
-        """
-        model_ids = []
-        for version in range(1, latest_version + 1):
-            model_ids.append(cls._get_instance_id(exp_id, version))
-
-        models = cls.get_multi(model_ids)
-        return models
-
 
 class ExpSummaryModel(base_models.BaseModel):
     """Summary model for an Oppia exploration.


Test patch:
diff --git a/core/storage/exploration/gae_models_test.py b/core/storage/exploration/gae_models_test.py
new file mode 100644
--- /dev/null
+++ b/core/storage/exploration/gae_models_test.py
@@ -0,0 +1,318 @@
+# coding: utf-8
+#
+# Copyright 2018 The Oppia Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for Exploration models."""
+
+from constants import constants
+from core.domain import exp_domain
+from core.domain import exp_services
+from core.domain import rights_manager
+from core.platform import models
+from core.tests import test_utils
+
+(exploration_models,) = models.Registry.import_models(
+    [models.NAMES.exploration])
+
+
+class ExplorationModelUnitTest(test_utils.GenericTestBase):
+    """Test the ExplorationModel class."""
+
+    def test_get_exploration_count(self):
+        exploration = exp_domain.Exploration.create_default_exploration(
+            'id', title='A Title',
+            category='A Category', objective='An Objective')
+        exp_services.save_new_exploration('id', exploration)
+
+        self.assertEquals(
+            exploration_models.ExplorationModel.get_exploration_count(), 1)
+        saved_exploration = (
+            exploration_models.ExplorationModel.get_all().fetch(1)[0])
+        self.assertEquals(saved_exploration.title, 'A Title')
+        self.assertEquals(saved_exploration.category, 'A Category')
+        self.assertEquals(saved_exploration.objective, 'An Objective')
+
+
+class ExplorationRightsModelUnitTest(test_utils.GenericTestBase):
+    """Test the ExplorationRightsModel class."""
+
+    def test_save(self):
+        exploration_models.ExplorationRightsModel(
+            id='id_0',
+            owner_ids=['owner_id'],
+            editor_ids=['editor_id'],
+            translator_ids=['translator_id'],
+            viewer_ids=['viewer_id'],
+            community_owned=False,
+            status=constants.ACTIVITY_STATUS_PUBLIC,
+            viewable_if_private=False,
+            first_published_msec=0.0
+        ).save(
+            'cid', 'Created new exploration right',
+            [{'cmd': rights_manager.CMD_CREATE_NEW}])
+        saved_model = exploration_models.ExplorationRightsModel.get('id_0')
+        self.assertEqual(saved_model.id, 'id_0')
+        self.assertEqual(saved_model.owner_ids, ['owner_id'])
+        self.assertEqual(saved_model.translator_ids, ['translator_id'])
+        self.assertEqual(saved_model.viewer_ids, ['viewer_id'])
+
+
+class ExplorationCommitLogEntryModelUnitTest(test_utils.GenericTestBase):
+    """Test the ExplorationCommitLogEntryModel class."""
+
+    def test_get_all_non_private_commits(self):
+        private_commit = (
+            exploration_models.ExplorationCommitLogEntryModel.create(
+                'a', 1, 'commiter_id', 'username', 'msg',
+                'create', [{}],
+                constants.ACTIVITY_STATUS_PRIVATE, False))
+        public_commit = (
+            exploration_models.ExplorationCommitLogEntryModel.create(
+                'b', 1, 'commiter_id', 'username', 'msg',
+                'create', [{}],
+                constants.ACTIVITY_STATUS_PUBLIC, False))
+        private_commit.exploration_id = 'a'
+        public_commit.exploration_id = 'b'
+        private_commit.put()
+        public_commit.put()
+        results, _, more = (
+            exploration_models.ExplorationCommitLogEntryModel
+            .get_all_non_private_commits(2, None, None))
+        self.assertFalse(more)
+        self.assertEquals(len(results), 1)
+
+
+class ExpSummaryModelUnitTest(test_utils.GenericTestBase):
+    """Tests for the ExpSummaryModel."""
+
+    def test_get_non_private(self):
+        public_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id0',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PUBLIC,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        public_exploration_summary_model.put()
+
+        private_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id1',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PRIVATE,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        private_exploration_summary_model.put()
+        exploration_summary_models = (
+            exploration_models.ExpSummaryModel.get_non_private())
+        self.assertEqual(
+            exploration_summary_models,
+            [public_exploration_summary_model])
+
+    def test_get_top_rated(self):
+        good_rating_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id0',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PUBLIC,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        good_rating_exploration_summary_model.scaled_average_rating = 100
+        good_rating_exploration_summary_model.put()
+
+        bad_rating_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id1',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PUBLIC,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        bad_rating_exploration_summary_model.scaled_average_rating = 0
+        bad_rating_exploration_summary_model.put()
+
+        self.assertEqual(
+            exploration_models.ExpSummaryModel.get_top_rated(1),
+            [good_rating_exploration_summary_model])
+        self.assertEqual(
+            exploration_models.ExpSummaryModel.get_top_rated(2),
+            [good_rating_exploration_summary_model,
+             bad_rating_exploration_summary_model])
+        self.assertEqual(
+            exploration_models.ExpSummaryModel.get_top_rated(3),
+            [good_rating_exploration_summary_model,
+             bad_rating_exploration_summary_model])
+
+        # Test that private summaries should be ignored.
+        good_rating_exploration_summary_model.status = (
+            constants.ACTIVITY_STATUS_PRIVATE)
+        good_rating_exploration_summary_model.put()
+        self.assertEqual(
+            exploration_models.ExpSummaryModel.get_top_rated(2),
+            [bad_rating_exploration_summary_model])
+
+    def test_get_private_at_least_viewable(self):
+        viewable_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id0',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PRIVATE,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['a'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        viewable_exploration_summary_model.put()
+
+        unviewable_exploration_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id1',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PRIVATE,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        unviewable_exploration_summary_model.put()
+        exploration_summary_models = (
+            exploration_models.ExpSummaryModel
+            .get_private_at_least_viewable('a'))
+        self.assertEqual(1, len(exploration_summary_models))
+        self.assertEqual('id0', exploration_summary_models[0].id)
+
+    def test_get_at_least_editable(self):
+        editable_collection_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id0',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PRIVATE,
+                community_owned=False,
+                owner_ids=['a'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        editable_collection_summary_model.put()
+
+        uneditable_collection_summary_model = (
+            exploration_models.ExpSummaryModel(
+                id='id1',
+                title='title',
+                category='category',
+                objective='objective',
+                language_code='language_code',
+                tags=['tag'],
+                status=constants.ACTIVITY_STATUS_PRIVATE,
+                community_owned=False,
+                owner_ids=['owner_id'],
+                editor_ids=['editor_id'],
+                viewer_ids=['viewer_id'],
+                contributor_ids=[''],
+                contributors_summary={},
+                version=0,
+                exploration_model_last_updated=None,
+                exploration_model_created_on=None,
+            ))
+        uneditable_collection_summary_model.put()
+
+        exploration_summary_models = (
+            exploration_models.ExpSummaryModel
+            .get_at_least_editable('a'))
+        self.assertEqual(1, len(exploration_summary_models))
+        self.assertEqual('id0', exploration_summary_models[0].id)
+
+        exploration_summary_models = (
+            exploration_models.ExpSummaryModel
+            .get_at_least_editable('viewer_id'))
+        self.assertEqual(0, len(exploration_summary_models))
+
+        exploration_summary_models = (
+            exploration_models.ExpSummaryModel
+            .get_at_least_editable('nonexistent_id'))
+        self.assertEqual(0, len(exploration_summary_models))