Skip to content
Browse files

Fix Solr more-like-this tests (closes #655)

* Refactored the MLT tests to be less brittle in checking only
  the top 5 results without respect to slight ordering
  variations.
* Refactored LiveSolrMoreLikeThisTestCase into multiple tests
* Convert MLT templatetag tests to rely on mocks for stability
  and to avoid hard-coding backend assumptions, at the expense
  of relying completely on the backend MLT queryset-level tests
  to exercise that code.
* Updated MLT code to always assume deferred querysets are
  available (introduced in Django 1.1) and removed a hard-coded
  internal attr check
  • Loading branch information...
1 parent c8b601b commit 96f16246e7d762c254e077d7898c39810a7f06b4 @acdha acdha committed
Showing with 78 additions and 71 deletions.
  1. +38 −26 tests/solr_tests/tests/solr_backend.py
  2. +40 −45 tests/solr_tests/tests/templatetags.py
View
64 tests/solr_tests/tests/solr_backend.py
@@ -997,32 +997,44 @@ def tearDown(self):
super(LiveSolrMoreLikeThisTestCase, self).tearDown()
def test_more_like_this(self):
- mlt = self.sqs.more_like_this(MockModel.objects.get(pk=1))
- self.assertEqual(mlt.count(), 22)
- self.assertEqual([result.pk for result in mlt], ['14', '6', '10', '22', '4', '5', '3', '12', '2', '19', '18', '13', '15', '21', '7', '23', '20', '9', '1', '2', '17', '16'])
- self.assertEqual(len([result.pk for result in mlt]), 22)
-
- alt_mlt = self.sqs.filter(name='daniel3').more_like_this(MockModel.objects.get(pk=3))
- self.assertEqual(alt_mlt.count(), 8)
- self.assertEqual([result.pk for result in alt_mlt], ['17', '16', '19', '23', '22', '13', '1', '2'])
- self.assertEqual(len([result.pk for result in alt_mlt]), 8)
-
- alt_mlt_with_models = self.sqs.models(MockModel).more_like_this(MockModel.objects.get(pk=1))
- self.assertEqual(alt_mlt_with_models.count(), 20)
- self.assertEqual([result.pk for result in alt_mlt_with_models], ['14', '6', '10', '22', '4', '5', '3', '12', '2', '19', '18', '13', '15', '21', '7', '23', '20', '9', '17', '16'])
- self.assertEqual(len([result.pk for result in alt_mlt_with_models]), 20)
-
- if hasattr(MockModel.objects, 'defer'):
- # Make sure MLT works with deferred bits.
- mi = MockModel.objects.defer('foo').get(pk=1)
- self.assertEqual(mi._deferred, True)
- deferred = self.sqs.models(MockModel).more_like_this(mi)
- self.assertEqual(deferred.count(), 0)
- self.assertEqual([result.pk for result in deferred], [])
- self.assertEqual(len([result.pk for result in deferred]), 0)
-
- # Ensure that swapping the ``result_class`` works.
- self.assertTrue(isinstance(self.sqs.result_class(MockSearchResult).more_like_this(MockModel.objects.get(pk=1))[0], MockSearchResult))
+ all_mlt = self.sqs.more_like_this(MockModel.objects.get(pk=1))
+ self.assertEqual(all_mlt.count(), len([result.pk for result in all_mlt]),
+ msg="mlt SearchQuerySet .count() didn't match retrieved result length")
+
+ # Rather than hard-code assumptions about Solr's return order, we have a few very similar
+ # items which we'll confirm are included in the first 5 results. This is still ugly as we're
+ # hard-coding primary keys but it's better than breaking any time a Solr update or data
+ # change causes a score to shift slightly
+
+ top_results = [int(result.pk) for result in all_mlt[:5]]
+ for i in (14, 6, 4, 22, 10):
+ self.assertIn(i, top_results)
+
+ filtered_mlt = self.sqs.filter(name='daniel3').more_like_this(MockModel.objects.get(pk=3))
+ self.assertLess(filtered_mlt.count(), all_mlt.count())
+ top_filtered_results = [int(result.pk) for result in filtered_mlt[:5]]
+ for i in (23, 13, 17, 16, 19):
+ self.assertIn(i, top_filtered_results)
+
+ filtered_mlt_with_models = self.sqs.models(MockModel).more_like_this(MockModel.objects.get(pk=1))
+ self.assertLessEqual(filtered_mlt_with_models.count(), all_mlt.count())
+ top_filtered_with_models = [int(result.pk) for result in filtered_mlt_with_models[:5]]
+ for i in (14, 6, 4, 22, 10):
+ self.assertIn(i, top_filtered_with_models)
+
+ def test_more_like_this_defer(self):
+ mi = MockModel.objects.defer('foo').get(pk=1)
+ # FIXME: this currently is known to fail because haystack.utils.loading doesn't see the
+ # MockModel_Deferred_foo class as registered:
+ deferred = self.sqs.models(MockModel).more_like_this(mi)
+ self.assertEqual(deferred.count(), 0)
+ self.assertEqual([result.pk for result in deferred], [])
+ self.assertEqual(len([result.pk for result in deferred]), 0)
+
+ def test_more_like_this_custom_result_class(self):
+ """Ensure that swapping the ``result_class`` works"""
+ first_result = self.sqs.result_class(MockSearchResult).more_like_this(MockModel.objects.get(pk=1))[0]
+ self.assertIsInstance(first_result, MockSearchResult)
class LiveSolrAutocompleteTestCase(TestCase):
View
85 tests/solr_tests/tests/templatetags.py
@@ -1,60 +1,55 @@
+# encoding: utf-8
+from mock import call, patch
+
from django.template import Template, Context
from django.test import TestCase
-from haystack import connections
-from haystack import indexes
-from haystack.utils.loading import UnifiedIndex
+
from core.models import MockModel
-from solr_tests.tests.solr_backend import clear_solr_index
-class MLTSearchIndex(indexes.RealTimeSearchIndex, indexes.Indexable):
- text = indexes.CharField(document=True, model_attr='foo')
+@patch("haystack.templatetags.more_like_this.SearchQuerySet")
+class MoreLikeThisTagTestCase(TestCase):
+ def render(self, template, context):
+ # Why on Earth does Django not have a TemplateTestCase yet?
+ t = Template(template)
+ c = Context(context)
+ return t.render(c)
+
+ def test_more_like_this_without_limit(self, mock_sqs):
+ mock_model = MockModel.objects.get(pk=3)
+ template = """{% load more_like_this %}{% more_like_this entry as related_content %}{% for rc in related_content %}{{ rc.id }}{% endfor %}"""
+ context = {'entry': mock_model}
+
+ mlt = mock_sqs.return_value.more_like_this
+ mlt.return_value = [{"id": "test_id"}]
- def get_model(self):
- return MockModel
+ self.assertEqual("test_id", self.render(template, context))
+ mlt.assert_called_once_with(mock_model)
-class MoreLikeThisTagTestCase(TestCase):
- fixtures = ['bulk_data.json']
+ def test_more_like_this_with_limit(self, mock_sqs):
+ mock_model = MockModel.objects.get(pk=3)
+ template = """{% load more_like_this %}{% more_like_this entry as related_content limit 5 %}{% for rc in related_content %}{{ rc.id }}{% endfor %}"""
+ context = {'entry': mock_model}
- def setUp(self):
- super(MoreLikeThisTagTestCase, self).setUp()
+ mlt = mock_sqs.return_value.more_like_this
+ mlt.return_value.__getitem__.return_value = [{"id": "test_id"}]
- clear_solr_index()
+ self.assertEqual("test_id", self.render(template, context))
- # Stow.
- self.old_ui = connections['default'].get_unified_index()
- self.ui = UnifiedIndex()
- self.smmi = MLTSearchIndex()
- self.ui.build(indexes=[self.smmi])
- connections['default']._index = self.ui
+ mlt.assert_called_once_with(mock_model)
- # Force indexing of the content.
- for mock in MockModel.objects.all():
- mock.save()
+ mock_sqs.assert_has_calls([call().more_like_this(mock_model),
+ call().more_like_this().__getitem__(slice(None, 5))],
+ any_order=True)
- def tearDown(self):
- connections['default']._index = self.old_ui
- super(MoreLikeThisTagTestCase, self).tearDown()
+ def test_more_like_this_for_model(self, mock_sqs):
+ mock_model = MockModel.objects.get(pk=3)
+ template = """{% load more_like_this %}{% more_like_this entry as related_content for "core.mock" limit 5 %}{% for rc in related_content %}{{ rc.id }}{% endfor %}"""
+ context = {'entry': mock_model}
- def render(self, template, context):
- # Why on Earth does Django not have a TemplateTestCase yet?
- t = Template(template)
- c = Context(context)
- return t.render(c)
+ self.render(template, context)
- def test_more_like_this_with_limit(self):
- mock = MockModel.objects.get(pk=3)
- template = """{% load more_like_this %}{% more_like_this entry as related_content limit 5 %}{% for rc in related_content %}{{ rc.id }} {% endfor %}"""
- context = {
- 'entry': mock,
- }
- self.assertEqual(set(self.render(template, context).split()), set(u'core.mockmodel.2 core.mockmodel.18 core.mockmodel.17 core.mockmodel.15 core.mockmodel.21 '.split()))
-
- def test_more_like_this_without_limit(self):
- mock = MockModel.objects.get(pk=3)
- template = """{% load more_like_this %}{% more_like_this entry as related_content %}{% for rc in related_content %}{{ rc.id }} {% endfor %}"""
- context = {
- 'entry': mock,
- }
- self.assertEqual(set(self.render(template, context).split()), set(u'core.mockmodel.2 core.mockmodel.18 core.mockmodel.23 core.mockmodel.15 core.mockmodel.21 core.mockmodel.13 core.mockmodel.17 core.mockmodel.16 core.mockmodel.20 core.mockmodel.1 core.mockmodel.22 core.mockmodel.19 core.mockmodel.8 core.mockmodel.6 core.mockmodel.11 core.mockmodel.14 core.mockmodel.12 core.mockmodel.9 core.mockmodel.7'.split()))
+ mock_sqs.assert_has_calls([call().models().more_like_this(mock_model),
+ call().models().more_like_this().__getitem__(slice(None, 5))],
+ any_order=True)

0 comments on commit 96f1624

Please sign in to comment.
Something went wrong with that request. Please try again.