"""
Tests for django-watson.

Fun fact: The MySQL full text search engine does not support indexing of words
that are 3 letters or fewer. Thus, the standard metasyntactic variables in
these tests have been amended to 'fooo' and 'baar'. Ho hum.
"""

import os
from unittest import skipUnless

from django.db import models
from django.test import TestCase
from django.core.management import call_command
from django.conf.urls.defaults import *
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.models import User
from django.http import HttpResponseNotFound, HttpResponseServerError
from django import template
from django.utils import simplejson as json

import watson
from watson.registration import RegistrationError, get_backend, SearchEngine
from watson.models import SearchEntry


class TestModelBase(models.Model):

    title = models.CharField(
        max_length = 200,
    )
    
    content = models.TextField(
        blank = True,
    )
    
    description = models.TextField(
        blank = True,
    )
    
    is_published = models.BooleanField(
        default = True,
    )
    
    def __unicode__(self):
        return self.title

    class Meta:
        abstract = True
        app_label = "auth"  # Hack: Cannot use an app_label that is under South control, due to http://south.aeracode.org/ticket/520
        
        
class WatsonTestModel1(TestModelBase):

    pass


str_pk_gen = 0;

def get_str_pk():
    global str_pk_gen
    str_pk_gen += 1;
    return str(str_pk_gen)
    
    
class WatsonTestModel2(TestModelBase):

    id = models.CharField(
        primary_key = True,
        max_length = 100,
        default = get_str_pk
    )


class RegistrationTest(TestCase):
    
    def testRegistration(self):
        # Register the model and test.
        watson.register(WatsonTestModel1)
        self.assertTrue(watson.is_registered(WatsonTestModel1))
        self.assertRaises(RegistrationError, lambda: watson.register(WatsonTestModel1))
        self.assertTrue(WatsonTestModel1 in watson.get_registered_models())
        self.assertTrue(isinstance(watson.get_adapter(WatsonTestModel1), watson.SearchAdapter))
        # Unregister the model and text.
        watson.unregister(WatsonTestModel1)
        self.assertFalse(watson.is_registered(WatsonTestModel1))
        self.assertRaises(RegistrationError, lambda: watson.unregister(WatsonTestModel1))
        self.assertTrue(WatsonTestModel1 not in watson.get_registered_models())
        self.assertRaises(RegistrationError, lambda: isinstance(watson.get_adapter(WatsonTestModel1)))


complex_registration_search_engine = SearchEngine("restricted")


class InstallUninstallTestBase(TestCase):

    def testUninstallAndInstall(self):
        # Not too much to test here, as some backends don't require installation.
        # Just make sure the commands don't error.
        call_command("uninstallwatson", verbosity=0)
        call_command("installwatson", verbosity=0)
        
    @skipUnless(get_backend().requires_installation, "search backend does not require installation")
    def testRealInstallAndUninstall(self):
        backend = get_backend()
        call_command("uninstallwatson", verbosity=0)
        self.assertFalse(backend.is_installed())
        call_command("installwatson", verbosity=0)
        self.assertTrue(backend.is_installed())


class SearchTestBase(TestCase):

    model1 = WatsonTestModel1
    
    model2 = WatsonTestModel2

    def setUp(self):
        # If migrations are off, then this is needed to get the indices installed. It has to
        # be called in the setUp() method, but multiple invocations should be safe.
        call_command("installwatson", verbosity=0)
        # Remove all the current registered models.
        self.registered_models = watson.get_registered_models()
        for model in self.registered_models:
            watson.unregister(model)
        # Register the test models.
        watson.register(self.model1)
        watson.register(self.model2, exclude=("id",))
        complex_registration_search_engine.register(WatsonTestModel1, exclude=("content", "description",), store=("is_published",))
        complex_registration_search_engine.register(WatsonTestModel2, fields=("title",))
        # Create some test models.
        self.test11 = WatsonTestModel1.objects.create(
            title = "title model1 instance11",
            content = "content model1 instance11",
            description = "description model1 instance11",
        )
        self.test12 = WatsonTestModel1.objects.create(
            title = "title model1 instance12",
            content = "content model1 instance12",
            description = "description model1 instance12",
        )
        self.test21 = WatsonTestModel2.objects.create(
            title = "title model2 instance21",
            content = "content model2 instance21",
            description = "description model2 instance21",
        )
        self.test22 = WatsonTestModel2.objects.create(
            title = "title model2 instance22",
            content = "content model2 instance22",
            description = "description model2 instance22",
        )

    def tearDown(self):
        # Re-register the old registered models.
        for model in self.registered_models:
            watson.register(model)
        # Unregister the test models.
        watson.unregister(self.model1)
        watson.unregister(self.model2)
        complex_registration_search_engine.unregister(WatsonTestModel1)
        complex_registration_search_engine.unregister(WatsonTestModel2)
        # Delete the test models.
        WatsonTestModel1.objects.all().delete()
        WatsonTestModel2.objects.all().delete()
        del self.test11
        del self.test12
        del self.test21
        del self.test22
        # Delete the search index.
        SearchEntry.objects.all().delete()


