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