Merge branch 'release/0.9.2'
[philo.git] / philo / tests.py
1 import sys
2 import traceback
3
4 from django import template
5 from django.conf import settings
6 from django.contrib.contenttypes.models import ContentType
7 from django.db import connection, models
8 from django.template import loader
9 from django.template.loaders import cached
10 from django.test import TestCase
11 from django.test.utils import setup_test_template_loader, restore_template_loaders
12 from django.utils.datastructures import SortedDict
13
14 from philo.exceptions import AncestorDoesNotExist
15 from philo.models import Node, Page, Template, Tag
16
17
18 class TemplateTestCase(TestCase):
19         fixtures = ['test_fixtures.json']
20         
21         def test_templates(self):
22                 "Tests to make sure that embed behaves with complex includes and extends"
23                 template_tests = self.get_template_tests()
24                 
25                 # Register our custom template loader. Shamelessly cribbed from django/tests/regressiontests/templates/tests.py:384.
26                 cache_loader = setup_test_template_loader(
27                         dict([(name, t[0]) for name, t in template_tests.iteritems()]),
28                         use_cached_loader=True,
29                 )
30                 
31                 failures = []
32                 tests = template_tests.items()
33                 tests.sort()
34                 
35                 # Turn TEMPLATE_DEBUG off, because tests assume that.
36                 old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
37                 
38                 # Set TEMPLATE_STRING_IF_INVALID to a known string.
39                 old_invalid = settings.TEMPLATE_STRING_IF_INVALID
40                 expected_invalid_str = 'INVALID'
41                 
42                 # Run tests
43                 for name, vals in tests:
44                         xx, context, result = vals
45                         try:
46                                 test_template = loader.get_template(name)
47                                 output = test_template.render(template.Context(context))
48                         except Exception:
49                                 exc_type, exc_value, exc_tb = sys.exc_info()
50                                 if exc_type != result:
51                                         tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb))
52                                         failures.append("Template test %s -- FAILED. Got %s, exception: %s\n%s" % (name, exc_type, exc_value, tb))
53                                 continue
54                         if output != result:
55                                 failures.append("Template test %s -- FAILED. Expected %r, got %r" % (name, result, output))
56                 
57                 # Cleanup
58                 settings.TEMPLATE_DEBUG = old_td
59                 settings.TEMPLATE_STRING_IF_INVALID = old_invalid
60                 restore_template_loaders()
61                 
62                 self.assertEqual(failures, [], "Tests failed:\n%s\n%s" % ('-'*70, ("\n%s\n" % ('-'*70)).join(failures)))
63         
64         
65         def get_template_tests(self):
66                 # SYNTAX --
67                 # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
68                 embedded = Tag.objects.get(pk=1)
69                 return {
70                         # EMBED INCLUSION HANDLING
71                         
72                         'embed01': ('{{ embedded.name|safe }}', {'embedded': embedded}, embedded.name),
73                         'embed02': ('{{ embedded.name|safe }}{{ var1 }}{{ var2 }}', {'embedded': embedded}, embedded.name),
74                         'embed03': ('{{ embedded.name|safe }} is a lie!', {'embedded': embedded}, '%s is a lie!' % embedded.name),
75                         
76                         # Simple template structure with embed
77                         'simple01': ('{% embed philo.tag with "embed01" %}{% embed philo.tag 1 %}Simple{% block one %}{% endblock %}', {'embedded': embedded}, '%sSimple' % embedded.name),
78                         'simple02': ('{% extends "simple01" %}', {}, '%sSimple' % embedded.name),
79                         'simple03': ('{% embed philo.tag with "embed000" %}', {}, settings.TEMPLATE_STRING_IF_INVALID),
80                         'simple04': ('{% embed philo.tag 1 %}', {}, settings.TEMPLATE_STRING_IF_INVALID),
81                         'simple05': ('{% embed philo.tag with "embed01" %}{% embed embedded %}', {'embedded': embedded}, embedded.name),
82                         
83                         # Kwargs
84                         'kwargs01': ('{% embed philo.tag with "embed02" %}{% embed philo.tag 1 var1="hi" var2=lo %}', {'lo': 'lo'}, '%shilo' % embedded.name),
85                         
86                         # Filters/variables
87                         'filters01': ('{% embed philo.tag with "embed02" %}{% embed philo.tag 1 var1=hi|first var2=lo|slice:"3" %}', {'hi': ["These", "words"], 'lo': 'lower'}, '%sTheselow' % embedded.name),
88                         'filters02': ('{% embed philo.tag with "embed01" %}{% embed philo.tag entry %}', {'entry': 1}, embedded.name),
89                         
90                         # Blocky structure
91                         'block01': ('{% block one %}Hello{% endblock %}', {}, 'Hello'),
92                         'block02': ('{% extends "simple01" %}{% block one %}{% embed philo.tag 1 %}{% endblock %}', {}, "%sSimple%s" % (embedded.name, embedded.name)),
93                         'block03': ('{% extends "simple01" %}{% embed philo.tag with "embed03" %}{% block one %}{% embed philo.tag 1 %}{% endblock %}', {}, "%sSimple%s is a lie!" % (embedded.name, embedded.name)),
94                         
95                         # Blocks and includes
96                         'block-include01': ('{% extends "simple01" %}{% embed philo.tag with "embed03" %}{% block one %}{% include "simple01" %}{% embed philo.tag 1 %}{% endblock %}', {}, "%sSimple%sSimple%s is a lie!" % (embedded.name, embedded.name, embedded.name)),
97                         'block-include02': ('{% extends "simple01" %}{% block one %}{% include "simple04" %}{% embed philo.tag with "embed03" %}{% include "simple04" %}{% embed philo.tag 1 %}{% endblock %}', {}, "%sSimple%s%s is a lie!%s is a lie!" % (embedded.name, embedded.name, embedded.name, embedded.name)),
98                         
99                         # Tests for more complex situations...
100                         'complex01': ('{% block one %}{% endblock %}complex{% block two %}{% endblock %}', {}, 'complex'),
101                         'complex02': ('{% extends "complex01" %}', {}, 'complex'),
102                         'complex03': ('{% extends "complex02" %}{% embed philo.tag with "embed01" %}', {}, 'complex'),
103                         'complex04': ('{% extends "complex03" %}{% block one %}{% embed philo.tag 1 %}{% endblock %}', {}, '%scomplex' % embedded.name),
104                         'complex05': ('{% extends "complex03" %}{% block one %}{% include "simple04" %}{% endblock %}', {}, '%scomplex' % embedded.name),
105                 }
106
107
108 class NodeURLTestCase(TestCase):
109         """Tests the features of the node_url template tag."""
110         urls = 'philo.urls'
111         fixtures = ['test_fixtures.json']
112         
113         def setUp(self):
114                 self.templates = [
115                                 ("{% node_url %}", "/root/second"),
116                                 ("{% node_url for node2 %}", "/root/second2"),
117                                 ("{% node_url as hello %}<p>{{ hello|slice:'1:' }}</p>", "<p>root/second</p>"),
118                                 ("{% node_url for nodes|first %}", "/root"),
119                 ]
120                 
121                 nodes = Node.objects.all()
122                 
123                 self.context = template.Context({
124                         'node': nodes.get(slug='second'),
125                         'node2': nodes.get(slug='second2'),
126                         'nodes': nodes,
127                 })
128         
129         def test_nodeurl(self):
130                 for string, result in self.templates:
131                         self.assertEqual(template.Template(string).render(self.context), result)
132
133 class TreePathTestCase(TestCase):
134         urls = 'philo.urls'
135         fixtures = ['test_fixtures.json']
136         
137         def assertQueryLimit(self, max, expected_result, *args, **kwargs):
138                 # As a rough measure of efficiency, limit the number of queries required for a given operation.
139                 settings.DEBUG = True
140                 call = kwargs.pop('callable', Node.objects.get_with_path)
141                 try:
142                         queries = len(connection.queries)
143                         if isinstance(expected_result, type) and issubclass(expected_result, Exception):
144                                 self.assertRaises(expected_result, call, *args, **kwargs)
145                         else:
146                                 self.assertEqual(call(*args, **kwargs), expected_result)
147                         queries = len(connection.queries) - queries
148                         if queries > max:
149                                 raise AssertionError('"%d" unexpectedly not less than or equal to "%s"' % (queries, max))
150                 finally:
151                         settings.DEBUG = False
152         
153         def test_get_with_path(self):
154                 root = Node.objects.get(slug='root')
155                 third = Node.objects.get(slug='third')
156                 second2 = Node.objects.get(slug='second2')
157                 fifth = Node.objects.get(slug='fifth')
158                 e = Node.DoesNotExist
159                 
160                 # Empty segments
161                 self.assertQueryLimit(0, root, '', root=root)
162                 self.assertQueryLimit(0, e, '')
163                 self.assertQueryLimit(0, (root, None), '', root=root, absolute_result=False)
164                 
165                 # Absolute result
166                 self.assertQueryLimit(1, third, 'root/second/third')
167                 self.assertQueryLimit(1, third, 'second/third', root=root)
168                 self.assertQueryLimit(1, third, 'root//////second/third///')
169                 
170                 self.assertQueryLimit(1, e, 'root/secont/third')
171                 self.assertQueryLimit(1, e, 'second/third')
172                 
173                 # Non-absolute result (binary search)
174                 self.assertQueryLimit(2, (second2, 'sub/path/tail'), 'root/second2/sub/path/tail', absolute_result=False)
175                 self.assertQueryLimit(3, (second2, 'sub'), 'root/second2/sub/', absolute_result=False)
176                 self.assertQueryLimit(2, e, 'invalid/path/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/0', absolute_result=False)
177                 self.assertQueryLimit(1, (root, None), 'root', absolute_result=False)
178                 self.assertQueryLimit(2, (second2, None), 'root/second2', absolute_result=False)
179                 self.assertQueryLimit(3, (third, None), 'root/second/third', absolute_result=False)
180                 
181                 # with root != None
182                 self.assertQueryLimit(1, (second2, None), 'second2', root=root, absolute_result=False)
183                 self.assertQueryLimit(2, (third, None), 'second/third', root=root, absolute_result=False)
184                 
185                 # Eliminate trailing slash
186                 self.assertQueryLimit(2, (second2, 'sub/path/tail'), 'root/second2/sub/path/tail/', absolute_result=False)
187                 
188                 # Speed increase for leaf nodes - should this be tested?
189                 self.assertQueryLimit(1, (fifth, 'sub/path/tail/len/five'), 'root/second/third/fourth/fifth/sub/path/tail/len/five', absolute_result=False)
190         
191         def test_get_path(self):
192                 root = Node.objects.get(slug='root')
193                 root2 = Node.objects.get(slug='root')
194                 third = Node.objects.get(slug='third')
195                 second2 = Node.objects.get(slug='second2')
196                 fifth = Node.objects.get(slug='fifth')
197                 e = AncestorDoesNotExist
198                 
199                 self.assertQueryLimit(0, 'root', callable=root.get_path)
200                 self.assertQueryLimit(0, '', root2, callable=root.get_path)
201                 self.assertQueryLimit(1, 'root/second/third', callable=third.get_path)
202                 self.assertQueryLimit(1, 'second/third', root, callable=third.get_path)
203                 self.assertQueryLimit(1, e, third, callable=second2.get_path)
204                 self.assertQueryLimit(1, '? - ?', root, ' - ', 'title', callable=third.get_path)
205
206
207 class ContainerTestCase(TestCase):
208         def test_simple_containers(self):
209                 t = Template(code="{% container one %}{% container two %}{% container three %}{% container two %}")
210                 contentlet_specs, contentreference_specs = t.containers
211                 self.assertEqual(len(contentreference_specs.keyOrder), 0)
212                 self.assertEqual(contentlet_specs, ['one', 'two', 'three'])
213                 
214                 ct = ContentType.objects.get_for_model(Tag)
215                 t = Template(code="{% container one references philo.tag as tag1 %}{% container two references philo.tag as tag2 %}{% container one references philo.tag as tag1 %}")
216                 contentlet_specs, contentreference_specs = t.containers
217                 self.assertEqual(len(contentlet_specs), 0)
218                 self.assertEqual(contentreference_specs, SortedDict([('one', ct), ('two', ct)]))