class InternalsTest(SearchTestBase):

    def testSearchEntriesCreated(self):
        self.assertEqual(SearchEntry.objects.filter(engine_slug="default").count(), 4)
        
    def testBuildWatsonCommand(self):
        # Hack a change into the model using a bulk update, which doesn't send signals.
        WatsonTestModel1.objects.filter(id=self.test11.id).update(title="fooo")
        # Test that no update has happened.
        self.assertEqual(watson.search("fooo").count(), 0)
        # Run the rebuild command.
        call_command("buildwatson", verbosity=0)
        # Test that the update is now applies.
        self.assertEqual(watson.search("fooo").count(), 1)
        
    def testUpdateSearchIndex(self):
        # Update a model and make sure that the search results match.
        self.test11.title = "fooo"
        self.test11.save()
        # Test a search that should get one model.
        exact_search = watson.search("fooo")
        self.assertEqual(len(exact_search), 1)
        self.assertEqual(exact_search[0].title, "fooo")
        # Delete a model and make sure that the search results match.
        self.test11.delete()
        self.assertEqual(watson.search("fooo").count(), 0)
    
    def testSearchIndexUpdateDeferredByContext(self):
        with watson.update_index():
            self.test11.title = "fooo"
            self.test11.save()
            self.assertEqual(watson.search("fooo").count(), 0)
        self.assertEqual(watson.search("fooo").count(), 1)
    
    def testSearchIndexUpdateAbandonedOnError(self):
        try:
            with watson.update_index():
                self.test11.title = "fooo"
                self.test11.save()
                raise Exception("Foo")
        except:
            pass
        # Test a search that should get not model.
        self.assertEqual(watson.search("fooo").count(), 0)
        
    def testFixesDuplicateSearchEntries(self):
        search_entries = SearchEntry.objects.filter(engine_slug="default")
        # Duplicate a couple of search entries.
        for search_entry in search_entries.all()[:2]:
            search_entry.id = None
            search_entry.save()
        # Make sure that we have six (including duplicates).
        self.assertEqual(search_entries.all().count(), 6)
        # Run the rebuild command.
        call_command("buildwatson", verbosity=0)
        # Make sure that we have four again (including duplicates).
        self.assertEqual(search_entries.all().count(), 4)
    
    def testEmptyFilterGivesAllResults(self):
        for model in (WatsonTestModel1, WatsonTestModel2):
            self.assertEqual(watson.filter(model, "").count(), 2)
            self.assertEqual(watson.filter(model, " ").count(), 2)
        
    def testFilter(self):
        for model in (WatsonTestModel1, WatsonTestModel2):
            # Test can find all.
            self.assertEqual(watson.filter(model, "TITLE").count(), 2)
        # Test can find a specific one.
        obj = watson.filter(WatsonTestModel1, "INSTANCE12").get()
        self.assertTrue(isinstance(obj, WatsonTestModel1))
        self.assertEqual(obj.title, "title model1 instance12")
        # Test can do filter on a queryset.
        obj = watson.filter(WatsonTestModel1.objects.filter(title__icontains="TITLE"), "INSTANCE12").get()
        self.assertTrue(isinstance(obj, WatsonTestModel1))
        self.assertEqual(obj.title, "title model1 instance12")
    
    @skipUnless(get_backend().supports_prefix_matching, "Search backend does not support prefix matching.")    
    def testPrefixFilter(self):
        self.assertEqual(watson.filter(WatsonTestModel1, "INSTAN").count(), 2)
        
        
