-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_utils.py
1120 lines (913 loc) · 43.7 KB
/
test_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import difflib
import pprint
import datetime
from lxml import etree
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.http import HttpRequest
from django.http.request import QueryDict
from django.test import TestCase
from django.test.client import Client, ClientHandler, encode_multipart, \
MULTIPART_CONTENT, BOUNDARY
from django.utils.importlib import import_module
from django.utils import timezone
class SuperClientHandler(ClientHandler):
def get_response(self, request):
request.body # access it now to stop later access from blowing up
# after cms.utils.get_language_from_request() forces discarding it.
response = super(SuperClientHandler, self).get_response(request)
response.real_request = request
return response
class SuperClient(Client):
def __init__(self, enforce_csrf_checks=False, **defaults):
super(SuperClient, self).__init__(enforce_csrf_checks, **defaults)
self.handler = SuperClientHandler(enforce_csrf_checks)
def get(self, *args, **extra):
response = Client.get(self, *args, **extra)
return self.capture_results('get', response, *args, **extra)
def post(self, path, data={}, content_type=MULTIPART_CONTENT,
auto_parse_response_as_xhtml=True, **extra):
"""
Pickle the request first, in case it contains a StringIO (file upload)
that can't be read twice.
If the data doesn't have an items() method, then it's probably already
been converted to a string (encoded), and if we try again we'll call
the nonexistent items() method and fail, so just don't encode it at
all."""
if content_type == MULTIPART_CONTENT and \
getattr(data, 'items', None) is not None:
data = encode_multipart(BOUNDARY, data)
# print "session cookie = %s" % (
# self.cookies[django_settings.SESSION_COOKIE_NAME])
response = Client.post(self, path, data, content_type,
**extra)
if response is None:
raise Exception("POST method responded with None!")
return self.capture_results('post', response, path, data, content_type,
auto_parse_response_as_xhtml=auto_parse_response_as_xhtml, **extra)
def capture_results(self, method_name, response, *args, **kwargs):
# print("%s.%s(%s)" % (self, method_name, args))
self.last_method = method_name
self.last_method_args = args
self.last_method_kwargs = kwargs
self.last_response = response
from django.template.response import SimpleTemplateResponse
if isinstance(response, SimpleTemplateResponse):
response.render()
if getattr(response, 'context', None) is None and \
getattr(response, 'context_data', None) is not None:
response.context = response.context_data
if not response.content:
return response # without setting the parsed attribute
if not kwargs.get('auto_parse_response_as_xhtml', True):
return response # without setting the parsed attribute
mime_type, _, charset = response['Content-Type'].partition(';')
if mime_type != "text/html":
return response # without setting the parsed attribute
if not response.content:
raise Exception("Response is HTML but unexpectedly has no "
"content: %s: %s" % (response.status_code, response.content))
# http://stackoverflow.com/questions/5170252/whats-the-best-way-to-handle-nbsp-like-entities-in-xml-documents-with-lxml
xml = """<?xml version="1.0" encoding="utf-8"?>\n""" + response.content
parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False)
try:
root = etree.fromstring(xml, parser)
except SyntaxError as e:
import re
match = re.match('Opening and ending tag mismatch: ' +
'(\w+) line (\d+) and (\w+), line (\d+), column (\d+)', str(e))
if match:
lineno = int(match.group(2))
else:
match = re.match('.*, line (\d+), column (\d+)', str(e))
if match:
lineno = int(match.group(1))
else:
lineno = e.lineno
lines = xml.splitlines(True)
if lineno is not None:
first_line = max(lineno - 5, 1)
last_line = min(lineno + 5, len(lines))
print xml
print "Context (line %s):\n>>%s<<" % (lineno,
"".join(lines[first_line:last_line]))
else:
print repr(e)
raise
response.parsed = root
return response
def retry(self):
"""Try the same request again (e.g. after login)."""
# print "retry kwargs = %s" % self.last_method_kwargs
return getattr(self, self.last_method)(*self.last_method_args,
**self.last_method_kwargs)
"""
def request(self, **request):
print "request = %s" % request
return super(SuperClient, self).request(**request)
"""
def additional_login(self, user):
"""
Simulate another user logging in, without changing our current
credentials or cookies.
"""
engine = import_module(settings.SESSION_ENGINE)
# Create a fake request to store login details.
request = HttpRequest()
if self.session:
request.session = self.session
else:
request.session = engine.SessionStore()
request.user = None
# login() doesn't give our session store a chance to
# initialise itself for the current request, and django
# never calls login() during real operation? so it's OK
# to work around this limitation by poking the request
# into the session for test purposes?
request.session.request = request
self.fake_login_request = request
login(request, user)
# Save the session values.
request.session.save()
return request
def login(self, **credentials):
"""
Sets the Factory to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
Work around the limitation in django.test.Client that it always
constructs the session with just the cookie name, not using our
Middleware, so we don't get to capture the request and hence neither
the current user.
"""
user = authenticate(**credentials)
if user and user.is_active \
and 'django.contrib.sessions' in settings.INSTALLED_APPS:
request = self.additional_login(user)
# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.cookies[session_cookie].update(cookie_data)
return request
else:
return None
from django.forms.forms import BoundField
class FormUtilsMixin(object):
def extract_fields(self, form):
"""
Extract a list of fields names and values from a ModelForm.
Typical usage:
fields = dict(extract_fields(my_form))
self.assertEquals("Foo", fields['bar'].verbose_name)
"""
for fieldset in form:
for line in fieldset:
for field in line:
if isinstance(field.field, BoundField):
yield field.field.name, field
else:
yield field.field['name'], field
def extract_field(self, form, field_name):
for fieldset in form:
for line in fieldset:
for field in line:
if isinstance(field.field, BoundField):
if field.field.name == field_name:
return field
else:
if field.field['name'] == field_name:
return field
raise KeyError('Field not found in form: %s' % field_name)
def extract_form(self, response, message=None):
"""
Extract the form inserted into the context by generic
class-based views. Quite trivial, but makes tests more
readable.
"""
if message is None:
prefix = ''
else:
prefix = '%s: ' % message
self.assertIn('context', dir(response), prefix + "Missing context " +
"in response: %s: %s" % (response, dir(response)))
self.assertIsNotNone(response.context, prefix + "Empty context in " +
"response: %s: %s" % (response, dir(response)))
return self.assertInDict('form', response.context, prefix +
"Missing form in response context")
def extract_admin_form(self, response):
"""
Extract the form generated by the admin interface.
"""
self.assertIn('context', dir(response), "Missing context " +
"in response: %s: %s" % (response, dir(response)))
self.assertIsNotNone(response.context, "Empty context in response: " +
"%s: %s" % (response, dir(response)))
return self.assertInDict('adminform', response.context,
response.content)
def extract_admin_form_as_normal_form(self, response):
form = self.extract_admin_form(response)
flat_form = []
for fieldset in form:
for line in fieldset:
for field in line:
flat_form.append(field.field)
return flat_form
def extract_admin_form_fields(self, response):
"""
Extract all fields from a form generated by the admin interface.
"""
return dict(self.extract_fields(self.extract_admin_form(response)))
def extract_admin_form_field(self, response, field_name):
"""
Extract a named field from a form generated by the admin interface.
"""
fields = self.extract_admin_form_fields(response)
return self.assertInDict(field_name, fields)
def extract_values_from_choices(self, choices):
# allow for the optgroup syntax
possible_values = []
for option_value, option_label in choices:
if isinstance(option_label, (list, tuple)):
possible_values += [v for v, label in option_label]
else:
possible_values.append(option_value)
return possible_values
def extract_labels_from_choices(self, choices):
# allow for the optgroup syntax
possible_values = []
for option_value, option_label in choices:
if isinstance(option_label, (list, tuple)):
possible_values += [label for v, label in option_label]
else:
possible_values.append(option_label)
return possible_values
def value_to_datadict(self, widget, name, value, strict=True):
"""
There's a value_from_datadict method in each django.forms.widgets widget,
but nothing that goes in the reverse direction, and
test_utils.AptivateEnhancedTestCase.update_form_values really wants to
convert form instance values (Python data) into a set of parameters
suitable for passing to client.post().
This needs to be implemented for each subclass of Widget that doesn't
just convert its value to a string.
"""
import django.forms.widgets
import django.contrib.admin.widgets
import django.contrib.auth.forms
from django.utils.encoding import force_unicode
try:
from captcha.widgets import ReCaptcha
except ImportError:
ReCaptcha = None
if isinstance(widget, django.forms.widgets.FileInput):
# this is a special case: don't convert FieldFile objects to strings,
# because the TestClient needs to detect and encode them properly.
if bool(value):
return {name: value}
else:
# empty file upload, don't set any parameters
return {}
elif isinstance(widget, django.forms.widgets.MultiWidget):
values = {}
for index, subwidget in enumerate(widget.widgets):
param_name = "%s_%s" % (name, index)
values.update(self.value_to_datadict(subwidget, param_name,
value, strict))
return values
elif isinstance(widget, django.forms.widgets.CheckboxInput):
if widget.check_test(value):
return {name: '1'}
else:
# unchecked checkboxes are not sent in HTML
return {}
elif isinstance(widget, django.forms.widgets.Select):
if '__iter__' in dir(value):
values = list(value)
else:
values = [value]
choices = list(widget.choices)
possible_values = self.extract_values_from_choices(choices)
found_values = []
for v in values:
if v in possible_values:
found_values.append(str(v))
elif v == '' and isinstance(widget, django.forms.widgets.RadioSelect):
# It's possible not to select any option in a RadioSelect
# widget, although this will probably generate an error
# as the field is probably required, but we need to be
# able to test that behaviour, by passing an empty string.
#
# In that case, we don't add anything to the POST data,
# because a user agent wouldn't either if the user hasn't
# selected any of the radio buttons
pass
elif strict:
# Since user agent behaviour differs, authors should ensure
# that each menu includes a default pre-selected OPTION
# (i.e. that a list includes a selected value)
raise Exception("List without selected value: "
"%s = %s (should be one of: %s)" %
(name, value, self.extract_labels_from_choices(choices)))
else:
# don't add anything to the list right now
pass
if found_values:
return {name: found_values}
elif isinstance(widget, django.forms.widgets.RadioSelect):
# As above, it's possible not to select any option in a
# RadioSelect widget. In that case, we don't add anything
# to the POST data.
return {}
elif len(possible_values) == 0:
# it's possible to select no option in a drop-down list with
# no options!
return {}
else:
# most browsers pre-select the first value
return {name: str(possible_values[0])}
elif isinstance(widget, django.contrib.admin.widgets.RelatedFieldWidgetWrapper):
subwidget = widget.widget
subwidget.choices = list(widget.choices)
return self.value_to_datadict(subwidget, name, value, strict)
elif isinstance(widget, django.forms.widgets.Textarea):
return {name: force_unicode(value)}
elif isinstance(widget, django.contrib.auth.forms.ReadOnlyPasswordHashWidget):
return {}
elif isinstance(widget, ReCaptcha):
raise Exception("You can't spoof a ReCaptcha, delete it from "
"form.fields instead!")
elif getattr(widget, '_format_value', None):
value = widget._format_value(value)
if value is None:
value = ''
return {name: value}
else:
raise Exception("Don't know how to convert data to form values " +
"for %s" % widget)
def update_form_values(self, form, **new_values):
"""
Extract the values from a form, change the ones passed as
keyword arguments, empty keys whose value is None, delete keys
which represent a file upload where no file is provided, and
return a values dict suitable for self.client.post().
"""
params = dict()
field_names = [bound_field.name for bound_field in form]
for name in new_values:
if name not in field_names:
self.fail("Tried to change value for unknown field %s. Valid "
"field names are: %s" % (name, field_names))
for bound_field in form:
# fields[k] returns a BoundField, not a django.forms.fields.Field
# which is where the widget lives
form_field = bound_field.field
widget = form_field.widget
# defaults to the current value bound into the form:
value = new_values.get(bound_field.name, bound_field.value())
# be strict with values passed by tests to this function,
# and lax with values that were already in the record/form
new_params = self.value_to_datadict(widget, bound_field.name, value,
strict=(bound_field.name in new_values))
params.update(new_params)
return params
def fill_form_with_dummy_data(self, form, post_data=None):
import django.forms.fields
import django.forms.widgets
try:
from captcha.widgets import ReCaptcha
except ImportError:
ReCaptcha = None
if post_data is None:
post_data = {}
else:
post_data = dict(post_data)
fields_to_delete = []
for field in form:
if field.field.required and not post_data.get(field.name):
widget = field.field.widget
if isinstance(widget, django.forms.widgets.Select):
choices = list(widget.choices)
if not choices:
choices = list(field.field.choices)
possible_values = [v for v, label in choices]
if isinstance(widget, django.forms.widgets.SelectMultiple):
value = [possible_values[0]]
else:
value = possible_values[0]
elif isinstance(field.field, django.forms.fields.EmailField):
value = "[email protected]"
elif isinstance(widget, ReCaptcha):
fields_to_delete.append(field.name)
continue
else:
value = "Whee"
post_data[field.name] = value
query_dict = QueryDict('', mutable=True).copy()
for key, value in post_data.iteritems():
if hasattr(value, '__iter__'):
query_dict.setlist(key, value)
else:
query_dict.setlist(key, [value])
query_dict._mutable = False
new_form = form.__class__(query_dict)
for field_name in fields_to_delete:
del new_form.fields[field_name]
# post_data is not very useful if fields_to_delete is not empty,
# because any form constructed with it won't validate, but it is
# useful under some circumstances, so return it anyway.
"""
if fields_to_delete:
post_data = None
"""
return new_form, post_data
def extract_error_message(self, response):
error_message = response.parsed.findtext('.//' +
self.xhtml('div') + '[@class="error-message"]')
if error_message is None:
error_message = response.parsed.findtext('.//' +
self.xhtml('p') + '[@class="errornote"]')
if error_message is not None:
# extract individual field errors, if any
more_error_messages = response.parsed.findtext('.//' +
self.xhtml('td') + '[@class="errors-cell"]')
if more_error_messages is not None:
error_message += more_error_messages
# trim and canonicalise whitespace
error_message = error_message.strip()
import re
error_message = re.sub('\\s+', ' ', error_message)
# return message or None
return error_message
def assert_changelist_not_admin_form_with_errors(self, response):
"""
Checks that the response (to a POST to an admin change form) contains
a changelist, which means that the update was successful; and not
an adminform with errors, which would mean that the update was
unsuccessful.
If not, the update unexpectedly failed, so we extract and report the
error messages from the form in a helpful way.
"""
self.assertTrue(hasattr(response, 'context'), "Missing context " +
"in response: %s: %s" % (response, dir(response)))
from django.http import HttpResponseRedirect
self.assertNotIsInstance(response, HttpResponseRedirect,
"Response is a redirect: did you forget to add follow=True " +
"to the request?")
self.assertIsNotNone(response.context, "Empty context in response: " +
"%s: %s" % (response, dir(response)))
if 'adminform' in response.context:
# if there are global errors, this will fail, and show us all
# the errors when it does.
self.assertDictEqual({}, response.context['adminform'].form.errors)
# if there are field errors, this will fail, and show us the
# the field name and the errors
for fieldset in response.context['adminform']:
for line in fieldset:
# should this be line.errors()?
# as FieldlineWithCustomReadOnlyField.errors
# is a method, not a property:
self.assertIsNone(line.errors,
"should not be any errors on %s" % line)
for field in line:
# similarly django.contrib.admin.helpers.AdminField.errors
# is a method:
self.assertIsNone(field.errors,
"should not be any errors on %s" % field)
self.assertIsNone(response.context['adminform'].form.non_field_errors)
self.assertIsNone(self.extract_error_message(response))
self.assertNotIn('adminform', response.context, "Unexpected " +
"admin form in response context: %s" % response)
self.assertIn('cl', response.context, "Missing changelist " +
"in response context: %s" % response)
def assert_admin_form_with_errors_not_changelist(self, response,
expected_field_errors=None, expected_non_field_errors=None):
"""
Checks that the response (to a POST to an admin change form) contains
an adminform with errors, which means that the update was
unsuccessful, and not a changelist, which would mean that the update
was successful when it should not have been.
Also check that the errors on the adminform are exactly what we
expected.
"""
from django.http import HttpResponseRedirect
self.assertNotIsInstance(response, HttpResponseRedirect,
('Unexpected redirect to %s: did the POST succeed when it ' +
'should have failed? Expected errors were: %s') %
(response.get('location', None), expected_field_errors))
self.assertTrue(hasattr(response, 'context'), "Missing context " +
"in response: %s: %s" % (response, dir(response)))
self.assertIsNotNone(response.context, "Empty context in response: " +
"%s: %s" % (response, dir(response)))
self.assertIn('adminform', response.context)
if expected_field_errors is not None:
self.assertDictEqual(expected_field_errors,
response.context['adminform'].form.errors)
"""
for fieldset in response.context['adminform']:
for line in fieldset:
self.assertEqual('', line.errors())
for field in line:
self.assertEqual('', field.errors())
"""
if expected_non_field_errors is not None:
self.assertListEqual(expected_non_field_errors,
response.context['adminform'].form.non_field_errors())
top_error = self.extract_error_message(response)
if not expected_field_errors and not expected_non_field_errors:
self.assertIsNone(top_error)
else:
self.assertIsNotNone(top_error)
import re
self.assertTrue(re.match('Please correct the error(s)? below.',
top_error), "Unexpected error message at top of form: %s" %
top_error)
self.assertNotIn('cl', response.context, "Unexpected changelist " +
"in response context: %s" % response)
class AptivateEnhancedTestCase(FormUtilsMixin, TestCase):
longMessage = True
def _pre_setup(self):
"""
We need to change the Haystack configuration before fixtures are
loaded, otherwise they end up in the developer's index and not the
temporary test index, which is bad for both developers and tests.
This is an internal interface and its use is not recommended.
"""
super(AptivateEnhancedTestCase, self)._pre_setup()
settings.MEDIA_ROOT = '/dev/shm/test_uploads'
import os
if os.path.exists(settings.MEDIA_ROOT):
import shutil
shutil.rmtree(settings.MEDIA_ROOT)
os.mkdir(settings.MEDIA_ROOT)
def setUp(self):
TestCase.setUp(self)
self.client = SuperClient()
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
from django.core.mail.backends.locmem import EmailBackend
EmailBackend() # create the outbox
from django.core import mail
self.emails = mail.outbox
from django.template import Template, VariableNode, FilterExpression
from django.template.debug import DebugVariableNode
self.addTypeEqualityFunc(Template, self.assertTemplateEqual)
# self.addTypeEqualityFunc(NodeList, self.assertListEqual)
self.addTypeEqualityFunc(VariableNode, self.assertVariableNodeEqual)
self.addTypeEqualityFunc(DebugVariableNode,
self.assertVariableNodeEqual)
self.addTypeEqualityFunc(FilterExpression,
self.assertFilterExpressionEqual)
import warnings
warnings.filterwarnings('error',
r"DateTimeField received a naive datetime",
RuntimeWarning, r'django\.db\.models\.fields')
from django.contrib.auth.hashers import make_password
self.test_password = 'testpass'
self.test_password_encrypted = make_password(self.test_password)
def assertTemplateEqual(self, first, second, msg=None):
self.assertListEqual(first.nodelist, second.nodelist, msg)
def assertVariableNodeEqual(self, first, second, msg=None):
self.assertEqual(first.filter_expression, second.filter_expression, msg)
def assertFilterExpressionEqual(self, first, second, msg=None):
self.assertEqual(first.token, second.token, msg)
def assertSequenceEqual(self, seq1, seq2,
msg=None, seq_type=None, max_diff=80*8):
"""
Argh! Copied and pasted from case.py to change one line: use
self.assertEquals instead of ==.
An equality assertion for ordered sequences (like lists and tuples).
For the purposes of this function, a valid ordered sequence type is one
which can be indexed, has a length, and has an equality operator.
Args:
seq1: The first sequence to compare.
seq2: The second sequence to compare.
seq_type: The expected datatype of the sequences, or None if no
datatype should be enforced.
msg: Optional message to use on failure instead of a list of
differences.
max_diff: Maximum size off the diff, larger diffs are not shown
"""
from unittest.util import safe_repr
if seq_type is not None:
seq_type_name = seq_type.__name__
if not isinstance(seq1, seq_type):
raise self.failureException('First sequence is not a %s: %s'
% (seq_type_name, safe_repr(seq1)))
if not isinstance(seq2, seq_type):
raise self.failureException('Second sequence is not a %s: %s'
% (seq_type_name, safe_repr(seq2)))
else:
seq_type_name = "sequence"
differing = None
try:
len1 = len(seq1)
except (TypeError, NotImplementedError):
differing = 'First %s has no length. Non-sequence?' % (
seq_type_name)
if differing is None:
try:
len2 = len(seq2)
except (TypeError, NotImplementedError):
differing = 'Second %s has no length. Non-sequence?' % (
seq_type_name)
if differing is None:
# here!
if len1 == len2:
try:
for i in range(len1):
self.assertEqual(seq1[i], seq2[i])
# all pass
return
except self.failureException:
# will be handled below
pass
seq1_repr = repr(seq1)
seq2_repr = repr(seq2)
if len(seq1_repr) > 30:
seq1_repr = seq1_repr[:30] + '...'
if len(seq2_repr) > 30:
seq2_repr = seq2_repr[:30] + '...'
elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr)
differing = '%ss differ: %s != %s\n' % elements
for i in xrange(min(len1, len2)):
try:
item1 = seq1[i]
except (TypeError, IndexError, NotImplementedError):
differing += ('\nUnable to index element %d of first %s\n' %
(i, seq_type_name))
break
try:
item2 = seq2[i]
except (TypeError, IndexError, NotImplementedError):
differing += ('\nUnable to index element %d of second %s\n' %
(i, seq_type_name))
break
if item1 != item2:
differing += ('\nFirst differing element %d:\n%s\n%s\n' %
(i, item1, item2))
break
else:
if (len1 == len2 and seq_type is None and
type(seq1) != type(seq2)):
# The sequences are the same, but have differing types.
return
if len1 > len2:
differing += ('\nFirst %s contains %d additional '
'elements.\n' % (seq_type_name, len1 - len2))
try:
differing += ('First extra element %d:\n%s\n' %
(len2, seq1[len2]))
except (TypeError, IndexError, NotImplementedError):
differing += ('Unable to index element %d '
'of first %s\n' % (len2, seq_type_name))
elif len1 < len2:
differing += ('\nSecond %s contains %d additional '
'elements.\n' % (seq_type_name, len2 - len1))
try:
differing += ('First extra element %d:\n%s\n' %
(len1, seq2[len1]))
except (TypeError, IndexError, NotImplementedError):
differing += ('Unable to index element %d '
'of second %s\n' % (len1, seq_type_name))
standardMsg = differing
diffMsg = '\n' + '\n'.join(
difflib.ndiff(pprint.pformat(seq1).splitlines(),
pprint.pformat(seq2).splitlines()))
standardMsg = self._truncateMessage(standardMsg, diffMsg)
msg = self._formatMessage(msg, standardMsg)
self.fail(msg)
def assign_fixture_to_filefield(self, fixture_file_name, filefield):
import sys
module = sys.modules[self.__class__.__module__]
import os.path
path = os.path.join(os.path.dirname(module.__file__), 'fixtures',
fixture_file_name)
from django.core.files import File as DjangoFile
df = DjangoFile(open(path))
filefield.save(fixture_file_name, df, save=False)
def login(self, user):
credentials = dict(username=user.username,
password=self.test_password)
self.assertIn('django.contrib.sessions', settings.INSTALLED_APPS,
"This method currently doesn't work if the " +
"django.contrib.sessions app is not enabled")
user_authenticated = authenticate(**credentials)
self.assertIsNotNone(user_authenticated, "authentication failed " +
"for %s" % credentials)
self.assertTrue(user_authenticated.is_active, "cannot log in as " +
"inactive user %s" % user)
self.fake_login_request = self.client.login(**credentials)
self.assertTrue(self.fake_login_request, "Login failed")
self.current_user = user
def assertInDict(self, member, container, msg=None):
"""
Returns the member if the assertion passes.
Makes sense that if you're asserting that a dictionary has a
member, you might want to use that member! Just saying. If not,
you can always throw it away.
"""
if isinstance(container, dict):
self.assertIsInstance(member, str, "Dict keys must be strings")
elif isinstance(container, str):
self.assertIsInstance(member, str, "Only strings can be in other strings")
self.assertIn(member, container, msg=msg)
try:
return container[member]
except TypeError as e:
raise TypeError(("%s (is the second argument really a " +
"dictionary? %s)") % (e, container))
def absolute_url_for_site(self, relative_url):
"""
Convert a relative URL to an absolute URL, using the name of the
current site, which is hackish but doesn't require a request object
(so it can be generated in an email, for example), makes the
canonical name configurable, and matches what the absurl
templatetag does.
"""
from django.contrib.sites.models import Site
return "http://%s%s" % (Site.objects.get_current().domain,
relative_url)
def absolute_url_for_request(self, relative_url):
"""
Convert a relative URL to an absolute URL, using the server name
hard-coded in django.test.client.RequestFactory, which matches the
value used by HttpRequest.build_absolute_uri when called by
the test client.
"""
return "http://%s%s" % ('testserver', relative_url)
XHTML_NS = "{http://www.w3.org/1999/xhtml}"
def xhtml(self, name):
return "%s%s" % (self.XHTML_NS, name)
def get_page_element(self, xpath, required=True):
self.assertTrue(self.client.last_response.content,
"Last response was empty or not parsed: %s" %
self.client.last_response)
element = self.client.last_response.parsed.find(xpath)
self.assertIsNotNone(element, "Failed to find %s in page: %s" %
(xpath, self.client.last_response.content))
return element
def assert_search_results_table_get_queryset(self, response):
try:
table = response.context['results_table']
except KeyError:
self.fail("No table in response context: %s" %
response.context.keys())
import django_tables2 as tables
self.assertIsInstance(table, tables.Table)
columns = table.base_columns.items()
self.assertNotIn('score', [c[0] for c in columns],
"Score column is disabled on request")
data = table.data
from django_tables2.tables import TableData
self.assertIsInstance(data, TableData)
queryset = data.queryset
from haystack.query import SearchQuerySet
self.assertIsInstance(queryset, SearchQuerySet)
return table, queryset
def assert_not_redirected(self, response, message=None):
from django.http import HttpResponseRedirect
self.assertNotIsInstance(response, HttpResponseRedirect,
message)
self.assertSequenceEqual([],
getattr(response, 'redirect_chain', []), message)
def assert_followed_redirect(self, response, expected_url,
expected_code=200, error_message_path=None):
return self.assertFollowedRedirect(response, expected_url,
expected_code, error_message_path)
def assertFollowedRedirect(self, response, expected_url,
expected_code=200, error_message_path=None):
expected_uri = response.real_request.build_absolute_uri(expected_url)
self.assertNotIn(int(response.status_code), (301, 302),
"Response was a redirect, but was not followed. " +
"Please add follow=True to the request call")
message = "Response was not a redirect to %s: " % expected_uri
message += "(there should be a redirect chain)\n\n"
"""
elif error_message_path:
error_message_path = './/' + error_message_path
error_message = response.parsed.findtext(error_message_path)
if error_message:
message += ": " + error_message
else:
message += (", and failed to find an error message matching %s"
% error_message_path)
"""
message += "The complete response (status %s) was: %s" % \
(response.status_code, response.content)
redirect_chain = self.assertInDict(
'redirect_chain', response.__dict__, message)
self.assert_no_form_with_errors(response)
expected_uri = response.real_request.build_absolute_uri(expected_url)
self.assertSequenceEqual([(expected_uri, 302)],
redirect_chain, message)
self.assertEquals(expected_code, response.status_code,
"final response, after following, should have been a " +
"%s, not this: %s" % (expected_code, response.content))
def assertRedirectedWithoutFollowing(self, response, expected_url,
status_code=302, host=None, msg_prefix=''):
"""Asserts that a response redirected to a specific URL. Unlike
:method:assertRedirects, this one will work for external links,
since it doesn't try to follow them.
"""
if msg_prefix:
msg_prefix += ": "