import json from datetime import date, datetime, timedelta from io import StringIO from django.contrib.auth.models import Permission from django.core import management from django.test import TestCase from django.urls import reverse from wagtail.admin.admin_url_finder import AdminURLFinder from wagtail.contrib.search_promotions.models import ( Query, QueryDailyHits, SearchPromotion, ) from wagtail.contrib.search_promotions.templatetags.wagtailsearchpromotions_tags import ( get_search_promotions, ) from wagtail.test.utils import WagtailTestUtils class TestSearchPromotions(TestCase): def test_search_pick_create(self): # Create a search pick to the root page SearchPromotion.objects.create( query=Query.get("root page"), page_id=1, sort_order=0, description="First search promotion", ) # Check self.assertEqual(Query.get("root page").editors_picks.count(), 1) self.assertEqual(Query.get("root page").editors_picks.first().page_id, 1) def test_search_pick_link_create(self): # Add 3 search picks in a different order to their sort_order values # They should be ordered by their sort order values and not their insertion order SearchPromotion.objects.create( query=Query.get("root page"), external_link_url="https://wagtail.org", sort_order=0, description="First search promotion", ) # Check self.assertEqual(Query.get("root page").editors_picks.count(), 1) self.assertEqual( Query.get("root page").editors_picks.first().external_link_url, "https://wagtail.org", ) def test_search_pick_ordering(self): SearchPromotion.objects.create( query=Query.get("root page"), page_id=1, sort_order=0, description="First search pick", ) SearchPromotion.objects.create( query=Query.get("root page"), page_id=1, sort_order=2, description="Last search pick", ) SearchPromotion.objects.create( query=Query.get("root page"), external_link_url="https://wagtail.org", sort_order=1, description="Middle search pick", ) # Check self.assertEqual(Query.get("root page").editors_picks.count(), 3) self.assertEqual( Query.get("root page").editors_picks.first().description, "First search pick", ) self.assertEqual( Query.get("root page").editors_picks.last().description, "Last search pick" ) def test_get_most_popular(self): popularQuery = Query.get("popular") for i in range(5): popularQuery.add_hit() SearchPromotion.objects.create( query=Query.get("popular"), page_id=2, sort_order=0, description="Popular search pick", ) SearchPromotion.objects.create( query=Query.get("uninteresting"), page_id=1, sort_order=2, description="Uninteresting search pick", ) # Check self.assertEqual(Query.get_most_popular().count(), 1) popular_picks = Query.get("popular").editors_picks.first() self.assertEqual( popular_picks.description, "Popular search pick", ) self.assertEqual(popular_picks.query.hits, 5) def test_get_most_popular_since(self): TODAY = date.today() TWO_DAYS_AGO = TODAY - timedelta(days=2) FIVE_DAYS_AGO = TODAY - timedelta(days=5) popularQuery = Query.get("popular") for i in range(5): popularQuery.add_hit(date=FIVE_DAYS_AGO) surpriseQuery = Query.get("surprise") surpriseQuery.add_hit(date=TODAY) surpriseQuery.add_hit(date=TWO_DAYS_AGO) surpriseQuery.add_hit(date=FIVE_DAYS_AGO) SearchPromotion.objects.create( query=Query.get("popular"), page_id=2, sort_order=0, description="Popular search pick", ) SearchPromotion.objects.create( query=Query.get("surprise"), page_id=2, sort_order=2, description="Surprising search pick", ) # Check most_popular_queries = Query.get_most_popular(date_since=TWO_DAYS_AGO) self.assertEqual(most_popular_queries.count(), 1) editors_picks = Query.get("surprise").editors_picks surprise_picks = editors_picks.first() self.assertEqual( surprise_picks.description, "Surprising search pick", ) self.assertEqual(surprise_picks.query.hits, 3) class TestGetSearchPromotionsTemplateTag(TestCase): def test_get_search_promotions_template_tag(self): # Create a search pick to the root page pick = SearchPromotion.objects.create( query=Query.get("root page"), page_id=1, sort_order=0, description="First search pick", ) # Create another search pick against a different query SearchPromotion.objects.create( query=Query.get("root page again"), page_id=1, sort_order=0, description="Second search pick", ) # Check search_picks = list(get_search_promotions("root page")) self.assertEqual(search_picks, [pick]) def test_get_search_promotions_with_none_query_string(self): search_picks = list(get_search_promotions(None)) self.assertEqual(search_picks, []) class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase): def setUp(self): self.user = self.login() def test_simple(self): response = self.client.get(reverse("wagtailsearchpromotions:index")) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html") def test_search(self): response = self.client.get( reverse("wagtailsearchpromotions:index"), {"q": "Hello"} ) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["query_string"], "Hello") self.assertContains( response, 'Sorry, no promoted results match "Hello"', ) def test_search_with_results(self): SearchPromotion.objects.create( query=Query.get("search promotion query"), page_id=1, ) SearchPromotion.objects.create( query=Query.get("search promotion query"), external_link_url="https://wagtail.org", ) response = self.client.get( reverse("wagtailsearchpromotions:index"), {"q": "search promotion query"} ) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["query_string"], "search promotion query") self.assertContains(response, '') self.assertContains( response, '', ) def make_search_picks(self): for i in range(50): SearchPromotion.objects.create( query=Query.get("query " + str(i)), page_id=1, sort_order=0, description="First search pick", ) def test_pagination(self): self.make_search_picks() response = self.client.get(reverse("wagtailsearchpromotions:index"), {"p": 2}) # Check response self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html") # Check that we got the correct page self.assertEqual(response.context["page_obj"].number, 2) def test_pagination_invalid(self): self.make_search_picks() response = self.client.get( reverse("wagtailsearchpromotions:index"), {"p": "Hello World!"} ) # Check response self.assertEqual(response.status_code, 404) def test_pagination_out_of_range(self): self.make_search_picks() response = self.client.get( reverse("wagtailsearchpromotions:index"), {"p": 99999} ) # Check response self.assertEqual(response.status_code, 404) def test_num_queries(self): url = reverse("wagtailsearchpromotions:index") self.make_search_picks() # Warm up the cache self.client.get(url) # Number of queries with the current number of search picks with self.assertNumQueries(11): self.client.get(url) # Add more SearchPromotions and QueryDailyHits to some of the queries today = date.today() for i in range(20): query = Query.get("query " + str(i)) promos = [ SearchPromotion( query=query, page_id=j % 2 + 1, sort_order=j, description=f"Search pick {j}", ) for j in range(5) ] hits = [ QueryDailyHits(query=query, date=today - timedelta(days=j), hits=j) for j in range(5) ] SearchPromotion.objects.bulk_create(promos) QueryDailyHits.objects.bulk_create(hits) # Number of queries after the addition of more search picks and hits # should remain the same (no N+1 queries) with self.assertNumQueries(11): self.client.get(url) def test_results_are_ordered_alphabetically(self): self.make_search_picks() SearchPromotion.objects.create( query=Query.get("aaargh snake"), page_id=1, sort_order=0, description="ooh, it's a snake", ) # Add another one to make sure it's not ordered descending by pk SearchPromotion.objects.create( query=Query.get("beloved snake"), page_id=1, sort_order=0, description="beloved snake goes ssSSSS", ) response = self.client.get(reverse("wagtailsearchpromotions:index")) # Check response self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html") # "aargh snake" should be the first result alphabetically self.assertEqual(response.context["queries"][0].query_string, "aaargh snake") self.assertEqual(response.context["queries"][1].query_string, "beloved snake") def test_multiple_searchpromotions(self): today = date.today() for i in range(10): Query.get("root page").add_hit(date=today - timedelta(days=i)) SearchPromotion.objects.create( query=Query.get("root page"), page_id=1, sort_order=0, description="First search pick", ) SearchPromotion.objects.create( query=Query.get("root page"), page_id=2, sort_order=0, description="Second search pick", ) response = self.client.get(reverse("wagtailsearchpromotions:index")) self.assertContains(response, "10", html=True) self.assertEqual(Query.get("root page").hits, 10) soup = self.get_soup(response.content) root_page_edit_url = reverse("wagtailadmin_pages:edit", args=(1,)) homepage_edit_url = reverse("wagtailadmin_pages:edit", args=(2,)) root_page_edit_link = soup.select_one(f'a[href="{root_page_edit_url}"]') homepage_edit_link = soup.select_one(f'a[href="{homepage_edit_url}"]') self.assertIsNotNone(root_page_edit_link) self.assertIsNotNone(homepage_edit_link) self.assertEqual(Query.get("root page").editors_picks.count(), 2) def test_results_ordering(self): self.make_search_picks() url = reverse("wagtailsearchpromotions:index") SearchPromotion.objects.create( query=Query.get("zyzzyvas"), page_id=1, sort_order=0, description="no definition found, this is a valid scrabble word though", ) SearchPromotion.objects.create( query=Query.get("aardwolf"), page_id=1, sort_order=22, description="Striped hyena of southeast Africa that feeds chiefly on insects", ) popularQuery = Query.get("optimal") for i in range(50): popularQuery.add_hit() SearchPromotion.objects.create( query=popularQuery, page_id=1, sort_order=15, description="An oddly popular search term?", ) popularQuery = Query.get("suboptimal") for i in range(25): popularQuery.add_hit() SearchPromotion.objects.create( query=popularQuery, page_id=1, sort_order=15, description="Not as popular", ) # ordered by querystring (reversed) response = self.client.get(url + "?ordering=-query_string") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["page_obj"][0].query_string, "zyzzyvas") # last page, still ordered by query string (reversed) response = self.client.get(url + "?ordering=-query_string&p=3") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["page_obj"][-1].query_string, "aardwolf") # ordered by querystring (not reversed) response = self.client.get(url + "?ordering=query_string") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["page_obj"][0].query_string, "aardwolf") # ordered by sum of daily hits (reversed) response = self.client.get(url + "?ordering=-views") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["page_obj"][0].query_string, "optimal") self.assertEqual(response.context["page_obj"][1].query_string, "suboptimal") # ordered by sum of daily hits, last page (not reversed) response = self.client.get(url + "?ordering=views&p=3") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["page_obj"][-1].query_string, "optimal") self.assertEqual(response.context["page_obj"][-2].query_string, "suboptimal") def test_get_with_no_permission(self): self.user.is_superuser = False self.user.save() # Only basic access_admin permission is given self.user.user_permissions.add( Permission.objects.get( content_type__app_label="wagtailadmin", codename="access_admin", ) ) response = self.client.get(reverse("wagtailsearchpromotions:index")) self.assertEqual(response.status_code, 302) self.assertRedirects(response, reverse("wagtailadmin_home")) def test_get_with_edit_permission_only(self): self.user.is_superuser = False self.user.save() # Only basic access_admin permission is given self.user.user_permissions.add( Permission.objects.get( content_type__app_label="wagtailadmin", codename="access_admin", ), Permission.objects.get( content_type__app_label="wagtailsearchpromotions", codename="change_searchpromotion", ), ) response = self.client.get(reverse("wagtailsearchpromotions:index")) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html") soup = self.get_soup(response.content) add_url = reverse("wagtailsearchpromotions:add") # Should not render add link self.assertIsNone(soup.select_one(f'a[href="{add_url}"]')) class TestSearchPromotionsAddView(WagtailTestUtils, TestCase): def setUp(self): self.login() def test_simple(self): response = self.client.get(reverse("wagtailsearchpromotions:add")) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/add.html") def test_post(self): # Submit post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 1, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-page": 1, "editors_picks-0-description": "Hello", } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that the search pick was created self.assertTrue(Query.get("test").editors_picks.filter(page_id=1).exists()) def test_post_with_external_link(self): # Submit post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 1, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-external_link_url": "https://wagtail.org", "editors_picks-0-external_link_text": "Wagtail", "editors_picks-0-description": "Hello", } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that the search pick was created self.assertTrue( Query.get("test") .editors_picks.filter(external_link_url="https://wagtail.org") .exists() ) def test_post_without_recommendations(self): # Submit post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 0, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be given an error self.assertEqual(response.status_code, 200) self.assertFormSetError( response.context["searchpicks_formset"], None, None, "Please specify at least one recommendation for this search term.", ) def test_post_with_page_and_external_link(self): post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 1, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-page": 1, "editors_picks-0-external_link_url": "https://wagtail.org", "editors_picks-0-external_link_text": "Wagtail", "editors_picks-0-description": "Hello", } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be given an error self.assertEqual(response.status_code, 200) self.assertFormSetError( response.context["searchpicks_formset"], None, None, "Please only select a page OR enter an external link.", ) def test_post_missing_recommendation(self): post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 1, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-description": "Hello", } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be given an error self.assertEqual(response.status_code, 200) self.assertFormSetError( response.context["searchpicks_formset"], None, None, "You must recommend a page OR an external link.", ) def test_post_missing_external_text(self): post_data = { "query_string": "test", "editors_picks-TOTAL_FORMS": 1, "editors_picks-INITIAL_FORMS": 0, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-external_link_url": "https://wagtail.org", } response = self.client.post(reverse("wagtailsearchpromotions:add"), post_data) # User should be given an error self.assertEqual(response.status_code, 200) self.assertFormSetError( response.context["searchpicks_formset"], None, None, "You must enter an external link text if you enter an external link URL.", ) class TestSearchPromotionsEditView(WagtailTestUtils, TestCase): def setUp(self): self.user = self.login() # Create a search pick to edit self.query = Query.get("Hello") self.search_pick = self.query.editors_picks.create( page_id=1, sort_order=0, description="Root page" ) self.search_pick_2 = self.query.editors_picks.create( page_id=2, sort_order=1, description="Homepage" ) def test_simple(self): response = self.client.get( reverse("wagtailsearchpromotions:edit", args=(self.query.id,)) ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/edit.html") url_finder = AdminURLFinder(self.user) expected_url = "/admin/searchpicks/%d/" % self.query.id self.assertEqual(url_finder.get_edit_url(self.search_pick), expected_url) def test_post(self): # Submit post_data = { "query_string": "Hello", "editors_picks-TOTAL_FORMS": 2, "editors_picks-INITIAL_FORMS": 2, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-id": self.search_pick.id, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-page": 1, "editors_picks-0-description": "Description has changed", # Change "editors_picks-1-id": self.search_pick_2.id, "editors_picks-1-DELETE": "", "editors_picks-1-ORDER": 1, "editors_picks-1-page": 2, "editors_picks-1-description": "Homepage", } response = self.client.post( reverse("wagtailsearchpromotions:edit", args=(self.query.id,)), post_data ) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that the search pick description was edited self.assertEqual( SearchPromotion.objects.get(id=self.search_pick.id).description, "Description has changed", ) def test_post_reorder(self): # Check order before reordering self.assertEqual(Query.get("Hello").editors_picks.all()[0], self.search_pick) self.assertEqual(Query.get("Hello").editors_picks.all()[1], self.search_pick_2) # Submit post_data = { "query_string": "Hello", "editors_picks-TOTAL_FORMS": 2, "editors_picks-INITIAL_FORMS": 2, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-id": self.search_pick.id, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 1, # Change "editors_picks-0-page": 1, "editors_picks-0-description": "Root page", "editors_picks-1-id": self.search_pick_2.id, "editors_picks-1-DELETE": "", "editors_picks-1-ORDER": 0, # Change "editors_picks-1-page": 2, "editors_picks-1-description": "Homepage", } response = self.client.post( reverse("wagtailsearchpromotions:edit", args=(self.query.id,)), post_data ) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that the ordering has been saved correctly self.assertEqual( SearchPromotion.objects.get(id=self.search_pick.id).sort_order, 1 ) self.assertEqual( SearchPromotion.objects.get(id=self.search_pick_2.id).sort_order, 0 ) # Check that the recommendations were reordered self.assertEqual(Query.get("Hello").editors_picks.all()[0], self.search_pick_2) self.assertEqual(Query.get("Hello").editors_picks.all()[1], self.search_pick) def test_post_delete_recommendation(self): # Submit post_data = { "query_string": "Hello", "editors_picks-TOTAL_FORMS": 2, "editors_picks-INITIAL_FORMS": 2, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-id": self.search_pick.id, "editors_picks-0-DELETE": "", "editors_picks-0-ORDER": 0, "editors_picks-0-page": 1, "editors_picks-0-description": "Root page", "editors_picks-1-id": self.search_pick_2.id, "editors_picks-1-DELETE": 1, "editors_picks-1-ORDER": 1, "editors_picks-1-page": 2, "editors_picks-1-description": "Homepage", } response = self.client.post( reverse("wagtailsearchpromotions:edit", args=(self.query.id,)), post_data ) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that the recommendation was deleted self.assertFalse( SearchPromotion.objects.filter(id=self.search_pick_2.id).exists() ) # The other recommendation should still exist self.assertTrue(SearchPromotion.objects.filter(id=self.search_pick.id).exists()) def test_post_without_recommendations(self): # Submit post_data = { "query_string": "Hello", "editors_picks-TOTAL_FORMS": 2, "editors_picks-INITIAL_FORMS": 2, "editors_picks-MAX_NUM_FORMS": 1000, "editors_picks-0-id": self.search_pick.id, "editors_picks-0-DELETE": 1, "editors_picks-0-ORDER": 0, "editors_picks-0-page": 1, "editors_picks-0-description": "Description has changed", # Change "editors_picks-1-id": self.search_pick_2.id, "editors_picks-1-DELETE": 1, "editors_picks-1-ORDER": 1, "editors_picks-1-page": 2, "editors_picks-1-description": "Homepage", } response = self.client.post( reverse("wagtailsearchpromotions:edit", args=(self.query.id,)), post_data ) # User should be given an error self.assertEqual(response.status_code, 200) self.assertFormSetError( response.context["searchpicks_formset"], None, None, "Please specify at least one recommendation for this search term.", ) class TestSearchPromotionsDeleteView(WagtailTestUtils, TestCase): def setUp(self): self.login() # Create a search pick to delete self.query = Query.get("Hello") self.search_pick = self.query.editors_picks.create( page_id=1, description="Root page" ) self.search_pick_2 = self.query.editors_picks.create( page_id=2, description="Homepage" ) def test_simple(self): response = self.client.get( reverse("wagtailsearchpromotions:delete", args=(self.query.id,)) ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "wagtailsearchpromotions/confirm_delete.html") def test_post(self): # Submit response = self.client.post( reverse("wagtailsearchpromotions:delete", args=(self.query.id,)) ) # User should be redirected back to the index self.assertRedirects(response, reverse("wagtailsearchpromotions:index")) # Check that both recommendations were deleted self.assertFalse( SearchPromotion.objects.filter(id=self.search_pick_2.id).exists() ) # The other recommendation should still exist self.assertFalse( SearchPromotion.objects.filter(id=self.search_pick.id).exists() ) class TestGarbageCollectManagementCommand(TestCase): def test_garbage_collect_command(self): nowdt = datetime.now() old_hit_date = (nowdt - timedelta(days=14)).date() recent_hit_date = (nowdt - timedelta(days=1)).date() # Add 10 hits that are more than one week old. The related queries and the daily hits # should be deleted by the search_garbage_collect command. query_ids_to_be_deleted = [] for i in range(10): q = Query.get(f"Hello {i}") q.add_hit(date=old_hit_date) query_ids_to_be_deleted.append(q.id) # Add 10 hits that are less than one week old. These ones should not be deleted. recent_query_ids = [] for i in range(10): q = Query.get(f"World {i}") q.add_hit(date=recent_hit_date) recent_query_ids.append(q.id) # Add 10 queries that are promoted. These ones should not be deleted. promoted_query_ids = [] for i in range(10): q = Query.get(f"Foo bar {i}") q.add_hit(date=old_hit_date) SearchPromotion.objects.create( query=q, page_id=1, sort_order=0, description="Test" ) promoted_query_ids.append(q.id) management.call_command("searchpromotions_garbage_collect", stdout=StringIO()) self.assertFalse(Query.objects.filter(id__in=query_ids_to_be_deleted).exists()) self.assertFalse( QueryDailyHits.objects.filter( date=old_hit_date, query_id__in=query_ids_to_be_deleted ).exists() ) self.assertEqual(Query.objects.filter(id__in=recent_query_ids).count(), 10) self.assertEqual( QueryDailyHits.objects.filter( date=recent_hit_date, query_id__in=recent_query_ids ).count(), 10, ) self.assertEqual(Query.objects.filter(id__in=promoted_query_ids).count(), 10) self.assertEqual( QueryDailyHits.objects.filter( date=recent_hit_date, query_id__in=promoted_query_ids ).count(), 0, ) class TestQueryChooserView(WagtailTestUtils, TestCase): def setUp(self): self.login() def get(self, params={}): return self.client.get("/admin/searchpicks/queries/chooser/", params) def test_simple(self): response = self.get() self.assertEqual(response.status_code, 200) self.assertTemplateUsed( response, "wagtailsearchpromotions/queries/chooser/chooser.html" ) response_json = json.loads(response.content.decode()) self.assertEqual(response_json["step"], "chooser") def test_search(self): response = self.get({"q": "Hello"}) self.assertEqual(response.status_code, 200) def test_pagination(self): # page numbers in range should be accepted response = self.get({"p": 1}) self.assertEqual(response.status_code, 200) # page numbers out of range should return 404 response = self.get({"p": 9999}) self.assertEqual(response.status_code, 404) class TestHitCounter(TestCase): def test_no_hits(self): self.assertEqual(Query.get("Hello").hits, 0) def test_hit(self): # Add a hit Query.get("Hello").add_hit() # Test self.assertEqual(Query.get("Hello").hits, 1) def test_10_hits(self): # Add 10 hits for i in range(10): Query.get("Hello").add_hit() # Test self.assertEqual(Query.get("Hello").hits, 10) class TestQueryStringNormalisation(TestCase): def setUp(self): self.query = Query.get(" Hello World! ") def test_normalisation(self): self.assertEqual(str(self.query), "hello world!") def test_equivalent_queries(self): queries = [ " Hello World!", "Hello World! ", "hello world!", " Hello world! ", ] for query in queries: self.assertEqual(self.query, Query.get(query)) def test_different_queries(self): queries = [ "HelloWorld", "HelloWorld!" " Hello World! ", "Hello", ] for query in queries: self.assertNotEqual(self.query, Query.get(query)) class TestQueryPopularity(TestCase): def test_query_popularity(self): # Add 3 hits to unpopular query for i in range(3): Query.get("unpopular query").add_hit() # Add 10 hits to popular query for i in range(10): Query.get("popular query").add_hit() # Get most popular queries popular_queries = Query.get_most_popular() # Check list self.assertEqual(popular_queries.count(), 2) self.assertEqual(popular_queries[0], Query.get("popular query")) self.assertEqual(popular_queries[1], Query.get("unpopular query")) # Add 5 hits to little popular query for i in range(5): Query.get("little popular query").add_hit() # Check list again, little popular query should be in the middle self.assertEqual(popular_queries.count(), 3) self.assertEqual(popular_queries[0], Query.get("popular query")) self.assertEqual(popular_queries[1], Query.get("little popular query")) self.assertEqual(popular_queries[2], Query.get("unpopular query")) # Unpopular query goes viral! for i in range(20): Query.get("unpopular query").add_hit() # Unpopular query should be most popular now self.assertEqual(popular_queries.count(), 3) self.assertEqual(popular_queries[0], Query.get("unpopular query")) self.assertEqual(popular_queries[1], Query.get("popular query")) self.assertEqual(popular_queries[2], Query.get("little popular query"))