class SearchTest(SearchTestBase):
    
    def emptySearchTextGivesNoResults(self):
        self.assertEqual(watson.search("").count(), 0)
        self.assertEqual(watson.search(" ").count(), 0)        
    
    def testMultiTableSearch(self):
        # Test a search that should get all models.
        self.assertEqual(watson.search("TITLE").count(), 4)
        self.assertEqual(watson.search("CONTENT").count(), 4)
        self.assertEqual(watson.search("DESCRIPTION").count(), 4)
        self.assertEqual(watson.search("TITLE CONTENT DESCRIPTION").count(), 4)
        # Test a search that should get two models.
        self.assertEqual(watson.search("MODEL1").count(), 2)
        self.assertEqual(watson.search("MODEL2").count(), 2)
        self.assertEqual(watson.search("TITLE MODEL1").count(), 2)
        self.assertEqual(watson.search("TITLE MODEL2").count(), 2)
        # Test a search that should get one model.
        self.assertEqual(watson.search("INSTANCE11").count(), 1)
        self.assertEqual(watson.search("INSTANCE21").count(), 1)
        self.assertEqual(watson.search("TITLE INSTANCE11").count(), 1)
        self.assertEqual(watson.search("TITLE INSTANCE21").count(), 1)
        # Test a search that should get zero models.
        self.assertEqual(watson.search("FOOO").count(), 0)
        self.assertEqual(watson.search("FOOO INSTANCE11").count(), 0)
        self.assertEqual(watson.search("MODEL2 INSTANCE11").count(), 0)
        
    @skipUnless(get_backend().supports_prefix_matching, "Search backend does not support prefix matching.")
    def testMultiTablePrefixSearch(self):
        self.assertEqual(watson.search("DESCR").count(), 4)
    
    def testLimitedModelList(self):
        # Test a search that should get all models.
        self.assertEqual(watson.search("TITLE", models=(WatsonTestModel1, WatsonTestModel2)).count(), 4)
        # Test a search that should get two models.
        self.assertEqual(watson.search("MODEL1", models=(WatsonTestModel1, WatsonTestModel2)).count(), 2)
        self.assertEqual(watson.search("MODEL1", models=(WatsonTestModel1,)).count(), 2)
        self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel1, WatsonTestModel2)).count(), 2)
        self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel2,)).count(), 2)
        # Test a search that should get one model.
        self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1, WatsonTestModel2)).count(), 1)
        self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1,)).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel1, WatsonTestModel2,)).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2,)).count(), 1)
        # Test a search that should get zero models.
        self.assertEqual(watson.search("MODEL1", models=(WatsonTestModel2,)).count(), 0)
        self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel1,)).count(), 0)
        self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel1,)).count(), 0)
        self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel2,)).count(), 0)
        
    def testExcludedModelList(self):
        # Test a search that should get all models.
        self.assertEqual(watson.search("TITLE", exclude=()).count(), 4)
        # Test a search that should get two models.
        self.assertEqual(watson.search("MODEL1", exclude=()).count(), 2)
        self.assertEqual(watson.search("MODEL1", exclude=(WatsonTestModel2,)).count(), 2)
        self.assertEqual(watson.search("MODEL2", exclude=()).count(), 2)
        self.assertEqual(watson.search("MODEL2", exclude=(WatsonTestModel1,)).count(), 2)
        # Test a search that should get one model.
        self.assertEqual(watson.search("INSTANCE11", exclude=()).count(), 1)
        self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel2,)).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", exclude=()).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel1,)).count(), 1)
        # Test a search that should get zero models.
        self.assertEqual(watson.search("MODEL1", exclude=(WatsonTestModel1,)).count(), 0)
        self.assertEqual(watson.search("MODEL2", exclude=(WatsonTestModel2,)).count(), 0)
        self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2,)).count(), 0)
        self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel1,)).count(), 0)

    def testLimitedModelQuerySet(self):
        # Test a search that should get all models.
        self.assertEqual(watson.search("TITLE", models=(WatsonTestModel1.objects.filter(title__icontains="TITLE"), WatsonTestModel2.objects.filter(title__icontains="TITLE"),)).count(), 4)
        # Test a search that should get two models.
        self.assertEqual(watson.search("MODEL1", models=(WatsonTestModel1.objects.filter(
            title__icontains = "MODEL1",
            description__icontains = "MODEL1",
        ),)).count(), 2)
        self.assertEqual(watson.search("MODEL2", models=(WatsonTestModel2.objects.filter(
            title__icontains = "MODEL2",
            description__icontains = "MODEL2",
        ),)).count(), 2)
        # Test a search that should get one model.
        self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1.objects.filter(
            title__icontains = "MODEL1",
        ),)).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2.objects.filter(
            title__icontains = "MODEL2",
        ),)).count(), 1)
        # Test a search that should get no models.
        self.assertEqual(watson.search("INSTANCE11", models=(WatsonTestModel1.objects.filter(
            title__icontains = "MODEL2",
        ),)).count(), 0)
        self.assertEqual(watson.search("INSTANCE21", models=(WatsonTestModel2.objects.filter(
            title__icontains = "MODEL1",
        ),)).count(), 0)
        
    def testExcludedModelQuerySet(self):
        # Test a search that should get all models.
        self.assertEqual(watson.search("TITLE", exclude=(WatsonTestModel1.objects.filter(title__icontains="FOOO"), WatsonTestModel2.objects.filter(title__icontains="FOOO"),)).count(), 4)
        # Test a search that should get two models.
        self.assertEqual(watson.search("MODEL1", exclude=(WatsonTestModel1.objects.filter(
            title__icontains = "INSTANCE21",
            description__icontains = "INSTANCE22",
        ),)).count(), 2)
        self.assertEqual(watson.search("MODEL2", exclude=(WatsonTestModel2.objects.filter(
            title__icontains = "INSTANCE11",
            description__icontains = "INSTANCE12",
        ),)).count(), 2)
        # Test a search that should get one model.
        self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel1.objects.filter(
            title__icontains = "MODEL2",
        ),)).count(), 1)
        self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2.objects.filter(
            title__icontains = "MODEL1",
        ),)).count(), 1)
        # Test a search that should get no models.
        self.assertEqual(watson.search("INSTANCE11", exclude=(WatsonTestModel1.objects.filter(
            title__icontains = "MODEL1",
        ),)).count(), 0)
        self.assertEqual(watson.search("INSTANCE21", exclude=(WatsonTestModel2.objects.filter(
            title__icontains = "MODEL2",
        ),)).count(), 0)
        
    def testKitchenSink(self):
        """For sanity, let's just test everything together in one giant search of doom!"""
        self.assertEqual(watson.search(
            "INSTANCE11",
            models = (
                WatsonTestModel1.objects.filter(title__icontains="INSTANCE11"),
                WatsonTestModel2.objects.filter(title__icontains="TITLE"),
            ),
            exclude = (
                WatsonTestModel1.objects.filter(title__icontains="MODEL2"),
                WatsonTestModel2.objects.filter(title__icontains="MODEL1"),
            )
        ).get().title, "title model1 instance11")
        
        
class LiveFilterSearchTest(SearchTest):
    
    model1 = WatsonTestModel1.objects.filter(is_published=True)
    
    model2 = WatsonTestModel2.objects.filter(is_published=True)
    
    def testUnpublishedModelsNotFound(self):
        # Make sure that there are four to find!
        self.assertEqual(watson.search("tItle Content Description").count(), 4)
        # Unpublish two objects.
        self.test11.is_published = False
        self.test11.save()
        self.test21.is_published = False
        self.test21.save()
        # This should return 4, but two of them are unpublished.
        self.assertEqual(watson.search("tItle Content Description").count(), 2)
        
    def testCanOverridePublication(self):
        # Unpublish two objects.
        self.test11.is_published = False
        self.test11.save()
        # This should still return 4, since we're overriding the publication.
        self.assertEqual(watson.search("tItle Content Description", models=(WatsonTestModel2, WatsonTestModel1._base_manager.all(),)).count(), 4)
        
        
class RankingTest(SearchTestBase):

    def setUp(self):
        super(RankingTest, self).setUp()
        self.test11.title += " fooo baar fooo"
        self.test11.save()
        self.test12.content += " fooo baar"
        self.test12.save()

    def testRankingParamPresentOnSearch(self):
        self.assertGreater(watson.search("TITLE")[0].watson_rank, 0)
        
    def testRankingParamPresentOnFilter(self):
        self.assertGreater(watson.filter(WatsonTestModel1, "TITLE")[0].watson_rank, 0)
        
    def testRankingParamAbsentOnSearch(self):
        self.assertRaises(AttributeError, lambda: watson.search("TITLE", ranking=False)[0].watson_rank)
        
    def testRankingParamAbsentOnFilter(self):
        self.assertRaises(AttributeError, lambda: watson.filter(WatsonTestModel1, "TITLE", ranking=False)[0].watson_rank)
    
    @skipUnless(get_backend().supports_ranking, "search backend does not support ranking")
    def testRankingWithSearch(self):
        self.assertEqual(
            [entry.title for entry in watson.search("FOOO")],
            [u"title model1 instance11 fooo baar fooo", u"title model1 instance12"]
        )
            
    @skipUnless(get_backend().supports_ranking, "search backend does not support ranking")
    def testRankingWithFilter(self):
        self.assertEqual(
            [entry.title for entry in watson.filter(WatsonTestModel1, "FOOO")],
            [u"title model1 instance11 fooo baar fooo", u"title model1 instance12"]
        )


class ComplexRegistrationTest(SearchTestBase):

    def testMetaStored(self):
        self.assertEqual(complex_registration_search_engine.search("instance11")[0].meta["is_published"], True)
        
    def testMetaNotStored(self):
        self.assertRaises(KeyError, lambda: complex_registration_search_engine.search("instance21")[0].meta["is_published"])
        
    def testFieldsExcludedOnSearch(self):
        self.assertEqual(complex_registration_search_engine.search("TITLE").count(), 4)
        self.assertEqual(complex_registration_search_engine.search("CONTENT").count(), 0)
        self.assertEqual(complex_registration_search_engine.search("DESCRIPTION").count(), 0)
        
    def testFieldsExcludedOnFilter(self):
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel1, "TITLE").count(), 2)
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel1, "CONTENT").count(), 0)
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel1, "DESCRIPTION").count(), 0)
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel2, "TITLE").count(), 2)
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel2, "CONTENT").count(), 0)
        self.assertEqual(complex_registration_search_engine.filter(WatsonTestModel2, "DESCRIPTION").count(), 0)


class WatsonTestModel1Admin(watson.SearchAdmin):

    search_fields = ("title", "description", "content",)
    
    list_display = ("title",)
    
    
admin.site.register(WatsonTestModel1, WatsonTestModel1Admin)


urlpatterns = patterns("",

    url("^simple/", include("watson.urls")),
    
    url("^custom/", include("watson.urls"), kwargs={
        "query_param": "fooo",
        "empty_query_redirect": "/simple/",
        "extra_context": {
            "foo": "bar",
            "foo2": lambda: "bar2",
        },
        "paginate_by": 10,
    }),
    
    url("^admin/", include(admin.site.urls)),

)


def handler404(request):
    return HttpResponseNotFound("Not found")
    
    
def handler500(request):
    return HttpResponseServerError("Server error")


class AdminIntegrationTest(SearchTestBase):

    urls = "watson.tests"
    
    def setUp(self):
        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
        settings.TEMPLATE_DIRS = (
            os.path.join(os.path.dirname(admin.__file__), "templates"),
        )
        super(AdminIntegrationTest, self).setUp()
        self.user = User(
            username = "foo",
            is_staff = True,
            is_superuser = True,
        )
        self.user.set_password("bar")
        self.user.save()
    
    @skipUnless("django.contrib.admin" in settings.INSTALLED_APPS, "Django admin site not installed")
    def testAdminIntegration(self):
        # Log the user in.
        if hasattr(self, "settings"):
            with self.settings(INSTALLED_APPS=tuple(set(tuple(settings.INSTALLED_APPS) + ("django.contrib.sessions",)))):  # HACK: Without this the client won't log in, for some reason.
                self.client.login(
                    username = "foo",
                    password = "bar",
                )
        else:
            self.client.login(
                username = "foo",
                password = "bar",
            )
        # Test a search with no query.
        response = self.client.get("/admin/auth/watsontestmodel1/")
        self.assertContains(response, "instance11")
        self.assertContains(response, "instance12")
        self.assertContains(response, "searchbar")  # Ensure that the search bar renders.
        # Test a search for all the instances.
        response = self.client.get("/admin/auth/watsontestmodel1/?q=title content description")
        self.assertContains(response, "instance11")
        self.assertContains(response, "instance12")
        # Test a search for half the instances.
        response = self.client.get("/admin/auth/watsontestmodel1/?q=instance11")
        self.assertContains(response, "instance11")
        self.assertNotContains(response, "instance12")
        
    def tearDown(self):
        super(AdminIntegrationTest, self).tearDown()
        self.user.delete()
        del self.user
        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
        
        
class SiteSearchTest(SearchTestBase):

    urls = "watson.tests"
    
    def setUp(self):
        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
        settings.TEMPLATE_DIRS = (
            os.path.join(os.path.dirname(admin.__file__), "templates"),
        )
        super(SiteSearchTest, self).setUp()
    
    def testSiteSearch(self):
        # Test a search than should find everything.
        response = self.client.get("/simple/?q=title")
        self.assertContains(response, "instance11")
        self.assertContains(response, "instance12")
        self.assertContains(response, "instance21")
        self.assertContains(response, "instance22")
        self.assertTemplateUsed(response, "watson/search_results.html")
        # Test a search that should find one thing.
        response = self.client.get("/simple/?q=instance11")
        self.assertContains(response, "instance11")
        self.assertNotContains(response, "instance12")
        self.assertNotContains(response, "instance21")
        self.assertNotContains(response, "instance22")
        # Test a search that should find nothing.
        response = self.client.get("/simple/?q=fooo")
        self.assertNotContains(response, "instance11")
        self.assertNotContains(response, "instance12")
        self.assertNotContains(response, "instance21")
        self.assertNotContains(response, "instance22")
        
    def testSiteSearchJSON(self):
        # Test a search that should find everything.
        response = self.client.get("/simple/json/?q=title")
        self.assertEqual(response["Content-Type"], "application/json; charset=utf-8")
        results = set(result["title"] for result in json.loads(response.content)["results"])
        self.assertEqual(len(results), 4)
        self.assertTrue("title model1 instance11" in results)
        self.assertTrue("title model1 instance12" in results)
        self.assertTrue("title model2 instance21" in results)
        self.assertTrue("title model2 instance22" in results)
        
    def testSiteSearchCustom(self):
        # Test a search than should find everything.
        response = self.client.get("/custom/?fooo=title")
        self.assertContains(response, "instance11")
        self.assertContains(response, "instance12")
        self.assertContains(response, "instance21")
        self.assertContains(response, "instance22")
        self.assertTemplateUsed(response, "watson/search_results.html")
        # Test that the extra context is included.
        self.assertEqual(response.context["foo"], "bar")
        self.assertEqual(response.context["foo2"], "bar2")
        # Test that pagination is included.
        self.assertEqual(response.context["paginator"].num_pages, 1)
        self.assertEqual(response.context["page_obj"].number, 1)
        self.assertEqual(response.context["search_results"], response.context["page_obj"].object_list)
        # Test a request for an empty page.
        try:
            response = self.client.get("/custom/?fooo=title&page=10")
        except template.TemplateDoesNotExist as ex:
            # No 404 template defined.
            self.assertEqual(ex.args[0], "404.html")
        else:
            self.assertEqual(response.status_code, 404)
        # Test a requet for the last page.
        response = self.client.get("/custom/?fooo=title&page=last")
        self.assertEqual(response.context["paginator"].num_pages, 1)
        self.assertEqual(response.context["page_obj"].number, 1)
        self.assertEqual(response.context["search_results"], response.context["page_obj"].object_list)
        # Test a search that should find nothing.
        response = self.client.get("/custom/?q=fooo")
        self.assertRedirects(response, "/simple/")
        
    def testSiteSearchCustomJSON(self):
        # Test a search that should find everything.
        response = self.client.get("/custom/json/?fooo=title&page=last")
        self.assertEqual(response["Content-Type"], "application/json; charset=utf-8")
        results = set(result["title"] for result in json.loads(response.content)["results"])
        self.assertEqual(len(results), 4)
        self.assertTrue("title model1 instance11" in results)
        self.assertTrue("title model1 instance12" in results)
        self.assertTrue("title model2 instance21" in results)
        self.assertTrue("title model2 instance22" in results)
        # Test a search with an invalid page.
        response = self.client.get("/custom/json/?fooo=title&page=200")
        self.assertEqual(response.status_code, 404)
        
    def tearDown(self):
        super(SiteSearchTest, self).tearDown()
        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS